Class: RubyLsp::Requests::SemanticHighlighting

Inherits:
Request
  • Object
show all
Defined in:
lib/ruby_lsp/requests/semantic_highlighting.rb

Overview

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.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(global_state, dispatcher, document, previous_result_id, range: nil) ⇒ SemanticHighlighting

: (GlobalState global_state, Prism::Dispatcher dispatcher, (RubyDocument | ERBDocument) document, String? previous_result_id, ?range: Range?) -> void



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 78

def initialize(global_state, dispatcher, document, previous_result_id, range: nil)
  super()

  @document = document
  @previous_result_id = previous_result_id
  @range = range
  @result_id = SemanticHighlighting.next_result_id.to_s #: String
  @response_builder = ResponseBuilders::SemanticHighlighting
    .new(document.code_units_cache) #: ResponseBuilders::SemanticHighlighting
  Listeners::SemanticHighlighting.new(dispatcher, @response_builder)

  Addon.addons.each do |addon|
    addon.create_semantic_highlighting_listener(@response_builder, dispatcher)
  end
end

Class Method Details

.compute_delta(current_tokens, previous_tokens, result_id) ⇒ Object

The compute_delta method receives the current semantic tokens and the previous semantic tokens and then tries to compute the smallest possible semantic token edit that will turn previous into current : (Array current_tokens, Array previous_tokens, String result_id) -> Interface::SemanticTokensDelta



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 29

def compute_delta(current_tokens, previous_tokens, result_id)
  # Find the index of the first token that is different between the two sets of tokens
  first_different_position = current_tokens.zip(previous_tokens).find_index { |new, old| new != old }

  # When deleting a token from the end, the first_different_position will be nil, but since we're removing at
  # the end, then we have to initialize it to the length of the current tokens after the deletion
  if !first_different_position && current_tokens.length < previous_tokens.length
    first_different_position = current_tokens.length
  end

  unless first_different_position
    return Interface::SemanticTokensDelta.new(result_id: result_id, edits: [])
  end

  # Filter the tokens based on the first different position. This must happen at this stage, before we try to
  # find the next position from the end or else we risk confusing sets of token that may have different lengths,
  # but end with the exact same token
  old_tokens = previous_tokens[first_different_position...] #: as !nil
  new_tokens = current_tokens[first_different_position...] #: as !nil

  # Then search from the end to find the first token that doesn't match. Since the user is normally editing the
  # middle of the file, this will minimize the number of edits since the end of the token array has not changed
  first_different_token_from_end = new_tokens.reverse.zip(old_tokens.reverse).find_index do |new, old|
    new != old
  end || 0

  # Filter the old and new tokens to only the section that will be replaced/inserted/deleted
  old_tokens = old_tokens[...old_tokens.length - first_different_token_from_end] #: as !nil
  new_tokens = new_tokens[...new_tokens.length - first_different_token_from_end] #: as !nil

  # And we send back a single edit, replacing an entire section for the new tokens
  Interface::SemanticTokensDelta.new(
    result_id: result_id,
    edits: [{ start: first_different_position, deleteCount: old_tokens.length, data: new_tokens }],
  )
end

.next_result_idObject

: -> Integer



67
68
69
70
71
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 67

def next_result_id
  @mutex.synchronize do
    @result_id += 1
  end
end

.providerObject

: -> Interface::SemanticTokensRegistrationOptions



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 14

def provider
  Interface::SemanticTokensRegistrationOptions.new(
    document_selector: nil,
    legend: Interface::SemanticTokensLegend.new(
      token_types: ResponseBuilders::SemanticHighlighting::TOKEN_TYPES.keys,
      token_modifiers: ResponseBuilders::SemanticHighlighting::TOKEN_MODIFIERS.keys,
    ),
    range: true,
    full: { delta: true },
  )
end

Instance Method Details

#performObject

: -> (Interface::SemanticTokens | Interface::SemanticTokensDelta)



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
# File 'lib/ruby_lsp/requests/semantic_highlighting.rb', line 96

def perform
  previous_tokens = @document.semantic_tokens
  tokens = @response_builder.response
  encoded_tokens = ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens)
  full_response = Interface::SemanticTokens.new(result_id: @result_id, data: encoded_tokens)
  @document.semantic_tokens = full_response

  if @range
    tokens_within_range = tokens.select { |token| @range.cover?(token.start_line - 1) }

    return Interface::SemanticTokens.new(
      result_id: @result_id,
      data: ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens_within_range),
    )
  end

  # Semantic tokens full delta
  if @previous_result_id
    response = if previous_tokens.is_a?(Interface::SemanticTokens) &&
        previous_tokens.result_id == @previous_result_id
      Requests::SemanticHighlighting.compute_delta(encoded_tokens, previous_tokens.data, @result_id)
    else
      full_response
    end

    return response
  end

  # Semantic tokens full
  full_response
end