ruby-lsp 0.17.15 → 0.17.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d715172510bba67801670dbfc574eed9851b9852aaea3f6e210acd74734af4
4
- data.tar.gz: b35b3a55c3d65987390e4b125a090ff8f6da902bfa7ab9c08eb9eb8c72d5157f
3
+ metadata.gz: fc4416ea8876d8207b6c1955cd47af5f3312a0f0dc3644563bfd35138a903535
4
+ data.tar.gz: 5961dd8ca49539c20049509c0afed8b2442c3d16ec12d1fb84c9e45dbd027760
5
5
  SHA512:
6
- metadata.gz: ef8a2ca24755eef8c5ed1146f1f6c40ec725988e9dd6a51efb326583e5606a221f733625f08d4dad5bb6091675c39fb18a485ff8c0bed1dc0b2e221a4390c585
7
- data.tar.gz: 16101462585105a9a201f6341908f0b238f77f4acaa6dac723c95e35fcddc7c7b68d937bead5beecc14ae6bcac276b2f813523304f7eaded3132d3aa07eb943b
6
+ metadata.gz: 4b77547221c1750871ff6e1a3635b0ae6f945b86862f484d20eb7587a1feb9830e1d1d7770f16c2e7efeb70e6a953529041f8acc8fe3d7c30940687d20563f96
7
+ data.tar.gz: 1093a1a6ac45e8627f26a6c9dbb0017079d2804904fe9a4303e3698d4131c6eeedf2bbafac93e6475f676860624b9c53d482c52cfe8380e67ed05ce525e99957
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.15
1
+ 0.17.16
@@ -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({}, T::Hash[String, T.untyped])
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, range: nil)
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
- type = Requests::Support::Sorbet.annotation?(node) ? :type : :method
89
- @response_builder.add_token(T.must(node.message_loc), type)
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
- return diagnostics if @document.syntax_error? || @active_linters.empty?
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: false },
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
- sig { params(global_state: GlobalState, dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
44
- def initialize(global_state, dispatcher, range: nil)
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, range: range)
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
- @response_builder.response
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(Interface::SemanticTokens) }
94
+ sig { override.returns(T::Array[SemanticToken]) }
95
95
  def response
96
- SemanticTokenEncoder.new.encode(@stack)
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(Interface::SemanticTokens)
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
- Interface::SemanticTokens.new(data: delta)
179
+ delta
180
180
  end
181
181
 
182
182
  # The delta array is computed according to the LSP specification:
@@ -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
- document.cache_set(
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
- unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
480
+ if document.past_expensive_limit?
425
481
  send_empty_response(message[:id])
426
482
  return
427
483
  end
428
484
 
429
- start_line = range.dig(:start, :line)
430
- end_line = range.dig(:end, :line)
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(@global_state, dispatcher, range: start_line..end_line)
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 }
@@ -66,7 +66,7 @@ module RubyLsp
66
66
  version: Integer,
67
67
  language_id: Document::LanguageId,
68
68
  encoding: Encoding,
69
- ).void
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
@@ -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.15
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-20 00:00:00.000000000 Z
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