ruby-lsp 0.17.15 → 0.17.16
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/VERSION +1 -1
- data/lib/ruby_lsp/document.rb +16 -2
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/inlay_hints.rb +11 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +12 -137
- data/lib/ruby_lsp/requests/diagnostics.rb +3 -1
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +113 -6
- data/lib/ruby_lsp/requests/support/common.rb +0 -9
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -4
- data/lib/ruby_lsp/server.rb +78 -16
- data/lib/ruby_lsp/store.rb +1 -1
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +38 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc4416ea8876d8207b6c1955cd47af5f3312a0f0dc3644563bfd35138a903535
|
4
|
+
data.tar.gz: 5961dd8ca49539c20049509c0afed8b2442c3d16ec12d1fb84c9e45dbd027760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b77547221c1750871ff6e1a3635b0ae6f945b86862f484d20eb7587a1feb9830e1d1d7770f16c2e7efeb70e6a953529041f8acc8fe3d7c30940687d20563f96
|
7
|
+
data.tar.gz: 1093a1a6ac45e8627f26a6c9dbb0017079d2804904fe9a4303e3698d4131c6eeedf2bbafac93e6475f676860624b9c53d482c52cfe8380e67ed05ce525e99957
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.16
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -17,6 +17,11 @@ module RubyLsp
|
|
17
17
|
|
18
18
|
ParseResultType = type_member
|
19
19
|
|
20
|
+
# This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
|
21
|
+
# This is the same number used by the TypeScript extension in VS Code
|
22
|
+
MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
|
23
|
+
EMPTY_CACHE = T.let(Object.new.freeze, Object)
|
24
|
+
|
20
25
|
abstract!
|
21
26
|
|
22
27
|
sig { returns(ParseResultType) }
|
@@ -34,9 +39,13 @@ module RubyLsp
|
|
34
39
|
sig { returns(Encoding) }
|
35
40
|
attr_reader :encoding
|
36
41
|
|
42
|
+
sig { returns(T.any(Interface::SemanticTokens, Object)) }
|
43
|
+
attr_accessor :semantic_tokens
|
44
|
+
|
37
45
|
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
|
38
46
|
def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
|
39
|
-
@cache = T.let(
|
47
|
+
@cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
|
48
|
+
@semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
|
40
49
|
@encoding = T.let(encoding, Encoding)
|
41
50
|
@source = T.let(source, String)
|
42
51
|
@version = T.let(version, Integer)
|
@@ -63,7 +72,7 @@ module RubyLsp
|
|
63
72
|
end
|
64
73
|
def cache_fetch(request_name, &block)
|
65
74
|
cached = @cache[request_name]
|
66
|
-
return cached if cached
|
75
|
+
return cached if cached != EMPTY_CACHE
|
67
76
|
|
68
77
|
result = block.call(self)
|
69
78
|
@cache[request_name] = result
|
@@ -108,6 +117,11 @@ module RubyLsp
|
|
108
117
|
Scanner.new(@source, @encoding)
|
109
118
|
end
|
110
119
|
|
120
|
+
sig { returns(T::Boolean) }
|
121
|
+
def past_expensive_limit?
|
122
|
+
@source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
|
123
|
+
end
|
124
|
+
|
111
125
|
class Scanner
|
112
126
|
extend T::Sig
|
113
127
|
|
@@ -366,7 +366,7 @@ module RubyLsp
|
|
366
366
|
|
367
367
|
return unless range
|
368
368
|
|
369
|
-
guessed_type = type.name
|
369
|
+
guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name
|
370
370
|
|
371
371
|
@index.method_completion_candidates(method_name, type.name).each do |entry|
|
372
372
|
entry_name = entry.name
|
@@ -69,6 +69,17 @@ module RubyLsp
|
|
69
69
|
tooltip: tooltip,
|
70
70
|
)
|
71
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
sig { params(node: T.nilable(Prism::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
76
|
+
def visible?(node, range)
|
77
|
+
return true if range.nil?
|
78
|
+
return false if node.nil?
|
79
|
+
|
80
|
+
loc = node.location
|
81
|
+
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
@@ -14,7 +14,7 @@ module RubyLsp
|
|
14
14
|
Kernel.methods(false),
|
15
15
|
Bundler::Dsl.instance_methods(false),
|
16
16
|
Module.private_instance_methods(false),
|
17
|
-
].flatten.map(&:to_s),
|
17
|
+
].flatten.map(&:to_s).freeze,
|
18
18
|
T::Array[String],
|
19
19
|
)
|
20
20
|
|
@@ -22,12 +22,10 @@ module RubyLsp
|
|
22
22
|
params(
|
23
23
|
dispatcher: Prism::Dispatcher,
|
24
24
|
response_builder: ResponseBuilders::SemanticHighlighting,
|
25
|
-
range: T.nilable(T::Range[Integer]),
|
26
25
|
).void
|
27
26
|
end
|
28
|
-
def initialize(dispatcher, response_builder
|
27
|
+
def initialize(dispatcher, response_builder)
|
29
28
|
@response_builder = response_builder
|
30
|
-
@range = range
|
31
29
|
@special_methods = T.let(nil, T.nilable(T::Array[String]))
|
32
30
|
@current_scope = T.let(ParameterScope.new, ParameterScope)
|
33
31
|
@inside_regex_capture = T.let(false, T::Boolean)
|
@@ -43,7 +41,6 @@ module RubyLsp
|
|
43
41
|
:on_block_node_leave,
|
44
42
|
:on_self_node_enter,
|
45
43
|
:on_module_node_enter,
|
46
|
-
:on_local_variable_write_node_enter,
|
47
44
|
:on_local_variable_read_node_enter,
|
48
45
|
:on_block_parameter_node_enter,
|
49
46
|
:on_required_keyword_parameter_node_enter,
|
@@ -52,16 +49,6 @@ module RubyLsp
|
|
52
49
|
:on_optional_parameter_node_enter,
|
53
50
|
:on_required_parameter_node_enter,
|
54
51
|
:on_rest_parameter_node_enter,
|
55
|
-
:on_constant_read_node_enter,
|
56
|
-
:on_constant_write_node_enter,
|
57
|
-
:on_constant_and_write_node_enter,
|
58
|
-
:on_constant_operator_write_node_enter,
|
59
|
-
:on_constant_or_write_node_enter,
|
60
|
-
:on_constant_target_node_enter,
|
61
|
-
:on_constant_path_node_enter,
|
62
|
-
:on_local_variable_and_write_node_enter,
|
63
|
-
:on_local_variable_operator_write_node_enter,
|
64
|
-
:on_local_variable_or_write_node_enter,
|
65
52
|
:on_local_variable_target_node_enter,
|
66
53
|
:on_block_local_variable_node_enter,
|
67
54
|
:on_match_write_node_enter,
|
@@ -74,7 +61,6 @@ module RubyLsp
|
|
74
61
|
sig { params(node: Prism::CallNode).void }
|
75
62
|
def on_call_node_enter(node)
|
76
63
|
return if @inside_implicit_node
|
77
|
-
return unless visible?(node, @range)
|
78
64
|
|
79
65
|
message = node.message
|
80
66
|
return unless message
|
@@ -85,8 +71,14 @@ module RubyLsp
|
|
85
71
|
return if message == "=~"
|
86
72
|
return if special_method?(message)
|
87
73
|
|
88
|
-
|
89
|
-
|
74
|
+
if Requests::Support::Sorbet.annotation?(node)
|
75
|
+
@response_builder.add_token(T.must(node.message_loc), :type)
|
76
|
+
elsif !node.receiver && !node.opening_loc
|
77
|
+
# If the node has a receiver, then the syntax is not ambiguous and semantic highlighting is not necessary to
|
78
|
+
# determine that the token is a method call. The only ambiguous case is method calls with implicit self
|
79
|
+
# receiver and no parenthesis, which may be confused with local variables
|
80
|
+
@response_builder.add_token(T.must(node.message_loc), :method)
|
81
|
+
end
|
90
82
|
end
|
91
83
|
|
92
84
|
sig { params(node: Prism::MatchWriteNode).void }
|
@@ -104,55 +96,9 @@ module RubyLsp
|
|
104
96
|
@inside_regex_capture = true if node.call.message == "=~"
|
105
97
|
end
|
106
98
|
|
107
|
-
sig { params(node: Prism::ConstantReadNode).void }
|
108
|
-
def on_constant_read_node_enter(node)
|
109
|
-
return if @inside_implicit_node
|
110
|
-
return unless visible?(node, @range)
|
111
|
-
|
112
|
-
@response_builder.add_token(node.location, :namespace)
|
113
|
-
end
|
114
|
-
|
115
|
-
sig { params(node: Prism::ConstantWriteNode).void }
|
116
|
-
def on_constant_write_node_enter(node)
|
117
|
-
return unless visible?(node, @range)
|
118
|
-
|
119
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
120
|
-
end
|
121
|
-
|
122
|
-
sig { params(node: Prism::ConstantAndWriteNode).void }
|
123
|
-
def on_constant_and_write_node_enter(node)
|
124
|
-
return unless visible?(node, @range)
|
125
|
-
|
126
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
127
|
-
end
|
128
|
-
|
129
|
-
sig { params(node: Prism::ConstantOperatorWriteNode).void }
|
130
|
-
def on_constant_operator_write_node_enter(node)
|
131
|
-
return unless visible?(node, @range)
|
132
|
-
|
133
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
134
|
-
end
|
135
|
-
|
136
|
-
sig { params(node: Prism::ConstantOrWriteNode).void }
|
137
|
-
def on_constant_or_write_node_enter(node)
|
138
|
-
return unless visible?(node, @range)
|
139
|
-
|
140
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
141
|
-
end
|
142
|
-
|
143
|
-
sig { params(node: Prism::ConstantTargetNode).void }
|
144
|
-
def on_constant_target_node_enter(node)
|
145
|
-
return unless visible?(node, @range)
|
146
|
-
|
147
|
-
@response_builder.add_token(node.location, :namespace)
|
148
|
-
end
|
149
|
-
|
150
99
|
sig { params(node: Prism::DefNode).void }
|
151
100
|
def on_def_node_enter(node)
|
152
101
|
@current_scope = ParameterScope.new(@current_scope)
|
153
|
-
return unless visible?(node, @range)
|
154
|
-
|
155
|
-
@response_builder.add_token(node.name_loc, :method, [:declaration])
|
156
102
|
end
|
157
103
|
|
158
104
|
sig { params(node: Prism::DefNode).void }
|
@@ -184,77 +130,43 @@ module RubyLsp
|
|
184
130
|
sig { params(node: Prism::RequiredKeywordParameterNode).void }
|
185
131
|
def on_required_keyword_parameter_node_enter(node)
|
186
132
|
@current_scope << node.name
|
187
|
-
return unless visible?(node, @range)
|
188
|
-
|
189
|
-
location = node.name_loc
|
190
|
-
@response_builder.add_token(location.copy(length: location.length - 1), :parameter)
|
191
133
|
end
|
192
134
|
|
193
135
|
sig { params(node: Prism::OptionalKeywordParameterNode).void }
|
194
136
|
def on_optional_keyword_parameter_node_enter(node)
|
195
137
|
@current_scope << node.name
|
196
|
-
return unless visible?(node, @range)
|
197
|
-
|
198
|
-
location = node.name_loc
|
199
|
-
@response_builder.add_token(location.copy(length: location.length - 1), :parameter)
|
200
138
|
end
|
201
139
|
|
202
140
|
sig { params(node: Prism::KeywordRestParameterNode).void }
|
203
141
|
def on_keyword_rest_parameter_node_enter(node)
|
204
142
|
name = node.name
|
205
|
-
|
206
|
-
if name
|
207
|
-
@current_scope << name.to_sym
|
208
|
-
|
209
|
-
@response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
|
210
|
-
end
|
143
|
+
@current_scope << name.to_sym if name
|
211
144
|
end
|
212
145
|
|
213
146
|
sig { params(node: Prism::OptionalParameterNode).void }
|
214
147
|
def on_optional_parameter_node_enter(node)
|
215
148
|
@current_scope << node.name
|
216
|
-
return unless visible?(node, @range)
|
217
|
-
|
218
|
-
@response_builder.add_token(node.name_loc, :parameter)
|
219
149
|
end
|
220
150
|
|
221
151
|
sig { params(node: Prism::RequiredParameterNode).void }
|
222
152
|
def on_required_parameter_node_enter(node)
|
223
153
|
@current_scope << node.name
|
224
|
-
return unless visible?(node, @range)
|
225
|
-
|
226
|
-
@response_builder.add_token(node.location, :parameter)
|
227
154
|
end
|
228
155
|
|
229
156
|
sig { params(node: Prism::RestParameterNode).void }
|
230
157
|
def on_rest_parameter_node_enter(node)
|
231
158
|
name = node.name
|
232
|
-
|
233
|
-
if name
|
234
|
-
@current_scope << name.to_sym
|
235
|
-
|
236
|
-
@response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
|
237
|
-
end
|
159
|
+
@current_scope << name.to_sym if name
|
238
160
|
end
|
239
161
|
|
240
162
|
sig { params(node: Prism::SelfNode).void }
|
241
163
|
def on_self_node_enter(node)
|
242
|
-
return unless visible?(node, @range)
|
243
|
-
|
244
164
|
@response_builder.add_token(node.location, :variable, [:default_library])
|
245
165
|
end
|
246
166
|
|
247
|
-
sig { params(node: Prism::LocalVariableWriteNode).void }
|
248
|
-
def on_local_variable_write_node_enter(node)
|
249
|
-
return unless visible?(node, @range)
|
250
|
-
|
251
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
252
|
-
end
|
253
|
-
|
254
167
|
sig { params(node: Prism::LocalVariableReadNode).void }
|
255
168
|
def on_local_variable_read_node_enter(node)
|
256
169
|
return if @inside_implicit_node
|
257
|
-
return unless visible?(node, @range)
|
258
170
|
|
259
171
|
# Numbered parameters
|
260
172
|
if /_\d+/.match?(node.name)
|
@@ -265,27 +177,6 @@ module RubyLsp
|
|
265
177
|
@response_builder.add_token(node.location, @current_scope.type_for(node.name))
|
266
178
|
end
|
267
179
|
|
268
|
-
sig { params(node: Prism::LocalVariableAndWriteNode).void }
|
269
|
-
def on_local_variable_and_write_node_enter(node)
|
270
|
-
return unless visible?(node, @range)
|
271
|
-
|
272
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
273
|
-
end
|
274
|
-
|
275
|
-
sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
|
276
|
-
def on_local_variable_operator_write_node_enter(node)
|
277
|
-
return unless visible?(node, @range)
|
278
|
-
|
279
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
280
|
-
end
|
281
|
-
|
282
|
-
sig { params(node: Prism::LocalVariableOrWriteNode).void }
|
283
|
-
def on_local_variable_or_write_node_enter(node)
|
284
|
-
return unless visible?(node, @range)
|
285
|
-
|
286
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
287
|
-
end
|
288
|
-
|
289
180
|
sig { params(node: Prism::LocalVariableTargetNode).void }
|
290
181
|
def on_local_variable_target_node_enter(node)
|
291
182
|
# If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
|
@@ -294,15 +185,11 @@ module RubyLsp
|
|
294
185
|
# prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
|
295
186
|
return if @inside_regex_capture
|
296
187
|
|
297
|
-
return unless visible?(node, @range)
|
298
|
-
|
299
188
|
@response_builder.add_token(node.location, @current_scope.type_for(node.name))
|
300
189
|
end
|
301
190
|
|
302
191
|
sig { params(node: Prism::ClassNode).void }
|
303
192
|
def on_class_node_enter(node)
|
304
|
-
return unless visible?(node, @range)
|
305
|
-
|
306
193
|
constant_path = node.constant_path
|
307
194
|
|
308
195
|
if constant_path.is_a?(Prism::ConstantReadNode)
|
@@ -342,8 +229,6 @@ module RubyLsp
|
|
342
229
|
|
343
230
|
sig { params(node: Prism::ModuleNode).void }
|
344
231
|
def on_module_node_enter(node)
|
345
|
-
return unless visible?(node, @range)
|
346
|
-
|
347
232
|
constant_path = node.constant_path
|
348
233
|
|
349
234
|
if constant_path.is_a?(Prism::ConstantReadNode)
|
@@ -365,8 +250,6 @@ module RubyLsp
|
|
365
250
|
|
366
251
|
sig { params(node: Prism::ImplicitNode).void }
|
367
252
|
def on_implicit_node_enter(node)
|
368
|
-
return unless visible?(node, @range)
|
369
|
-
|
370
253
|
@inside_implicit_node = true
|
371
254
|
end
|
372
255
|
|
@@ -375,14 +258,6 @@ module RubyLsp
|
|
375
258
|
@inside_implicit_node = false
|
376
259
|
end
|
377
260
|
|
378
|
-
sig { params(node: Prism::ConstantPathNode).void }
|
379
|
-
def on_constant_path_node_enter(node)
|
380
|
-
return if @inside_implicit_node
|
381
|
-
return unless visible?(node, @range)
|
382
|
-
|
383
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
384
|
-
end
|
385
|
-
|
386
261
|
private
|
387
262
|
|
388
263
|
# Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
|
@@ -46,7 +46,9 @@ module RubyLsp
|
|
46
46
|
diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
|
47
47
|
|
48
48
|
# Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
|
49
|
-
|
49
|
+
if @document.syntax_error? || @active_linters.empty? || @document.past_expensive_limit?
|
50
|
+
return diagnostics
|
51
|
+
end
|
50
52
|
|
51
53
|
@active_linters.each do |linter|
|
52
54
|
linter_diagnostics = linter.run_diagnostic(@uri, @document)
|
@@ -19,6 +19,16 @@ module RubyLsp
|
|
19
19
|
# some_invocation # --> semantic highlighting: method invocation
|
20
20
|
# var # --> semantic highlighting: local variable
|
21
21
|
# end
|
22
|
+
#
|
23
|
+
# # Strategy
|
24
|
+
#
|
25
|
+
# To maximize editor performance, the Ruby LSP will return the minimum number of semantic tokens, since applying
|
26
|
+
# them is an expensive operation for the client. This means that the server only returns tokens for ambiguous pieces
|
27
|
+
# of syntax, such as method invocations with no receivers or parenthesis (which can be confused with local
|
28
|
+
# variables).
|
29
|
+
#
|
30
|
+
# Offloading as much handling as possible to Text Mate grammars or equivalent will guarantee responsiveness in the
|
31
|
+
# editor and allow for a much smoother experience.
|
22
32
|
# ```
|
23
33
|
class SemanticHighlighting < Request
|
24
34
|
extend T::Sig
|
@@ -35,28 +45,125 @@ module RubyLsp
|
|
35
45
|
token_modifiers: ResponseBuilders::SemanticHighlighting::TOKEN_MODIFIERS.keys,
|
36
46
|
),
|
37
47
|
range: true,
|
38
|
-
full: { delta:
|
48
|
+
full: { delta: true },
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The compute_delta method receives the current semantic tokens and the previous semantic tokens and then tries
|
53
|
+
# to compute the smallest possible semantic token edit that will turn previous into current
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
current_tokens: T::Array[Integer],
|
57
|
+
previous_tokens: T::Array[Integer],
|
58
|
+
result_id: String,
|
59
|
+
).returns(Interface::SemanticTokensDelta)
|
60
|
+
end
|
61
|
+
def compute_delta(current_tokens, previous_tokens, result_id)
|
62
|
+
# Find the index of the first token that is different between the two sets of tokens
|
63
|
+
first_different_position = current_tokens.zip(previous_tokens).find_index { |new, old| new != old }
|
64
|
+
|
65
|
+
# When deleting a token from the end, the first_different_position will be nil, but since we're removing at
|
66
|
+
# the end, then we have to initialize it to the length of the current tokens after the deletion
|
67
|
+
if !first_different_position && current_tokens.length < previous_tokens.length
|
68
|
+
first_different_position = current_tokens.length
|
69
|
+
end
|
70
|
+
|
71
|
+
unless first_different_position
|
72
|
+
return Interface::SemanticTokensDelta.new(result_id: result_id, edits: [])
|
73
|
+
end
|
74
|
+
|
75
|
+
# Filter the tokens based on the first different position. This must happen at this stage, before we try to
|
76
|
+
# find the next position from the end or else we risk confusing sets of token that may have different lengths,
|
77
|
+
# but end with the exact same token
|
78
|
+
old_tokens = T.must(previous_tokens[first_different_position...])
|
79
|
+
new_tokens = T.must(current_tokens[first_different_position...])
|
80
|
+
|
81
|
+
# Then search from the end to find the first token that doesn't match. Since the user is normally editing the
|
82
|
+
# middle of the file, this will minimize the number of edits since the end of the token array has not changed
|
83
|
+
first_different_token_from_end = new_tokens.reverse.zip(old_tokens.reverse).find_index do |new, old|
|
84
|
+
new != old
|
85
|
+
end || 0
|
86
|
+
|
87
|
+
# Filter the old and new tokens to only the section that will be replaced/inserted/deleted
|
88
|
+
old_tokens = T.must(old_tokens[...old_tokens.length - first_different_token_from_end])
|
89
|
+
new_tokens = T.must(new_tokens[...new_tokens.length - first_different_token_from_end])
|
90
|
+
|
91
|
+
# And we send back a single edit, replacing an entire section for the new tokens
|
92
|
+
Interface::SemanticTokensDelta.new(
|
93
|
+
result_id: result_id,
|
94
|
+
edits: [{ start: first_different_position, deleteCount: old_tokens.length, data: new_tokens }],
|
39
95
|
)
|
40
96
|
end
|
97
|
+
|
98
|
+
sig { returns(Integer) }
|
99
|
+
def next_result_id
|
100
|
+
@mutex.synchronize do
|
101
|
+
@result_id += 1
|
102
|
+
end
|
103
|
+
end
|
41
104
|
end
|
42
105
|
|
43
|
-
|
44
|
-
|
106
|
+
@result_id = T.let(0, Integer)
|
107
|
+
@mutex = T.let(Mutex.new, Mutex)
|
108
|
+
|
109
|
+
sig do
|
110
|
+
params(
|
111
|
+
global_state: GlobalState,
|
112
|
+
dispatcher: Prism::Dispatcher,
|
113
|
+
document: T.any(RubyDocument, ERBDocument),
|
114
|
+
previous_result_id: T.nilable(String),
|
115
|
+
range: T.nilable(T::Range[Integer]),
|
116
|
+
).void
|
117
|
+
end
|
118
|
+
def initialize(global_state, dispatcher, document, previous_result_id, range: nil)
|
45
119
|
super()
|
120
|
+
|
121
|
+
@document = document
|
122
|
+
@previous_result_id = previous_result_id
|
123
|
+
@range = range
|
124
|
+
@result_id = T.let(SemanticHighlighting.next_result_id.to_s, String)
|
46
125
|
@response_builder = T.let(
|
47
126
|
ResponseBuilders::SemanticHighlighting.new(global_state.encoding),
|
48
127
|
ResponseBuilders::SemanticHighlighting,
|
49
128
|
)
|
50
|
-
Listeners::SemanticHighlighting.new(dispatcher, @response_builder
|
129
|
+
Listeners::SemanticHighlighting.new(dispatcher, @response_builder)
|
51
130
|
|
52
131
|
Addon.addons.each do |addon|
|
53
132
|
addon.create_semantic_highlighting_listener(@response_builder, dispatcher)
|
54
133
|
end
|
55
134
|
end
|
56
135
|
|
57
|
-
sig { override.returns(Interface::SemanticTokens) }
|
136
|
+
sig { override.returns(T.any(Interface::SemanticTokens, Interface::SemanticTokensDelta)) }
|
58
137
|
def perform
|
59
|
-
@
|
138
|
+
previous_tokens = @document.semantic_tokens
|
139
|
+
tokens = @response_builder.response
|
140
|
+
encoded_tokens = ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens)
|
141
|
+
full_response = Interface::SemanticTokens.new(result_id: @result_id, data: encoded_tokens)
|
142
|
+
@document.semantic_tokens = full_response
|
143
|
+
|
144
|
+
if @range
|
145
|
+
tokens_within_range = tokens.select { |token| @range.cover?(token.start_line - 1) }
|
146
|
+
|
147
|
+
return Interface::SemanticTokens.new(
|
148
|
+
result_id: @result_id,
|
149
|
+
data: ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens_within_range),
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Semantic tokens full delta
|
154
|
+
if @previous_result_id
|
155
|
+
response = if previous_tokens.is_a?(Interface::SemanticTokens) &&
|
156
|
+
previous_tokens.result_id == @previous_result_id
|
157
|
+
Requests::SemanticHighlighting.compute_delta(encoded_tokens, previous_tokens.data, @result_id)
|
158
|
+
else
|
159
|
+
full_response
|
160
|
+
end
|
161
|
+
|
162
|
+
return response
|
163
|
+
end
|
164
|
+
|
165
|
+
# Semantic tokens full
|
166
|
+
full_response
|
60
167
|
end
|
61
168
|
end
|
62
169
|
end
|
@@ -37,15 +37,6 @@ module RubyLsp
|
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
|
-
sig { params(node: T.nilable(Prism::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
41
|
-
def visible?(node, range)
|
42
|
-
return true if range.nil?
|
43
|
-
return false if node.nil?
|
44
|
-
|
45
|
-
loc = node.location
|
46
|
-
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
47
|
-
end
|
48
|
-
|
49
40
|
sig do
|
50
41
|
params(
|
51
42
|
node: Prism::Node,
|
@@ -91,9 +91,9 @@ module RubyLsp
|
|
91
91
|
@stack.last
|
92
92
|
end
|
93
93
|
|
94
|
-
sig { override.returns(
|
94
|
+
sig { override.returns(T::Array[SemanticToken]) }
|
95
95
|
def response
|
96
|
-
|
96
|
+
@stack
|
97
97
|
end
|
98
98
|
|
99
99
|
class SemanticToken
|
@@ -162,7 +162,7 @@ module RubyLsp
|
|
162
162
|
sig do
|
163
163
|
params(
|
164
164
|
tokens: T::Array[SemanticToken],
|
165
|
-
).returns(
|
165
|
+
).returns(T::Array[Integer])
|
166
166
|
end
|
167
167
|
def encode(tokens)
|
168
168
|
sorted_tokens = tokens.sort_by.with_index do |token, index|
|
@@ -176,7 +176,7 @@ module RubyLsp
|
|
176
176
|
compute_delta(token)
|
177
177
|
end
|
178
178
|
|
179
|
-
|
179
|
+
delta
|
180
180
|
end
|
181
181
|
|
182
182
|
# The delta array is computed according to the LSP specification:
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -23,6 +23,7 @@ module RubyLsp
|
|
23
23
|
run_initialize(message)
|
24
24
|
when "initialized"
|
25
25
|
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
26
|
+
|
26
27
|
run_initialized
|
27
28
|
when "textDocument/didOpen"
|
28
29
|
text_document_did_open(message)
|
@@ -40,6 +41,8 @@ module RubyLsp
|
|
40
41
|
text_document_code_lens(message)
|
41
42
|
when "textDocument/semanticTokens/full"
|
42
43
|
text_document_semantic_tokens_full(message)
|
44
|
+
when "textDocument/semanticTokens/full/delta"
|
45
|
+
text_document_semantic_tokens_delta(message)
|
43
46
|
when "textDocument/foldingRange"
|
44
47
|
text_document_folding_range(message)
|
45
48
|
when "textDocument/semanticTokens/range"
|
@@ -304,13 +307,26 @@ module RubyLsp
|
|
304
307
|
Document::LanguageId::Ruby
|
305
308
|
end
|
306
309
|
|
307
|
-
@store.set(
|
310
|
+
document = @store.set(
|
308
311
|
uri: text_document[:uri],
|
309
312
|
source: text_document[:text],
|
310
313
|
version: text_document[:version],
|
311
314
|
encoding: @global_state.encoding,
|
312
315
|
language_id: language_id,
|
313
316
|
)
|
317
|
+
|
318
|
+
if document.past_expensive_limit?
|
319
|
+
send_message(
|
320
|
+
Notification.new(
|
321
|
+
method: "window/showMessage",
|
322
|
+
params: Interface::ShowMessageParams.new(
|
323
|
+
type: Constant::MessageType::WARNING,
|
324
|
+
message: "This file is too long. For performance reasons, semantic highlighting and " \
|
325
|
+
"diagnostics will be disabled",
|
326
|
+
),
|
327
|
+
),
|
328
|
+
)
|
329
|
+
end
|
314
330
|
end
|
315
331
|
end
|
316
332
|
|
@@ -378,7 +394,7 @@ module RubyLsp
|
|
378
394
|
|
379
395
|
# If the response has already been cached by another request, return it
|
380
396
|
cached_response = document.cache_get(message[:method])
|
381
|
-
if cached_response
|
397
|
+
if cached_response != Document::EMPTY_CACHE
|
382
398
|
send_message(Result.new(id: message[:id], response: cached_response))
|
383
399
|
return
|
384
400
|
end
|
@@ -391,8 +407,6 @@ module RubyLsp
|
|
391
407
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
392
408
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
393
409
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
394
|
-
|
395
|
-
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
|
396
410
|
dispatcher.dispatch(parse_result.value)
|
397
411
|
|
398
412
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
@@ -401,19 +415,61 @@ module RubyLsp
|
|
401
415
|
document.cache_set("textDocument/documentSymbol", document_symbol.perform)
|
402
416
|
document.cache_set("textDocument/documentLink", document_link.perform)
|
403
417
|
document.cache_set("textDocument/codeLens", code_lens.perform)
|
404
|
-
|
405
|
-
"textDocument/semanticTokens/full",
|
406
|
-
semantic_highlighting.perform,
|
407
|
-
)
|
418
|
+
|
408
419
|
send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
|
409
420
|
end
|
410
421
|
|
411
422
|
alias_method :text_document_document_symbol, :run_combined_requests
|
412
423
|
alias_method :text_document_document_link, :run_combined_requests
|
413
424
|
alias_method :text_document_code_lens, :run_combined_requests
|
414
|
-
alias_method :text_document_semantic_tokens_full, :run_combined_requests
|
415
425
|
alias_method :text_document_folding_range, :run_combined_requests
|
416
426
|
|
427
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
428
|
+
def text_document_semantic_tokens_full(message)
|
429
|
+
document = @store.get(message.dig(:params, :textDocument, :uri))
|
430
|
+
|
431
|
+
if document.past_expensive_limit?
|
432
|
+
send_empty_response(message[:id])
|
433
|
+
return
|
434
|
+
end
|
435
|
+
|
436
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
437
|
+
send_empty_response(message[:id])
|
438
|
+
return
|
439
|
+
end
|
440
|
+
|
441
|
+
dispatcher = Prism::Dispatcher.new
|
442
|
+
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
|
443
|
+
dispatcher.visit(document.parse_result.value)
|
444
|
+
|
445
|
+
send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
|
446
|
+
end
|
447
|
+
|
448
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
449
|
+
def text_document_semantic_tokens_delta(message)
|
450
|
+
document = @store.get(message.dig(:params, :textDocument, :uri))
|
451
|
+
|
452
|
+
if document.past_expensive_limit?
|
453
|
+
send_empty_response(message[:id])
|
454
|
+
return
|
455
|
+
end
|
456
|
+
|
457
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
458
|
+
send_empty_response(message[:id])
|
459
|
+
return
|
460
|
+
end
|
461
|
+
|
462
|
+
dispatcher = Prism::Dispatcher.new
|
463
|
+
request = Requests::SemanticHighlighting.new(
|
464
|
+
@global_state,
|
465
|
+
dispatcher,
|
466
|
+
document,
|
467
|
+
message.dig(:params, :previousResultId),
|
468
|
+
)
|
469
|
+
dispatcher.visit(document.parse_result.value)
|
470
|
+
send_message(Result.new(id: message[:id], response: request.perform))
|
471
|
+
end
|
472
|
+
|
417
473
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
418
474
|
def text_document_semantic_tokens_range(message)
|
419
475
|
params = message[:params]
|
@@ -421,20 +477,26 @@ module RubyLsp
|
|
421
477
|
uri = params.dig(:textDocument, :uri)
|
422
478
|
document = @store.get(uri)
|
423
479
|
|
424
|
-
|
480
|
+
if document.past_expensive_limit?
|
425
481
|
send_empty_response(message[:id])
|
426
482
|
return
|
427
483
|
end
|
428
484
|
|
429
|
-
|
430
|
-
|
485
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
486
|
+
send_empty_response(message[:id])
|
487
|
+
return
|
488
|
+
end
|
431
489
|
|
432
490
|
dispatcher = Prism::Dispatcher.new
|
433
|
-
request = Requests::SemanticHighlighting.new(
|
491
|
+
request = Requests::SemanticHighlighting.new(
|
492
|
+
@global_state,
|
493
|
+
dispatcher,
|
494
|
+
document,
|
495
|
+
nil,
|
496
|
+
range: range.dig(:start, :line)..range.dig(:end, :line),
|
497
|
+
)
|
434
498
|
dispatcher.visit(document.parse_result.value)
|
435
|
-
|
436
|
-
response = request.perform
|
437
|
-
send_message(Result.new(id: message[:id], response: response))
|
499
|
+
send_message(Result.new(id: message[:id], response: request.perform))
|
438
500
|
end
|
439
501
|
|
440
502
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -66,7 +66,7 @@ module RubyLsp
|
|
66
66
|
version: Integer,
|
67
67
|
language_id: Document::LanguageId,
|
68
68
|
encoding: Encoding,
|
69
|
-
).
|
69
|
+
).returns(Document[T.untyped])
|
70
70
|
end
|
71
71
|
def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
|
72
72
|
@state[uri.to_s] = case language_id
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -20,8 +20,8 @@ module RubyLsp
|
|
20
20
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
21
21
|
&block)
|
22
22
|
server = RubyLsp::Server.new(test_mode: true)
|
23
|
-
server.global_state.stubs(:has_type_checker).returns(false) if stub_no_typechecker
|
24
23
|
server.global_state.apply_options({ initializationOptions: { experimentalFeaturesEnabled: true } })
|
24
|
+
server.global_state.instance_variable_set(:@has_type_checker, false) if stub_no_typechecker
|
25
25
|
language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
|
26
26
|
|
27
27
|
if source
|
@@ -36,9 +36,47 @@ module RubyLsp
|
|
36
36
|
def infer_receiver_for_call_node(node, node_context)
|
37
37
|
receiver = node.receiver
|
38
38
|
|
39
|
+
# For receivers inside parenthesis, such as ranges like (0...2), we need to unwrap the parenthesis to get the
|
40
|
+
# actual node
|
41
|
+
if receiver.is_a?(Prism::ParenthesesNode)
|
42
|
+
statements = receiver.body
|
43
|
+
|
44
|
+
if statements.is_a?(Prism::StatementsNode)
|
45
|
+
body = statements.body
|
46
|
+
|
47
|
+
if body.length == 1
|
48
|
+
receiver = body.first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
39
53
|
case receiver
|
40
54
|
when Prism::SelfNode, nil
|
41
55
|
self_receiver_handling(node_context)
|
56
|
+
when Prism::StringNode
|
57
|
+
Type.new("String")
|
58
|
+
when Prism::SymbolNode
|
59
|
+
Type.new("Symbol")
|
60
|
+
when Prism::ArrayNode
|
61
|
+
Type.new("Array")
|
62
|
+
when Prism::HashNode
|
63
|
+
Type.new("Hash")
|
64
|
+
when Prism::IntegerNode
|
65
|
+
Type.new("Integer")
|
66
|
+
when Prism::FloatNode
|
67
|
+
Type.new("Float")
|
68
|
+
when Prism::RegularExpressionNode
|
69
|
+
Type.new("Regexp")
|
70
|
+
when Prism::NilNode
|
71
|
+
Type.new("NilClass")
|
72
|
+
when Prism::TrueNode
|
73
|
+
Type.new("TrueClass")
|
74
|
+
when Prism::FalseNode
|
75
|
+
Type.new("FalseClass")
|
76
|
+
when Prism::RangeNode
|
77
|
+
Type.new("Range")
|
78
|
+
when Prism::LambdaNode
|
79
|
+
Type.new("Proc")
|
42
80
|
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
43
81
|
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
|
44
82
|
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.17.
|
4
|
+
version: 0.17.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|