Class: RubyLsp::Requests::SemanticHighlighting

Inherits:
BaseRequest
  • Object
show all
Extended by:
T::Sig
Includes:
SyntaxTree::WithScope
Defined in:
lib/ruby_lsp/requests/semantic_highlighting.rb

Overview

![Semantic highlighting demo](../../misc/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

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 Method Summary collapse

Methods inherited from BaseRequest

#full_constant_name, #locate, #range_from_syntax_tree_node, #visible?

Constructor Details

#initialize(document, range: nil, encoder: nil) ⇒ SemanticHighlighting

Returns a new instance of SemanticHighlighting.



112
113
114
115
116
117
118
119
120
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 112

def initialize(document, range: nil, encoder: nil)
  super(document)

  @encoder = encoder
  @tokens = T.let([], T::Array[SemanticToken])
  @tree = T.let(T.must(document.tree), SyntaxTree::Node)
  @range = range
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
end

Instance Method Details

#add_token(location, type, modifiers = []) ⇒ Object



291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 291

def add_token(location, type, modifiers = [])
  length = location.end_char - location.start_char
  modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
  @tokens.push(
    SemanticToken.new(
      location: location,
      length: length,
      type: T.must(TOKEN_TYPES[type]),
      modifier: modifiers_indices,
    ),
  )
end

#runObject



130
131
132
133
134
135
136
137
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 130

def run
  return @tokens unless @document.parsed?

  visit(@tree)
  return @tokens unless @encoder

  @encoder.encode(@tokens)
end

#visit_call(node) ⇒ Object



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

def visit_call(node)
  return super unless visible?(node, @range)

  visit(node.receiver)

  message = node.message
  if message != :call && !special_method?(message.value)
    type = Support::Sorbet.annotation?(node) ? :type : :method

    add_token(message.location, type)
  end

  visit(node.arguments)
end

#visit_class(node) ⇒ Object



274
275
276
277
278
279
280
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 274

def visit_class(node)
  return super unless visible?(node, @range)

  add_token(node.constant.location, :class, [:declaration])
  add_token(node.superclass.location, :class) if node.superclass
  visit(node.bodystmt)
end

#visit_command(node) ⇒ Object



156
157
158
159
160
161
162
163
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 156

def visit_command(node)
  return super unless visible?(node, @range)

  unless special_method?(node.message.value)
    add_token(node.message.location, :method)
  end
  visit(node.arguments)
end

#visit_command_call(node) ⇒ Object



166
167
168
169
170
171
172
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 166

def visit_command_call(node)
  return super unless visible?(node, @range)

  visit(node.receiver)
  add_token(node.message.location, :method)
  visit(node.arguments)
end

#visit_const(node) ⇒ Object



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

def visit_const(node)
  return super unless visible?(node, @range)

  add_token(node.location, :namespace)
end

#visit_def(node) ⇒ Object



182
183
184
185
186
187
188
189
190
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 182

def visit_def(node)
  return super unless visible?(node, @range)

  add_token(node.name.location, :method, [:declaration])
  visit(node.params)
  visit(node.bodystmt)
  visit(node.target) if node.target
  visit(node.operator) if node.operator
end

#visit_field(node) ⇒ Object



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

def visit_field(node)
  return super unless visible?(node, @range)

  add_token(node.name.location, :method)

  super
end

#visit_kw(node) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 193

def visit_kw(node)
  return super unless visible?(node, @range)

  case node.value
  when "self"
    add_token(node.location, :variable, [:default_library])
  end
end

#visit_module(node) ⇒ Object



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

def visit_module(node)
  return super unless visible?(node, @range)

  add_token(node.constant.location, :class, [:declaration])
  visit(node.bodystmt)
end

#visit_params(node) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 203

def visit_params(node)
  return super unless visible?(node, @range)

  node.keywords.each do |keyword,|
    location = keyword.location
    add_token(location_without_colon(location), :parameter)
  end

  node.requireds.each do |required|
    add_token(required.location, :parameter)
  end

  rest = node.keyword_rest
  if rest && !rest.is_a?(SyntaxTree::ArgsForward)
    name = rest.name
    add_token(name.location, :parameter) if name
  end

  super
end

#visit_var_field(node) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 234

def visit_var_field(node)
  return super unless visible?(node, @range)

  value = node.value

  case value
  when SyntaxTree::Ident
    type = type_for_local(value)
    add_token(value.location, type)
  else
    visit(value)
  end
end

#visit_var_ref(node) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 249

def visit_var_ref(node)
  return super unless visible?(node, @range)

  value = node.value

  case value
  when SyntaxTree::Ident
    type = type_for_local(value)
    add_token(value.location, type)
  else
    visit(value)
  end
end

#visit_vcall(node) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 264

def visit_vcall(node)
  return super unless visible?(node, @range)

  return if special_method?(node.value.value)

  type = Support::Sorbet.annotation?(node) ? :type : :method
  add_token(node.value.location, type)
end