Class: RubyLsp::Requests::SemanticHighlighting

Inherits:
Listener
  • Object
show all
Extended by:
T::Generic, T::Sig
Defined in:
lib/ruby_lsp/requests/semantic_highlighting.rb

Overview

![Semantic highlighting demo](../../semantic_highlighting.gif)

The [semantic highlighting](microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens) request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.

# Example

“‘ruby def foo

var = 1 # --> semantic highlighting: local variable
some_invocation # --> semantic highlighting: method invocation
var # --> semantic highlighting: local variable

end “‘

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 Listener

#response

Methods included from RubyLsp::Requests::Support::Common

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

Constructor Details

#initialize(dispatcher, range: nil) ⇒ SemanticHighlighting

Returns a new instance of SemanticHighlighting.



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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 111

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.



108
109
110
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 108

def _response
  @_response
end

Instance Method Details

#on_block_local_variable_node_enter(node) ⇒ Object



257
258
259
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 257

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

#on_block_node_enter(node) ⇒ Object



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

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

#on_block_node_leave(node) ⇒ Object



252
253
254
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 252

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

#on_block_parameter_node_enter(node) ⇒ Object



262
263
264
265
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 262

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

#on_call_node_enter(node) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 156

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 = Support::Sorbet.annotation?(node) ? :type : :method
  add_token(T.must(node.message_loc), type)
end

#on_class_node_enter(node) ⇒ Object



385
386
387
388
389
390
391
392
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 385

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



206
207
208
209
210
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 206

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



213
214
215
216
217
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 213

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



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

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



188
189
190
191
192
193
194
195
196
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 188

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



227
228
229
230
231
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 227

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



199
200
201
202
203
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 199

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



234
235
236
237
238
239
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 234

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



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

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

#on_keyword_rest_parameter_node_enter(node) ⇒ Object



286
287
288
289
290
291
292
293
294
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 286

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



351
352
353
354
355
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 351

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



358
359
360
361
362
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 358

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



365
366
367
368
369
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 365

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



338
339
340
341
342
343
344
345
346
347
348
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 338

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



372
373
374
375
376
377
378
379
380
381
382
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 372

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



331
332
333
334
335
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 331

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



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

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



183
184
185
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 183

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

#on_module_node_enter(node) ⇒ Object



395
396
397
398
399
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 395

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



277
278
279
280
281
282
283
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 277

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



297
298
299
300
301
302
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 297

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



268
269
270
271
272
273
274
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 268

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



305
306
307
308
309
310
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 305

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



313
314
315
316
317
318
319
320
321
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 313

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



324
325
326
327
328
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 324

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

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