ruby-lsp 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -26
- data/VERSION +1 -1
- data/lib/ruby_lsp/handler.rb +54 -66
- data/lib/ruby_lsp/queue.rb +187 -0
- data/lib/ruby_lsp/requests/folding_ranges.rb +7 -3
- data/lib/ruby_lsp/requests/on_type_formatting.rb +135 -0
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +36 -11
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e66f508823e7d8e663c90e9990ae13bf26c3480d4ce9d7d43db14185518064d
|
4
|
+
data.tar.gz: 70cfc0be15d1ad988d66a641191fa64679109ae1f6be56afdc01ded665b0a439
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2f71a448863bf25e229032cb06d3ae7e7ebf1ef50b414e6486bd902ec7f100dad1ae4cb7160ef9c5fa8069f3ff0bcbb3da29d9c16a49f2e347f8d3d8797049a
|
7
|
+
data.tar.gz: 4cd747fb8505531a1ab0f1c28c8b3a09a134d544c1834b2f821c220bce2e3b6efce325b73326e23d92891a27ae5ae3cd9a79e9bbfdfa1b45c8f6e490b74d5c9a
|
data/CHANGELOG.md
CHANGED
@@ -6,32 +6,11 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
-
## [0.
|
10
|
-
|
11
|
-
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- Support document links (https://github.com/Shopify/ruby-lsp/pull/195)
|
16
|
-
- Avoid returning on request blocks (https://github.com/Shopify/ruby-lsp/pull/232)
|
17
|
-
- Better specify gemspec files (https://github.com/Shopify/ruby-lsp/pull/233)
|
18
|
-
- Include Kernel instance methods as special methods for semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/231)
|
19
|
-
- Fix call processing when message is a :call symbol literal (https://github.com/Shopify/ruby-lsp/pull/236)
|
20
|
-
- Alert users about non auto-correctable diagnostics (https://github.com/Shopify/ruby-lsp/pull/230)
|
21
|
-
- Let clients pull diagnostics instead of pushing on edits (https://github.com/Shopify/ruby-lsp/pull/242)
|
22
|
-
|
23
|
-
## [0.2.1]
|
24
|
-
|
25
|
-
- Implement the exit lifecycle request (https://github.com/Shopify/ruby-lsp/pull/198)
|
26
|
-
- Remove the Sorbet runtime from the gem's default load path (https://github.com/Shopify/ruby-lsp/pull/214)
|
27
|
-
- Return nil if the document is already formatted (https://github.com/Shopify/ruby-lsp/pull/216)
|
28
|
-
- Handle nameless keyword rest parameters in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/222)
|
29
|
-
- Display a warning on invalid RuboCop configuration (https://github.com/Shopify/ruby-lsp/pull/226)
|
30
|
-
- Centralize request handling logic in server.rb (https://github.com/Shopify/ruby-lsp/pull/221)
|
31
|
-
- Fix folding ranges for chained invocations involving an FCall (https://github.com/Shopify/ruby-lsp/pull/223)
|
32
|
-
- Fix handling of argument fowarding in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/228)
|
33
|
-
- Recover from initial syntax errors when opening documents (https://github.com/Shopify/ruby-lsp/pull/224)
|
34
|
-
- Highlight occurrences and definitions in document highlight (https://github.com/Shopify/ruby-lsp/pull/187)
|
9
|
+
## [0.3.0]
|
10
|
+
- Add on type formatting completions (https://github.com/Shopify/ruby-lsp/pull/253)
|
11
|
+
- Upgrade syntax_tree requirement to >= 3.4 (https://github.com/Shopify/ruby-lsp/pull/254)
|
12
|
+
- Show error message when there's a InfiniteCorrectionLoop exception (https://github.com/Shopify/ruby-lsp/pull/252)
|
13
|
+
- Add request cancellation (https://github.com/Shopify/ruby-lsp/pull/243)
|
35
14
|
|
36
15
|
## [0.2.0]
|
37
16
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/ruby_lsp/handler.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require "ruby_lsp/requests"
|
5
5
|
require "ruby_lsp/store"
|
6
|
-
require "
|
6
|
+
require "ruby_lsp/queue"
|
7
7
|
|
8
8
|
module RubyLsp
|
9
9
|
Interface = LanguageServer::Protocol::Interface
|
@@ -14,11 +14,35 @@ module RubyLsp
|
|
14
14
|
extend T::Sig
|
15
15
|
VOID = T.let(Object.new.freeze, Object)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
class RequestHandler < T::Struct
|
18
|
+
extend T::Sig
|
19
|
+
|
20
|
+
const :action, T.proc.params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
|
21
|
+
const :parallel, T::Boolean
|
22
|
+
prop :error_handler,
|
23
|
+
T.nilable(T.proc.params(error: StandardError, request: T::Hash[Symbol, T.untyped]).void)
|
24
|
+
|
25
|
+
# A proc that runs in case a request has errored. Receives the error and the original request as arguments. Useful
|
26
|
+
# for displaying window messages on errors
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
block: T.proc.bind(Handler).params(error: StandardError, request: T::Hash[Symbol, T.untyped]).void
|
30
|
+
).void
|
31
|
+
end
|
32
|
+
def on_error(&block)
|
33
|
+
self.error_handler = block
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
extend T::Sig
|
39
|
+
|
40
|
+
sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
|
41
|
+
def start(&blk)
|
42
|
+
handler = new
|
43
|
+
handler.instance_exec(&blk)
|
44
|
+
handler.start
|
45
|
+
end
|
22
46
|
end
|
23
47
|
|
24
48
|
sig { returns(Store) }
|
@@ -28,61 +52,50 @@ module RubyLsp
|
|
28
52
|
def initialize
|
29
53
|
@writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
|
30
54
|
@reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
|
31
|
-
@handlers = T.let({}, T::Hash[String,
|
55
|
+
@handlers = T.let({}, T::Hash[String, RequestHandler])
|
32
56
|
@store = T.let(Store.new, Store)
|
57
|
+
@queue = T.let(Queue.new(@writer, @handlers), Queue)
|
33
58
|
end
|
34
59
|
|
35
60
|
sig { void }
|
36
61
|
def start
|
37
62
|
$stderr.puts "Starting Ruby LSP..."
|
38
|
-
|
63
|
+
|
64
|
+
@reader.read do |request|
|
65
|
+
handler = @handlers[request[:method]]
|
66
|
+
next if handler.nil?
|
67
|
+
|
68
|
+
if handler.parallel
|
69
|
+
@queue.push(request)
|
70
|
+
else
|
71
|
+
result = @queue.execute(request)
|
72
|
+
@queue.finalize_request(result, request)
|
73
|
+
end
|
74
|
+
end
|
39
75
|
end
|
40
76
|
|
41
77
|
private
|
42
78
|
|
79
|
+
sig { params(id: T.any(String, Integer)).void }
|
80
|
+
def cancel_request(id)
|
81
|
+
@queue.cancel(id)
|
82
|
+
end
|
83
|
+
|
43
84
|
sig do
|
44
85
|
params(
|
45
86
|
msg: String,
|
87
|
+
parallel: T::Boolean,
|
46
88
|
blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
|
47
|
-
).
|
89
|
+
).returns(RequestHandler)
|
48
90
|
end
|
49
|
-
def on(msg, &blk)
|
50
|
-
@handlers[msg] = blk
|
51
|
-
end
|
52
|
-
|
53
|
-
sig { params(request: T::Hash[Symbol, T.untyped]).void }
|
54
|
-
def handle(request)
|
55
|
-
result = T.let(nil, T.untyped)
|
56
|
-
error = T.let(nil, T.nilable(StandardError))
|
57
|
-
handler = @handlers[request[:method]]
|
58
|
-
|
59
|
-
request_time = Benchmark.realtime do
|
60
|
-
if handler
|
61
|
-
begin
|
62
|
-
result = handler.call(request)
|
63
|
-
rescue StandardError => e
|
64
|
-
error = e
|
65
|
-
end
|
66
|
-
|
67
|
-
if error
|
68
|
-
@writer.write(
|
69
|
-
{
|
70
|
-
id: request[:id],
|
71
|
-
error: { code: Constant::ErrorCodes::INTERNAL_ERROR, message: error.inspect, data: request.to_json },
|
72
|
-
}
|
73
|
-
)
|
74
|
-
elsif result != VOID
|
75
|
-
@writer.write(id: request[:id], result: result)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
@writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
|
91
|
+
def on(msg, parallel: false, &blk)
|
92
|
+
@handlers[msg] = RequestHandler.new(action: blk, parallel: parallel)
|
81
93
|
end
|
82
94
|
|
83
95
|
sig { void }
|
84
96
|
def shutdown
|
85
97
|
$stderr.puts "Shutting down Ruby LSP..."
|
98
|
+
@queue.shutdown
|
86
99
|
store.clear
|
87
100
|
end
|
88
101
|
|
@@ -101,30 +114,5 @@ module RubyLsp
|
|
101
114
|
params: Interface::ShowMessageParams.new(type: type, message: message)
|
102
115
|
)
|
103
116
|
end
|
104
|
-
|
105
|
-
sig do
|
106
|
-
params(
|
107
|
-
request: T::Hash[Symbol, T.untyped],
|
108
|
-
request_time: Float,
|
109
|
-
error: T.nilable(StandardError)
|
110
|
-
).returns(T::Hash[Symbol, T.any(String, Float)])
|
111
|
-
end
|
112
|
-
def telemetry_params(request, request_time, error)
|
113
|
-
uri = request.dig(:params, :textDocument, :uri)
|
114
|
-
|
115
|
-
params = {
|
116
|
-
request: request[:method],
|
117
|
-
lspVersion: RubyLsp::VERSION,
|
118
|
-
requestTime: request_time,
|
119
|
-
}
|
120
|
-
|
121
|
-
if error
|
122
|
-
params[:errorClass] = error.class.name
|
123
|
-
params[:errorMessage] = error.message
|
124
|
-
end
|
125
|
-
|
126
|
-
params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
|
127
|
-
params
|
128
|
-
end
|
129
117
|
end
|
130
118
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "benchmark"
|
5
|
+
|
6
|
+
module RubyLsp
|
7
|
+
class Queue
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
class Cancelled < Interrupt; end
|
11
|
+
|
12
|
+
class Result < T::Struct
|
13
|
+
const :response, T.untyped # rubocop:disable Sorbet/ForbidUntypedStructProps
|
14
|
+
const :error, T.nilable(StandardError)
|
15
|
+
const :request_time, T.nilable(Float)
|
16
|
+
end
|
17
|
+
|
18
|
+
class Job < T::Struct
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
const :request, T::Hash[Symbol, T.untyped]
|
22
|
+
prop :cancelled, T::Boolean
|
23
|
+
|
24
|
+
sig { void }
|
25
|
+
def cancel
|
26
|
+
self.cancelled = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sig do
|
31
|
+
params(
|
32
|
+
writer: LanguageServer::Protocol::Transport::Stdio::Writer,
|
33
|
+
handlers: T::Hash[String, Handler::RequestHandler]
|
34
|
+
).void
|
35
|
+
end
|
36
|
+
def initialize(writer, handlers)
|
37
|
+
@writer = writer
|
38
|
+
@handlers = handlers
|
39
|
+
# The job queue is the actual list of requests we have to process
|
40
|
+
@job_queue = T.let(Thread::Queue.new, Thread::Queue)
|
41
|
+
# The jobs hash is just a way of keeping a handle to jobs based on the request ID, so we can cancel them
|
42
|
+
@jobs = T.let({}, T::Hash[T.any(String, Integer), Job])
|
43
|
+
# The current job is a handle to cancel jobs that are currently being processed
|
44
|
+
@current_job = T.let(nil, T.nilable(Job))
|
45
|
+
@mutex = T.let(Mutex.new, Mutex)
|
46
|
+
@worker = T.let(new_worker, Thread)
|
47
|
+
|
48
|
+
Thread.main.priority = 1
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { params(request: T::Hash[Symbol, T.untyped]).void }
|
52
|
+
def push(request)
|
53
|
+
job = Job.new(request: request, cancelled: false)
|
54
|
+
|
55
|
+
# Remember a handle to the job, so that we can cancel it
|
56
|
+
@mutex.synchronize do
|
57
|
+
@jobs[request[:id]] = job
|
58
|
+
end
|
59
|
+
|
60
|
+
@job_queue << job
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { params(id: T.any(String, Integer)).void }
|
64
|
+
def cancel(id)
|
65
|
+
@mutex.synchronize do
|
66
|
+
# Cancel the job if it's still in the queue
|
67
|
+
@jobs[id]&.cancel
|
68
|
+
|
69
|
+
# Cancel the job if we're in the middle of processing it
|
70
|
+
if @current_job&.request&.dig(:id) == id
|
71
|
+
@worker.raise(Cancelled)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { void }
|
77
|
+
def shutdown
|
78
|
+
# Close the queue so that we can no longer receive items
|
79
|
+
@job_queue.close
|
80
|
+
# Clear any remaining jobs so that the thread can terminate
|
81
|
+
@job_queue.clear
|
82
|
+
# Wait until the thread is finished
|
83
|
+
@worker.join
|
84
|
+
end
|
85
|
+
|
86
|
+
# Executes a request and returns a Queue::Result. No IO should happen in this method, because it can be cancelled in
|
87
|
+
# the middle with a raise
|
88
|
+
sig { params(request: T::Hash[Symbol, T.untyped]).returns(Queue::Result) }
|
89
|
+
def execute(request)
|
90
|
+
response = T.let(nil, T.untyped)
|
91
|
+
error = T.let(nil, T.nilable(StandardError))
|
92
|
+
|
93
|
+
request_time = Benchmark.realtime do
|
94
|
+
response = T.must(@handlers[request[:method]]).action.call(request)
|
95
|
+
rescue StandardError => e
|
96
|
+
error = e
|
97
|
+
end
|
98
|
+
|
99
|
+
Queue::Result.new(response: response, error: error, request_time: request_time)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Finalize a Queue::Result. All IO operations should happen here to avoid any issues with cancelling requests
|
103
|
+
sig do
|
104
|
+
params(
|
105
|
+
result: Result,
|
106
|
+
request: T::Hash[Symbol, T.untyped]
|
107
|
+
).void
|
108
|
+
end
|
109
|
+
def finalize_request(result, request)
|
110
|
+
error = result.error
|
111
|
+
if error
|
112
|
+
T.must(@handlers[request[:method]]).error_handler&.call(error, request)
|
113
|
+
|
114
|
+
@writer.write(
|
115
|
+
id: request[:id],
|
116
|
+
error: {
|
117
|
+
code: LanguageServer::Protocol::Constant::ErrorCodes::INTERNAL_ERROR,
|
118
|
+
message: result.error.inspect,
|
119
|
+
data: request.to_json,
|
120
|
+
},
|
121
|
+
)
|
122
|
+
elsif result.response != Handler::VOID
|
123
|
+
@writer.write(id: request[:id], result: result.response)
|
124
|
+
end
|
125
|
+
|
126
|
+
request_time = result.request_time
|
127
|
+
if request_time
|
128
|
+
@writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, result.error))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
sig { returns(Thread) }
|
135
|
+
def new_worker
|
136
|
+
Thread.new do
|
137
|
+
# Thread::Queue#pop is thread safe and will wait until an item is available
|
138
|
+
while (job = T.let(@job_queue.pop, T.nilable(Job)))
|
139
|
+
# The only time when the job is nil is when the queue is closed and we can then terminate the thread
|
140
|
+
|
141
|
+
request = job.request
|
142
|
+
@mutex.synchronize do
|
143
|
+
@jobs.delete(request[:id])
|
144
|
+
@current_job = job
|
145
|
+
end
|
146
|
+
|
147
|
+
begin
|
148
|
+
next if job.cancelled
|
149
|
+
|
150
|
+
result = execute(request)
|
151
|
+
rescue Cancelled
|
152
|
+
# We need to return nil to the client even if the request was cancelled
|
153
|
+
result = Queue::Result.new(response: nil, error: nil, request_time: nil)
|
154
|
+
ensure
|
155
|
+
@mutex.synchronize { @current_job = nil }
|
156
|
+
finalize_request(result, request) unless result.nil?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
sig do
|
163
|
+
params(
|
164
|
+
request: T::Hash[Symbol, T.untyped],
|
165
|
+
request_time: Float,
|
166
|
+
error: T.nilable(StandardError)
|
167
|
+
).returns(T::Hash[Symbol, T.any(String, Float)])
|
168
|
+
end
|
169
|
+
def telemetry_params(request, request_time, error)
|
170
|
+
uri = request.dig(:params, :textDocument, :uri)
|
171
|
+
|
172
|
+
params = {
|
173
|
+
request: request[:method],
|
174
|
+
lspVersion: RubyLsp::VERSION,
|
175
|
+
requestTime: request_time,
|
176
|
+
}
|
177
|
+
|
178
|
+
if error
|
179
|
+
params[:errorClass] = error.class.name
|
180
|
+
params[:errorMessage] = error.message
|
181
|
+
end
|
182
|
+
|
183
|
+
params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
|
184
|
+
params
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -105,9 +105,13 @@ module RubyLsp
|
|
105
105
|
sig { returns(Integer) }
|
106
106
|
attr_reader :end_line
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
class << self
|
109
|
+
extend T::Sig
|
110
|
+
|
111
|
+
sig { params(node: SyntaxTree::Node, kind: String).returns(PartialRange) }
|
112
|
+
def from(node, kind)
|
113
|
+
new(node.location.start_line - 1, node.location.end_line - 1, kind)
|
114
|
+
end
|
111
115
|
end
|
112
116
|
|
113
117
|
sig { params(start_line: Integer, end_line: Integer, kind: String).void }
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# The [on type formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting)
|
9
|
+
# request formats code as the user is typing. For example, automatically adding `end` to class definitions.
|
10
|
+
#
|
11
|
+
# # Example
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# class Foo # <-- upon adding a line break, on type formatting is triggered
|
15
|
+
# # <-- cursor ends up here
|
16
|
+
# end # <-- end is automatically added
|
17
|
+
# ```
|
18
|
+
class OnTypeFormatting < BaseRequest
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
END_REGEXES = T.let([
|
22
|
+
/(if|unless|for|while|class|module|until|def|case).*/,
|
23
|
+
/.*\sdo/,
|
24
|
+
], T::Array[Regexp])
|
25
|
+
|
26
|
+
sig { params(document: Document, position: Document::PositionShape, trigger_character: String).void }
|
27
|
+
def initialize(document, position, trigger_character)
|
28
|
+
super(document)
|
29
|
+
|
30
|
+
scanner = Document::Scanner.new(document.source)
|
31
|
+
line_begin = position[:line] == 0 ? 0 : scanner.find_position({ line: position[:line] - 1, character: 0 })
|
32
|
+
line_end = scanner.find_position(position)
|
33
|
+
line = T.must(@document.source[line_begin..line_end])
|
34
|
+
|
35
|
+
@indentation = T.let(find_indentation(line), Integer)
|
36
|
+
@previous_line = T.let(line.strip.chomp, String)
|
37
|
+
@position = position
|
38
|
+
@edits = T.let([], T::Array[Interface::TextEdit])
|
39
|
+
@trigger_character = trigger_character
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
|
43
|
+
def run
|
44
|
+
return unless @document.syntax_errors?
|
45
|
+
|
46
|
+
case @trigger_character
|
47
|
+
when "{"
|
48
|
+
handle_curly_brace
|
49
|
+
when "|"
|
50
|
+
handle_pipe
|
51
|
+
when "\n"
|
52
|
+
handle_statement_end
|
53
|
+
end
|
54
|
+
|
55
|
+
@edits
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
sig { void }
|
61
|
+
def handle_pipe
|
62
|
+
return unless /".*|/.match?(@previous_line)
|
63
|
+
|
64
|
+
add_edit_with_text("|")
|
65
|
+
move_cursor_to(@position[:line], @position[:character])
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { void }
|
69
|
+
def handle_curly_brace
|
70
|
+
return unless /".*#\{/.match?(@previous_line)
|
71
|
+
|
72
|
+
add_edit_with_text("}")
|
73
|
+
move_cursor_to(@position[:line], @position[:character])
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { void }
|
77
|
+
def handle_statement_end
|
78
|
+
return unless END_REGEXES.any? { |regex| regex.match?(@previous_line) }
|
79
|
+
|
80
|
+
indents = " " * @indentation
|
81
|
+
|
82
|
+
add_edit_with_text(" \n#{indents}end")
|
83
|
+
move_cursor_to(@position[:line], @indentation + 2)
|
84
|
+
end
|
85
|
+
|
86
|
+
sig { params(text: String).void }
|
87
|
+
def add_edit_with_text(text)
|
88
|
+
position = Interface::Position.new(
|
89
|
+
line: @position[:line],
|
90
|
+
character: @position[:character]
|
91
|
+
)
|
92
|
+
|
93
|
+
@edits << Interface::TextEdit.new(
|
94
|
+
range: Interface::Range.new(
|
95
|
+
start: position,
|
96
|
+
end: position
|
97
|
+
),
|
98
|
+
new_text: text
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(line: Integer, character: Integer).void }
|
103
|
+
def move_cursor_to(line, character)
|
104
|
+
position = Interface::Position.new(
|
105
|
+
line: line,
|
106
|
+
character: character
|
107
|
+
)
|
108
|
+
|
109
|
+
# The $0 is a special snippet anchor that moves the cursor to that given position. See the snippets
|
110
|
+
# documentation for more information:
|
111
|
+
# https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets
|
112
|
+
@edits << Interface::TextEdit.new(
|
113
|
+
range: Interface::Range.new(
|
114
|
+
start: position,
|
115
|
+
end: position
|
116
|
+
),
|
117
|
+
new_text: "$0"
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(line: String).returns(Integer) }
|
122
|
+
def find_indentation(line)
|
123
|
+
count = 0
|
124
|
+
|
125
|
+
line.chars.each do |c|
|
126
|
+
break unless c == " "
|
127
|
+
|
128
|
+
count += 1
|
129
|
+
end
|
130
|
+
|
131
|
+
count
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -10,6 +10,7 @@ module RubyLsp
|
|
10
10
|
# - {RubyLsp::Requests::SelectionRanges}
|
11
11
|
# - {RubyLsp::Requests::SemanticHighlighting}
|
12
12
|
# - {RubyLsp::Requests::Formatting}
|
13
|
+
# - {RubyLsp::Requests::OnTypeFormatting}
|
13
14
|
# - {RubyLsp::Requests::Diagnostics}
|
14
15
|
# - {RubyLsp::Requests::CodeActions}
|
15
16
|
# - {RubyLsp::Requests::DocumentHighlight}
|
@@ -21,6 +22,7 @@ module RubyLsp
|
|
21
22
|
autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
|
22
23
|
autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
|
23
24
|
autoload :Formatting, "ruby_lsp/requests/formatting"
|
25
|
+
autoload :OnTypeFormatting, "ruby_lsp/requests/on_type_formatting"
|
24
26
|
autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
|
25
27
|
autoload :CodeActions, "ruby_lsp/requests/code_actions"
|
26
28
|
autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -48,6 +48,13 @@ module RubyLsp
|
|
48
48
|
}
|
49
49
|
end
|
50
50
|
|
51
|
+
on_type_formatting_provider = if enabled_features.include?("onTypeFormatting")
|
52
|
+
Interface::DocumentOnTypeFormattingOptions.new(
|
53
|
+
first_trigger_character: "{",
|
54
|
+
more_trigger_character: ["\n", "|"]
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
51
58
|
# TODO: switch back to using Interface::ServerCapabilities once the gem is updated for spec 3.17
|
52
59
|
Interface::InitializeResult.new(
|
53
60
|
capabilities: {
|
@@ -63,6 +70,7 @@ module RubyLsp
|
|
63
70
|
documentFormattingProvider: enabled_features.include?("formatting"),
|
64
71
|
documentHighlightProvider: enabled_features.include?("documentHighlights"),
|
65
72
|
codeActionProvider: enabled_features.include?("codeActions"),
|
73
|
+
documentOnTypeFormattingProvider: on_type_formatting_provider,
|
66
74
|
diagnosticProvider: diagnostics_provider,
|
67
75
|
}.reject { |_, v| !v }
|
68
76
|
)
|
@@ -91,26 +99,26 @@ module RubyLsp
|
|
91
99
|
Handler::VOID
|
92
100
|
end
|
93
101
|
|
94
|
-
on("textDocument/documentSymbol") do |request|
|
102
|
+
on("textDocument/documentSymbol", parallel: true) do |request|
|
95
103
|
store.cache_fetch(request.dig(:params, :textDocument, :uri), :document_symbol) do |document|
|
96
104
|
Requests::DocumentSymbol.new(document).run
|
97
105
|
end
|
98
106
|
end
|
99
107
|
|
100
|
-
on("textDocument/documentLink") do |request|
|
108
|
+
on("textDocument/documentLink", parallel: true) do |request|
|
101
109
|
uri = request.dig(:params, :textDocument, :uri)
|
102
110
|
store.cache_fetch(uri, :document_link) do |document|
|
103
111
|
RubyLsp::Requests::DocumentLink.new(uri, document).run
|
104
112
|
end
|
105
113
|
end
|
106
114
|
|
107
|
-
on("textDocument/foldingRange") do |request|
|
115
|
+
on("textDocument/foldingRange", parallel: true) do |request|
|
108
116
|
store.cache_fetch(request.dig(:params, :textDocument, :uri), :folding_ranges) do |document|
|
109
117
|
Requests::FoldingRanges.new(document).run
|
110
118
|
end
|
111
119
|
end
|
112
120
|
|
113
|
-
on("textDocument/selectionRange") do |request|
|
121
|
+
on("textDocument/selectionRange", parallel: true) do |request|
|
114
122
|
uri = request.dig(:params, :textDocument, :uri)
|
115
123
|
positions = request.dig(:params, :positions)
|
116
124
|
|
@@ -132,7 +140,7 @@ module RubyLsp
|
|
132
140
|
end
|
133
141
|
end
|
134
142
|
|
135
|
-
on("textDocument/semanticTokens/full") do |request|
|
143
|
+
on("textDocument/semanticTokens/full", parallel: true) do |request|
|
136
144
|
store.cache_fetch(request.dig(:params, :textDocument, :uri), :semantic_highlighting) do |document|
|
137
145
|
T.cast(
|
138
146
|
Requests::SemanticHighlighting.new(
|
@@ -144,13 +152,24 @@ module RubyLsp
|
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
147
|
-
on("textDocument/formatting") do |request|
|
155
|
+
on("textDocument/formatting", parallel: true) do |request|
|
148
156
|
uri = request.dig(:params, :textDocument, :uri)
|
149
157
|
|
150
158
|
Requests::Formatting.new(uri, store.get(uri)).run
|
159
|
+
rescue RuboCop::Runner::InfiniteCorrectionLoop => e
|
160
|
+
show_message(Constant::MessageType::ERROR, "Error from RuboCop: #{e.message}")
|
161
|
+
nil
|
151
162
|
end
|
152
163
|
|
153
|
-
on("textDocument/
|
164
|
+
on("textDocument/onTypeFormatting", parallel: true) do |request|
|
165
|
+
uri = request.dig(:params, :textDocument, :uri)
|
166
|
+
position = request.dig(:params, :position)
|
167
|
+
character = request.dig(:params, :ch)
|
168
|
+
|
169
|
+
Requests::OnTypeFormatting.new(store.get(uri), position, character).run
|
170
|
+
end
|
171
|
+
|
172
|
+
on("textDocument/documentHighlight", parallel: true) do |request|
|
154
173
|
document = store.get(request.dig(:params, :textDocument, :uri))
|
155
174
|
|
156
175
|
if document.parsed?
|
@@ -158,7 +177,7 @@ module RubyLsp
|
|
158
177
|
end
|
159
178
|
end
|
160
179
|
|
161
|
-
on("textDocument/codeAction") do |request|
|
180
|
+
on("textDocument/codeAction", parallel: true) do |request|
|
162
181
|
uri = request.dig(:params, :textDocument, :uri)
|
163
182
|
range = request.dig(:params, :range)
|
164
183
|
start_line = range.dig(:start, :line)
|
@@ -169,6 +188,11 @@ module RubyLsp
|
|
169
188
|
end
|
170
189
|
end
|
171
190
|
|
191
|
+
on("$/cancelRequest") do |request|
|
192
|
+
cancel_request(request[:params][:id])
|
193
|
+
Handler::VOID
|
194
|
+
end
|
195
|
+
|
172
196
|
on("textDocument/diagnostic") do |request|
|
173
197
|
uri = request.dig(:params, :textDocument, :uri)
|
174
198
|
response = store.cache_fetch(uri, :diagnostics) do |document|
|
@@ -176,9 +200,10 @@ module RubyLsp
|
|
176
200
|
end
|
177
201
|
|
178
202
|
{ kind: "full", items: response.map(&:to_lsp_diagnostic) } if response
|
179
|
-
|
180
|
-
|
181
|
-
|
203
|
+
end.on_error do |error|
|
204
|
+
if error.is_a?(RuboCop::ValidationError)
|
205
|
+
show_message(Constant::MessageType::ERROR, "Error in RuboCop configuration file: #{error.message}")
|
206
|
+
end
|
182
207
|
end
|
183
208
|
|
184
209
|
on("shutdown") { shutdown }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '3.4'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '3.4'
|
55
55
|
description: An opinionated language server for Ruby
|
56
56
|
email:
|
57
57
|
- ruby@shopify.com
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/ruby_lsp/document.rb
|
70
70
|
- lib/ruby_lsp/handler.rb
|
71
71
|
- lib/ruby_lsp/internal.rb
|
72
|
+
- lib/ruby_lsp/queue.rb
|
72
73
|
- lib/ruby_lsp/requests.rb
|
73
74
|
- lib/ruby_lsp/requests/base_request.rb
|
74
75
|
- lib/ruby_lsp/requests/code_actions.rb
|
@@ -78,6 +79,7 @@ files:
|
|
78
79
|
- lib/ruby_lsp/requests/document_symbol.rb
|
79
80
|
- lib/ruby_lsp/requests/folding_ranges.rb
|
80
81
|
- lib/ruby_lsp/requests/formatting.rb
|
82
|
+
- lib/ruby_lsp/requests/on_type_formatting.rb
|
81
83
|
- lib/ruby_lsp/requests/selection_ranges.rb
|
82
84
|
- lib/ruby_lsp/requests/semantic_highlighting.rb
|
83
85
|
- lib/ruby_lsp/requests/support/highlight_target.rb
|