Class: RubyLsp::Listeners::SemanticHighlighting

Inherits:
RubyLsp::Listener show all
Extended by:
T::Generic, T::Sig
Defined in:
lib/ruby_lsp/listeners/semantic_highlighting.rb

Defined Under Namespace

Classes: SemanticToken

Constant Summary collapse

ResponseType =
type_member { { fixed: T::Array[SemanticToken] } }
TOKEN_TYPES =
T.let(
  {
    namespace: 0,
    type: 1,
    class: 2,
    enum: 3,
    interface: 4,
    struct: 5,
    typeParameter: 6,
    parameter: 7,
    variable: 8,
    property: 9,
    enumMember: 10,
    event: 11,
    function: 12,
    method: 13,
    macro: 14,
    keyword: 15,
    modifier: 16,
    comment: 17,
    string: 18,
    number: 19,
    regexp: 20,
    operator: 21,
    decorator: 22,
  }.freeze,
  T::Hash[Symbol, Integer],
)
TOKEN_MODIFIERS =
T.let(
  {
    declaration: 0,
    definition: 1,
    readonly: 2,
    static: 3,
    deprecated: 4,
    abstract: 5,
    async: 6,
    modification: 7,
    documentation: 8,
    default_library: 9,
  }.freeze,
  T::Hash[Symbol, Integer],
)
SPECIAL_RUBY_METHODS =
T.let(
  [
    Module.instance_methods(false),
    Kernel.instance_methods(false),
    Kernel.methods(false),
    Bundler::Dsl.instance_methods(false),
    Module.private_instance_methods(false),
  ].flatten.map(&:to_s),
  T::Array[String],
)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from RubyLsp::Listener

#response

Methods included from Requests::Support::Common

#create_code_lens, #markdown_from_index_entries, #not_in_dependencies?, #range_from_location, #range_from_node, #self_receiver?, #visible?

Constructor Details

#initialize(dispatcher, range: nil) ⇒ SemanticHighlighting

Returns a new instance of SemanticHighlighting.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 96

def initialize(dispatcher, range: nil)
  super(dispatcher)

  @_response = T.let([], ResponseType)
  @range = range
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
  @current_scope = T.let(ParameterScope.new, ParameterScope)
  @inside_regex_capture = T.let(false, T::Boolean)

  dispatcher.register(
    self,
    :on_call_node_enter,
    :on_class_node_enter,
    :on_def_node_enter,
    :on_def_node_leave,
    :on_block_node_enter,
    :on_block_node_leave,
    :on_self_node_enter,
    :on_module_node_enter,
    :on_local_variable_write_node_enter,
    :on_local_variable_read_node_enter,
    :on_block_parameter_node_enter,
    :on_required_keyword_parameter_node_enter,
    :on_optional_keyword_parameter_node_enter,
    :on_keyword_rest_parameter_node_enter,
    :on_optional_parameter_node_enter,
    :on_required_parameter_node_enter,
    :on_rest_parameter_node_enter,
    :on_constant_read_node_enter,
    :on_constant_write_node_enter,
    :on_constant_and_write_node_enter,
    :on_constant_operator_write_node_enter,
    :on_constant_or_write_node_enter,
    :on_constant_target_node_enter,
    :on_local_variable_and_write_node_enter,
    :on_local_variable_operator_write_node_enter,
    :on_local_variable_or_write_node_enter,
    :on_local_variable_target_node_enter,
    :on_block_local_variable_node_enter,
    :on_match_write_node_enter,
    :on_match_write_node_leave,
  )
end

Instance Attribute Details

#_responseObject (readonly)

Returns the value of attribute _response.



93
94
95
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 93

def _response
  @_response
end

Instance Method Details

#on_block_local_variable_node_enter(node) ⇒ Object



242
243
244
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 242

def on_block_local_variable_node_enter(node)
  add_token(node.location, :variable)
end

#on_block_node_enter(node) ⇒ Object



232
233
234
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 232

def on_block_node_enter(node)
  @current_scope = ParameterScope.new(@current_scope)
end

#on_block_node_leave(node) ⇒ Object



237
238
239
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 237

def on_block_node_leave(node)
  @current_scope = T.must(@current_scope.parent)
end

#on_block_parameter_node_enter(node) ⇒ Object



247
248
249
250
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 247

def on_block_parameter_node_enter(node)
  name = node.name
  @current_scope << name.to_sym if name
end

#on_call_node_enter(node) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 141

def on_call_node_enter(node)
  return unless visible?(node, @range)

  message = node.message
  return unless message

  # We can't push a semantic token for [] and []= because the argument inside the brackets is a part of
  # the message_loc
  return if message.start_with?("[") && (message.end_with?("]") || message.end_with?("]="))
  return if message == "=~"
  return if special_method?(message)

  type = Requests::Support::Sorbet.annotation?(node) ? :type : :method
  add_token(T.must(node.message_loc), type)
end

#on_class_node_enter(node) ⇒ Object



370
371
372
373
374
375
376
377
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 370

def on_class_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.constant_path.location, :class, [:declaration])

  superclass = node.superclass
  add_token(superclass.location, :class) if superclass
end

#on_constant_and_write_node_enter(node) ⇒ Object



191
192
193
194
195
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 191

def on_constant_and_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, :namespace)
end

#on_constant_operator_write_node_enter(node) ⇒ Object



198
199
200
201
202
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 198

def on_constant_operator_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, :namespace)
end

#on_constant_or_write_node_enter(node) ⇒ Object



205
206
207
208
209
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 205

def on_constant_or_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, :namespace)
end

#on_constant_read_node_enter(node) ⇒ Object



173
174
175
176
177
178
179
180
181
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 173

def on_constant_read_node_enter(node)
  return unless visible?(node, @range)
  # When finding a module or class definition, we will have already pushed a token related to this constant. We
  # need to look at the previous two tokens and if they match this locatione exactly, avoid pushing another token
  # on top of the previous one
  return if @_response.last(2).any? { |token| token.location == node.location }

  add_token(node.location, :namespace)
end

#on_constant_target_node_enter(node) ⇒ Object



212
213
214
215
216
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 212

def on_constant_target_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.location, :namespace)
end

#on_constant_write_node_enter(node) ⇒ Object



184
185
186
187
188
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 184

def on_constant_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, :namespace)
end

#on_def_node_enter(node) ⇒ Object



219
220
221
222
223
224
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 219

def on_def_node_enter(node)
  @current_scope = ParameterScope.new(@current_scope)
  return unless visible?(node, @range)

  add_token(node.name_loc, :method, [:declaration])
end

#on_def_node_leave(node) ⇒ Object



227
228
229
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 227

def on_def_node_leave(node)
  @current_scope = T.must(@current_scope.parent)
end

#on_keyword_rest_parameter_node_enter(node) ⇒ Object



271
272
273
274
275
276
277
278
279
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 271

def on_keyword_rest_parameter_node_enter(node)
  name = node.name

  if name
    @current_scope << name.to_sym

    add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
  end
end

#on_local_variable_and_write_node_enter(node) ⇒ Object



336
337
338
339
340
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 336

def on_local_variable_and_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_operator_write_node_enter(node) ⇒ Object



343
344
345
346
347
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 343

def on_local_variable_operator_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_or_write_node_enter(node) ⇒ Object



350
351
352
353
354
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 350

def on_local_variable_or_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_local_variable_read_node_enter(node) ⇒ Object



323
324
325
326
327
328
329
330
331
332
333
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 323

def on_local_variable_read_node_enter(node)
  return unless visible?(node, @range)

  # Numbered parameters
  if /_\d+/.match?(node.name)
    add_token(node.location, :parameter)
    return
  end

  add_token(node.location, @current_scope.type_for(node.name))
end

#on_local_variable_target_node_enter(node) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 357

def on_local_variable_target_node_enter(node)
  # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
  # Unfortunately, if the regex contains a backslash, the location will be incorrect and we'll end up highlighting
  # the entire regex as a local variable. We process these captures in process_regexp_locals instead and then
  # prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
  return if @inside_regex_capture

  return unless visible?(node, @range)

  add_token(node.location, @current_scope.type_for(node.name))
end

#on_local_variable_write_node_enter(node) ⇒ Object



316
317
318
319
320
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 316

def on_local_variable_write_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.name_loc, @current_scope.type_for(node.name))
end

#on_match_write_node_enter(node) ⇒ Object



158
159
160
161
162
163
164
165
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 158

def on_match_write_node_enter(node)
  call = node.call

  if call.message == "=~"
    @inside_regex_capture = true
    process_regexp_locals(call)
  end
end

#on_match_write_node_leave(node) ⇒ Object



168
169
170
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 168

def on_match_write_node_leave(node)
  @inside_regex_capture = true if node.call.message == "=~"
end

#on_module_node_enter(node) ⇒ Object



380
381
382
383
384
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 380

def on_module_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.constant_path.location, :namespace, [:declaration])
end

#on_optional_keyword_parameter_node_enter(node) ⇒ Object



262
263
264
265
266
267
268
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 262

def on_optional_keyword_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  location = node.name_loc
  add_token(location.copy(length: location.length - 1), :parameter)
end

#on_optional_parameter_node_enter(node) ⇒ Object



282
283
284
285
286
287
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 282

def on_optional_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  add_token(node.name_loc, :parameter)
end

#on_required_keyword_parameter_node_enter(node) ⇒ Object



253
254
255
256
257
258
259
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 253

def on_required_keyword_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  location = node.name_loc
  add_token(location.copy(length: location.length - 1), :parameter)
end

#on_required_parameter_node_enter(node) ⇒ Object



290
291
292
293
294
295
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 290

def on_required_parameter_node_enter(node)
  @current_scope << node.name
  return unless visible?(node, @range)

  add_token(node.location, :parameter)
end

#on_rest_parameter_node_enter(node) ⇒ Object



298
299
300
301
302
303
304
305
306
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 298

def on_rest_parameter_node_enter(node)
  name = node.name

  if name
    @current_scope << name.to_sym

    add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
  end
end

#on_self_node_enter(node) ⇒ Object



309
310
311
312
313
# File 'lib/ruby_lsp/listeners/semantic_highlighting.rb', line 309

def on_self_node_enter(node)
  return unless visible?(node, @range)

  add_token(node.location, :variable, [:default_library])
end