ruby-lsp 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
# ![On type formatting demo](../../misc/on_type_formatting.gif)
|
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
|