ruby-lsp 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/VERSION +1 -1
  4. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +4 -8
  5. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +5 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -2
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -3
  8. data/lib/ruby_indexer/test/classes_and_modules_test.rb +9 -0
  9. data/lib/ruby_indexer/test/index_test.rb +27 -0
  10. data/lib/ruby_lsp/addon.rb +21 -10
  11. data/lib/ruby_lsp/check_docs.rb +8 -8
  12. data/lib/ruby_lsp/executor.rb +28 -10
  13. data/lib/ruby_lsp/internal.rb +1 -1
  14. data/lib/ruby_lsp/listeners/code_lens.rb +54 -55
  15. data/lib/ruby_lsp/listeners/completion.rb +17 -16
  16. data/lib/ruby_lsp/listeners/definition.rb +10 -16
  17. data/lib/ruby_lsp/listeners/document_highlight.rb +6 -11
  18. data/lib/ruby_lsp/listeners/document_link.rb +6 -12
  19. data/lib/ruby_lsp/listeners/document_symbol.rb +95 -55
  20. data/lib/ruby_lsp/listeners/folding_ranges.rb +19 -23
  21. data/lib/ruby_lsp/listeners/hover.rb +26 -30
  22. data/lib/ruby_lsp/listeners/inlay_hints.rb +7 -13
  23. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -124
  24. data/lib/ruby_lsp/listeners/signature_help.rb +11 -13
  25. data/lib/ruby_lsp/requests/code_lens.rb +9 -17
  26. data/lib/ruby_lsp/requests/completion.rb +7 -9
  27. data/lib/ruby_lsp/requests/definition.rb +10 -22
  28. data/lib/ruby_lsp/requests/document_highlight.rb +7 -5
  29. data/lib/ruby_lsp/requests/document_link.rb +7 -6
  30. data/lib/ruby_lsp/requests/document_symbol.rb +5 -11
  31. data/lib/ruby_lsp/requests/folding_ranges.rb +11 -6
  32. data/lib/ruby_lsp/requests/hover.rb +18 -24
  33. data/lib/ruby_lsp/requests/inlay_hints.rb +7 -8
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +12 -2
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +10 -8
  36. data/lib/ruby_lsp/requests/signature_help.rb +53 -18
  37. data/lib/ruby_lsp/requests/support/common.rb +23 -10
  38. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -1
  39. data/lib/ruby_lsp/requests.rb +0 -1
  40. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +29 -0
  41. data/lib/ruby_lsp/response_builders/document_symbol.rb +57 -0
  42. data/lib/ruby_lsp/response_builders/hover.rb +49 -0
  43. data/lib/ruby_lsp/response_builders/response_builder.rb +16 -0
  44. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +199 -0
  45. data/lib/ruby_lsp/response_builders/signature_help.rb +28 -0
  46. data/lib/ruby_lsp/response_builders.rb +13 -0
  47. data/lib/ruby_lsp/server.rb +3 -3
  48. data/lib/ruby_lsp/setup_bundler.rb +30 -5
  49. data/lib/ruby_lsp/store.rb +4 -4
  50. metadata +14 -9
  51. data/lib/ruby_lsp/listener.rb +0 -33
  52. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +0 -73
@@ -0,0 +1,199 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class SemanticHighlighting < ResponseBuilder
7
+ class UndefinedTokenType < StandardError; end
8
+
9
+ TOKEN_TYPES = T.let(
10
+ {
11
+ namespace: 0,
12
+ type: 1,
13
+ class: 2,
14
+ enum: 3,
15
+ interface: 4,
16
+ struct: 5,
17
+ typeParameter: 6,
18
+ parameter: 7,
19
+ variable: 8,
20
+ property: 9,
21
+ enumMember: 10,
22
+ event: 11,
23
+ function: 12,
24
+ method: 13,
25
+ macro: 14,
26
+ keyword: 15,
27
+ modifier: 16,
28
+ comment: 17,
29
+ string: 18,
30
+ number: 19,
31
+ regexp: 20,
32
+ operator: 21,
33
+ decorator: 22,
34
+ }.freeze,
35
+ T::Hash[Symbol, Integer],
36
+ )
37
+
38
+ TOKEN_MODIFIERS = T.let(
39
+ {
40
+ declaration: 0,
41
+ definition: 1,
42
+ readonly: 2,
43
+ static: 3,
44
+ deprecated: 4,
45
+ abstract: 5,
46
+ async: 6,
47
+ modification: 7,
48
+ documentation: 8,
49
+ default_library: 9,
50
+ }.freeze,
51
+ T::Hash[Symbol, Integer],
52
+ )
53
+
54
+ extend T::Sig
55
+
56
+ ResponseType = type_member { { fixed: Interface::SemanticTokens } }
57
+
58
+ sig { void }
59
+ def initialize
60
+ super
61
+ @stack = T.let([], T::Array[SemanticToken])
62
+ end
63
+
64
+ sig { params(location: Prism::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
65
+ def add_token(location, type, modifiers = [])
66
+ length = location.end_offset - location.start_offset
67
+ modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
68
+ @stack.push(
69
+ SemanticToken.new(
70
+ location: location,
71
+ length: length,
72
+ type: T.must(TOKEN_TYPES[type]),
73
+ modifier: modifiers_indices,
74
+ ),
75
+ )
76
+ end
77
+
78
+ sig { returns(T.nilable(SemanticToken)) }
79
+ def last
80
+ @stack.last
81
+ end
82
+
83
+ sig { override.returns(Interface::SemanticTokens) }
84
+ def response
85
+ SemanticTokenEncoder.new.encode(@stack)
86
+ end
87
+
88
+ class SemanticToken
89
+ extend T::Sig
90
+
91
+ sig { returns(Prism::Location) }
92
+ attr_reader :location
93
+
94
+ sig { returns(Integer) }
95
+ attr_reader :length
96
+
97
+ sig { returns(Integer) }
98
+ attr_reader :type
99
+
100
+ sig { returns(T::Array[Integer]) }
101
+ attr_reader :modifier
102
+
103
+ sig { params(location: Prism::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void }
104
+ def initialize(location:, length:, type:, modifier:)
105
+ @location = location
106
+ @length = length
107
+ @type = type
108
+ @modifier = modifier
109
+ end
110
+
111
+ sig { params(type_symbol: Symbol).void }
112
+ def replace_type(type_symbol)
113
+ type_int = TOKEN_TYPES[type_symbol]
114
+ raise UndefinedTokenType, "Undefined token type: #{type_symbol}" unless type_int
115
+
116
+ @type = type_int
117
+ end
118
+
119
+ sig { params(modifier_symbols: T::Array[Symbol]).void }
120
+ def replace_modifier(modifier_symbols)
121
+ @modifier = modifier_symbols.filter_map do |modifier_symbol|
122
+ modifier_index = TOKEN_MODIFIERS[modifier_symbol]
123
+ raise UndefinedTokenType, "Undefined token modifier: #{modifier_symbol}" unless modifier_index
124
+
125
+ modifier_index
126
+ end
127
+ end
128
+ end
129
+
130
+ class SemanticTokenEncoder
131
+ extend T::Sig
132
+
133
+ sig { void }
134
+ def initialize
135
+ @current_row = T.let(0, Integer)
136
+ @current_column = T.let(0, Integer)
137
+ end
138
+
139
+ sig do
140
+ params(
141
+ tokens: T::Array[SemanticToken],
142
+ ).returns(Interface::SemanticTokens)
143
+ end
144
+ def encode(tokens)
145
+ sorted_tokens = tokens.sort_by.with_index do |token, index|
146
+ # Enumerable#sort_by is not deterministic when the compared values are equal.
147
+ # When that happens, we need to use the index as a tie breaker to ensure
148
+ # that the order of the tokens is always the same.
149
+ [token.location.start_line, token.location.start_column, index]
150
+ end
151
+
152
+ delta = sorted_tokens.flat_map do |token|
153
+ compute_delta(token)
154
+ end
155
+
156
+ Interface::SemanticTokens.new(data: delta)
157
+ end
158
+
159
+ # The delta array is computed according to the LSP specification:
160
+ # > The protocol for the token format relative uses relative
161
+ # > positions, because most tokens remain stable relative to
162
+ # > each other when edits are made in a file. This simplifies
163
+ # > the computation of a delta if a server supports it. So each
164
+ # > token is represented using 5 integers.
165
+
166
+ # For more information on how each number is calculated, read:
167
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
168
+ sig { params(token: SemanticToken).returns(T::Array[Integer]) }
169
+ def compute_delta(token)
170
+ row = token.location.start_line - 1
171
+ column = token.location.start_column
172
+
173
+ begin
174
+ delta_line = row - @current_row
175
+
176
+ delta_column = column
177
+ delta_column -= @current_column if delta_line == 0
178
+
179
+ [delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
180
+ ensure
181
+ @current_row = row
182
+ @current_column = column
183
+ end
184
+ end
185
+
186
+ # Encode an array of modifiers to positions onto a bit flag
187
+ # For example, [:default_library] will be encoded as
188
+ # 0b1000000000, as :default_library is the 10th bit according
189
+ # to the token modifiers index map.
190
+ sig { params(modifiers: T::Array[Integer]).returns(Integer) }
191
+ def encode_modifiers(modifiers)
192
+ modifiers.inject(0) do |encoded_modifiers, modifier|
193
+ encoded_modifiers | (1 << modifier)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class SignatureHelp < ResponseBuilder
7
+ ResponseType = type_member { { fixed: T.nilable(Interface::SignatureHelp) } }
8
+
9
+ extend T::Sig
10
+
11
+ sig { void }
12
+ def initialize
13
+ super
14
+ @signature_help = T.let(nil, ResponseType)
15
+ end
16
+
17
+ sig { params(signature_help: ResponseType).void }
18
+ def replace(signature_help)
19
+ @signature_help = signature_help
20
+ end
21
+
22
+ sig { override.returns(ResponseType) }
23
+ def response
24
+ @signature_help
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ autoload :CollectionResponseBuilder, "ruby_lsp/response_builders/collection_response_builder"
7
+ autoload :DocumentSymbol, "ruby_lsp/response_builders/document_symbol"
8
+ autoload :Hover, "ruby_lsp/response_builders/hover"
9
+ autoload :ResponseBuilder, "ruby_lsp/response_builders/response_builder"
10
+ autoload :SemanticHighlighting, "ruby_lsp/response_builders/semantic_highlighting"
11
+ autoload :SignatureHelp, "ruby_lsp/response_builders/signature_help"
12
+ end
13
+ end
@@ -58,7 +58,7 @@ module RubyLsp
58
58
 
59
59
  sig { void }
60
60
  def start
61
- warn("Starting Ruby LSP v#{VERSION}...")
61
+ $stderr.puts("Starting Ruby LSP v#{VERSION}...")
62
62
 
63
63
  # Requests that have to be executed sequentially or in the main process are implemented here. All other requests
64
64
  # fall under the else branch which just pushes requests to the queue
@@ -73,7 +73,7 @@ module RubyLsp
73
73
  when "$/setTrace"
74
74
  VOID
75
75
  when "shutdown"
76
- warn("Shutting down Ruby LSP...")
76
+ $stderr.puts("Shutting down Ruby LSP...")
77
77
 
78
78
  @message_queue.close
79
79
  # Close the queue so that we can no longer receive items
@@ -92,7 +92,7 @@ module RubyLsp
92
92
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
93
93
  # https://microsoft.github.io/language-server-protocol/specification/#exit
94
94
  status = @store.empty? ? 0 : 1
95
- warn("Shutdown complete with status #{status}")
95
+ $stderr.puts("Shutdown complete with status #{status}")
96
96
  exit(status)
97
97
  else
98
98
  # Default case: push the request to the queue to be executed by the worker
@@ -59,7 +59,9 @@ module RubyLsp
59
59
 
60
60
  # Do not setup a custom bundle if both `ruby-lsp` and `debug` are already in the Gemfile
61
61
  if @dependencies["ruby-lsp"] && @dependencies["debug"]
62
- warn("Ruby LSP> Skipping custom bundle setup since both `ruby-lsp` and `debug` are already in #{@gemfile}")
62
+ $stderr.puts(
63
+ "Ruby LSP> Skipping custom bundle setup since both `ruby-lsp` and `debug` are already in #{@gemfile}",
64
+ )
63
65
 
64
66
  # If the user decided to add the `ruby-lsp` and `debug` to their Gemfile after having already run the Ruby LSP,
65
67
  # then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle install` for the top level and
@@ -76,7 +78,7 @@ module RubyLsp
76
78
  write_custom_gemfile
77
79
 
78
80
  unless @gemfile&.exist? && @lockfile&.exist?
79
- warn("Ruby LSP> Skipping lockfile copies because there's no top level bundle")
81
+ $stderr.puts("Ruby LSP> Skipping lockfile copies because there's no top level bundle")
80
82
  return run_bundle_install(@custom_gemfile)
81
83
  end
82
84
 
@@ -84,11 +86,14 @@ module RubyLsp
84
86
  current_lockfile_hash = Digest::SHA256.hexdigest(lockfile_contents)
85
87
 
86
88
  if @custom_lockfile.exist? && @lockfile_hash_path.exist? && @lockfile_hash_path.read == current_lockfile_hash
87
- warn("Ruby LSP> Skipping custom bundle setup since #{@custom_lockfile} already exists and is up to date")
89
+ $stderr.puts(
90
+ "Ruby LSP> Skipping custom bundle setup since #{@custom_lockfile} already exists and is up to date",
91
+ )
88
92
  return run_bundle_install(@custom_gemfile)
89
93
  end
90
94
 
91
95
  FileUtils.cp(@lockfile.to_s, @custom_lockfile.to_s)
96
+ correct_relative_remote_paths
92
97
  @lockfile_hash_path.write(current_lockfile_hash)
93
98
  run_bundle_install(@custom_gemfile)
94
99
  end
@@ -208,8 +213,8 @@ module RubyLsp
208
213
  command << "1>&2"
209
214
 
210
215
  # Add bundle update
211
- warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
212
- warn("Ruby LSP> Command: #{command}")
216
+ $stderr.puts("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
217
+ $stderr.puts("Ruby LSP> Command: #{command}")
213
218
  system(env, command)
214
219
  [bundle_gemfile.to_s, expanded_path, env["BUNDLE_APP_CONFIG"]]
215
220
  end
@@ -227,5 +232,25 @@ module RubyLsp
227
232
  # If the last updated file doesn't exist or was updated more than 4 hours ago, we should update
228
233
  !@last_updated_path.exist? || Time.parse(@last_updated_path.read) < (Time.now - FOUR_HOURS)
229
234
  end
235
+
236
+ # When a lockfile has remote references based on relative file paths, we need to ensure that they are pointing to
237
+ # the correct place since after copying the relative path is no longer valid
238
+ sig { void }
239
+ def correct_relative_remote_paths
240
+ content = @custom_lockfile.read
241
+ content.gsub!(/remote: (.*)/) do |match|
242
+ path = T.must(Regexp.last_match)[1]
243
+
244
+ # We should only apply the correction if the remote is a relative path. It might also be a URI, like
245
+ # `https://rubygems.org` or an absolute path, in which case we shouldn't do anything
246
+ if path&.start_with?(".")
247
+ "remote: #{File.expand_path(path, T.must(@gemfile).dirname)}"
248
+ else
249
+ match
250
+ end
251
+ end
252
+
253
+ @custom_lockfile.write(content)
254
+ end
230
255
  end
231
256
  end
@@ -23,6 +23,9 @@ module RubyLsp
23
23
  sig { returns(T::Hash[Symbol, RequestConfig]) }
24
24
  attr_accessor :features_configuration
25
25
 
26
+ sig { returns(String) }
27
+ attr_accessor :client_name
28
+
26
29
  sig { void }
27
30
  def initialize
28
31
  @state = T.let({}, T::Hash[String, Document])
@@ -33,10 +36,6 @@ module RubyLsp
33
36
  @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
34
37
  @features_configuration = T.let(
35
38
  {
36
- codeLens: RequestConfig.new({
37
- enableAll: false,
38
- gemfileLinks: true,
39
- }),
40
39
  inlayHint: RequestConfig.new({
41
40
  enableAll: false,
42
41
  implicitRescue: false,
@@ -45,6 +44,7 @@ module RubyLsp
45
44
  },
46
45
  T::Hash[Symbol, RequestConfig],
47
46
  )
47
+ @client_name = T.let("Unknown", String)
48
48
  end
49
49
 
50
50
  sig { params(uri: URI::Generic).returns(Document) }
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.13.3
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-11 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 0.19.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.20'
36
+ version: '0.22'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,21 +43,21 @@ dependencies:
43
43
  version: 0.19.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.20'
46
+ version: '0.22'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 0.5.5685
53
+ version: 0.5.10782
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 0.5.5685
60
+ version: 0.5.10782
61
61
  description: An opinionated language server for Ruby
62
62
  email:
63
63
  - ruby@shopify.com
@@ -97,7 +97,6 @@ files:
97
97
  - lib/ruby_lsp/document.rb
98
98
  - lib/ruby_lsp/executor.rb
99
99
  - lib/ruby_lsp/internal.rb
100
- - lib/ruby_lsp/listener.rb
101
100
  - lib/ruby_lsp/listeners/code_lens.rb
102
101
  - lib/ruby_lsp/listeners/completion.rb
103
102
  - lib/ruby_lsp/listeners/definition.rb
@@ -139,11 +138,17 @@ files:
139
138
  - lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
140
139
  - lib/ruby_lsp/requests/support/rubocop_runner.rb
141
140
  - lib/ruby_lsp/requests/support/selection_range.rb
142
- - lib/ruby_lsp/requests/support/semantic_token_encoder.rb
143
141
  - lib/ruby_lsp/requests/support/sorbet.rb
144
142
  - lib/ruby_lsp/requests/support/source_uri.rb
145
143
  - lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb
146
144
  - lib/ruby_lsp/requests/workspace_symbol.rb
145
+ - lib/ruby_lsp/response_builders.rb
146
+ - lib/ruby_lsp/response_builders/collection_response_builder.rb
147
+ - lib/ruby_lsp/response_builders/document_symbol.rb
148
+ - lib/ruby_lsp/response_builders/hover.rb
149
+ - lib/ruby_lsp/response_builders/response_builder.rb
150
+ - lib/ruby_lsp/response_builders/semantic_highlighting.rb
151
+ - lib/ruby_lsp/response_builders/signature_help.rb
147
152
  - lib/ruby_lsp/ruby_document.rb
148
153
  - lib/ruby_lsp/server.rb
149
154
  - lib/ruby_lsp/setup_bundler.rb
@@ -169,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
174
  - !ruby/object:Gem::Version
170
175
  version: '0'
171
176
  requirements: []
172
- rubygems_version: 3.5.4
177
+ rubygems_version: 3.5.6
173
178
  signing_key:
174
179
  specification_version: 4
175
180
  summary: An opinionated language server for Ruby
@@ -1,33 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyLsp
5
- # Listener is an abstract class to be used by requests for listening to events emitted when visiting an AST using the
6
- # Prism::Dispatcher.
7
- class Listener
8
- extend T::Sig
9
- extend T::Helpers
10
- extend T::Generic
11
- include Requests::Support::Common
12
-
13
- ResponseType = type_member
14
-
15
- abstract!
16
-
17
- sig { params(dispatcher: Prism::Dispatcher).void }
18
- def initialize(dispatcher)
19
- super()
20
- @dispatcher = dispatcher
21
- end
22
-
23
- sig { returns(ResponseType) }
24
- def response
25
- _response
26
- end
27
-
28
- # Override this method with an attr_reader that returns the response of your listener. The listener should
29
- # accumulate results in a @response variable and then provide the reader so that it is accessible
30
- sig { abstract.returns(ResponseType) }
31
- def _response; end
32
- end
33
- end
@@ -1,73 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyLsp
5
- module Requests
6
- module Support
7
- class SemanticTokenEncoder
8
- extend T::Sig
9
-
10
- sig { void }
11
- def initialize
12
- @current_row = T.let(0, Integer)
13
- @current_column = T.let(0, Integer)
14
- end
15
-
16
- sig do
17
- params(
18
- tokens: T::Array[Listeners::SemanticHighlighting::SemanticToken],
19
- ).returns(Interface::SemanticTokens)
20
- end
21
- def encode(tokens)
22
- delta = tokens
23
- .sort_by do |token|
24
- [token.location.start_line, token.location.start_column]
25
- end
26
- .flat_map do |token|
27
- compute_delta(token)
28
- end
29
-
30
- Interface::SemanticTokens.new(data: delta)
31
- end
32
-
33
- # The delta array is computed according to the LSP specification:
34
- # > The protocol for the token format relative uses relative
35
- # > positions, because most tokens remain stable relative to
36
- # > each other when edits are made in a file. This simplifies
37
- # > the computation of a delta if a server supports it. So each
38
- # > token is represented using 5 integers.
39
-
40
- # For more information on how each number is calculated, read:
41
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
42
- sig { params(token: Listeners::SemanticHighlighting::SemanticToken).returns(T::Array[Integer]) }
43
- def compute_delta(token)
44
- row = token.location.start_line - 1
45
- column = token.location.start_column
46
-
47
- begin
48
- delta_line = row - @current_row
49
-
50
- delta_column = column
51
- delta_column -= @current_column if delta_line == 0
52
-
53
- [delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
54
- ensure
55
- @current_row = row
56
- @current_column = column
57
- end
58
- end
59
-
60
- # Encode an array of modifiers to positions onto a bit flag
61
- # For example, [:default_library] will be encoded as
62
- # 0b1000000000, as :default_library is the 10th bit according
63
- # to the token modifiers index map.
64
- sig { params(modifiers: T::Array[Integer]).returns(Integer) }
65
- def encode_modifiers(modifiers)
66
- modifiers.inject(0) do |encoded_modifiers, modifier|
67
- encoded_modifiers | (1 << modifier)
68
- end
69
- end
70
- end
71
- end
72
- end
73
- end