ruby-lsp 0.0.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -0
  3. data/.rubocop.yml +25 -0
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile +10 -6
  6. data/Gemfile.lock +63 -16
  7. data/README.md +41 -0
  8. data/Rakefile +8 -1
  9. data/VERSION +1 -1
  10. data/bin/console +19 -0
  11. data/bin/tapioca +29 -0
  12. data/dev.yml +3 -0
  13. data/exe/ruby-lsp +19 -4
  14. data/lib/ruby-lsp.rb +2 -1
  15. data/lib/ruby_lsp/cli.rb +13 -5
  16. data/lib/ruby_lsp/document.rb +43 -14
  17. data/lib/ruby_lsp/handler.rb +107 -37
  18. data/lib/ruby_lsp/internal.rb +7 -0
  19. data/lib/ruby_lsp/requests/base_request.rb +18 -5
  20. data/lib/ruby_lsp/requests/code_actions.rb +20 -8
  21. data/lib/ruby_lsp/requests/diagnostics.rb +25 -7
  22. data/lib/ruby_lsp/requests/document_highlight.rb +113 -0
  23. data/lib/ruby_lsp/requests/document_symbol.rb +56 -16
  24. data/lib/ruby_lsp/requests/folding_ranges.rb +70 -34
  25. data/lib/ruby_lsp/requests/formatting.rb +24 -14
  26. data/lib/ruby_lsp/requests/selection_ranges.rb +18 -4
  27. data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -34
  28. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +16 -3
  29. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +61 -0
  30. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +50 -0
  31. data/lib/ruby_lsp/requests/support/selection_range.rb +4 -0
  32. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +24 -3
  33. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +6 -0
  34. data/lib/ruby_lsp/requests.rb +13 -1
  35. data/lib/ruby_lsp/store.rb +20 -3
  36. data/rakelib/check_docs.rake +34 -6
  37. data/ruby-lsp.gemspec +7 -5
  38. data/sorbet/config +4 -0
  39. data/sorbet/rbi/.rubocop.yml +8 -0
  40. data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
  41. data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
  42. data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
  43. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  44. data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
  45. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
  46. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  47. data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
  48. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
  49. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  50. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
  51. data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
  52. data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
  53. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
  54. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
  55. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  56. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
  57. data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
  58. data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
  59. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
  60. data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
  61. data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
  62. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
  63. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
  64. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
  65. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
  66. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
  67. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
  68. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
  69. data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
  70. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
  71. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
  72. data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
  73. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
  74. data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
  75. data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
  76. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
  77. data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
  78. data/sorbet/rbi/shims/fiddle.rbi +4 -0
  79. data/sorbet/rbi/shims/hash.rbi +6 -0
  80. data/sorbet/rbi/shims/rdoc.rbi +4 -0
  81. data/sorbet/tapioca/config.yml +13 -0
  82. data/sorbet/tapioca/require.rb +7 -0
  83. metadata +62 -13
  84. data/lib/ruby_lsp/requests/rubocop_request.rb +0 -49
  85. data/shipit.production.yml +0 -1
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require "ruby_lsp/requests"
@@ -6,51 +7,84 @@ require "benchmark"
6
7
 
7
8
  module RubyLsp
8
9
  class Handler
9
- IGNORED_FOR_TELEMETRY = [
10
- "initialized",
11
- "$/cancelRequest",
12
- ].freeze
10
+ extend T::Sig
11
+ VOID = T.let(Object.new.freeze, Object)
13
12
 
13
+ sig { returns(Store) }
14
14
  attr_reader :store
15
15
 
16
16
  Interface = LanguageServer::Protocol::Interface
17
17
  Constant = LanguageServer::Protocol::Constant
18
18
  Transport = LanguageServer::Protocol::Transport
19
19
 
20
+ sig { void }
20
21
  def initialize
21
- @writer = Transport::Stdio::Writer.new
22
- @reader = Transport::Stdio::Reader.new
23
- @handlers = {}
24
- @store = Store.new
22
+ @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
23
+ @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
24
+ @handlers = T.let({}, T::Hash[String, T.proc.params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)])
25
+ @store = T.let(Store.new, Store)
25
26
  end
26
27
 
28
+ sig { void }
27
29
  def start
28
30
  $stderr.puts "Starting Ruby LSP..."
29
- @reader.read do |request|
30
- with_telemetry(request) { handle(request) }
31
- end
31
+ @reader.read { |request| handle(request) }
32
32
  end
33
33
 
34
+ sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
34
35
  def config(&blk)
35
36
  instance_exec(&blk)
36
37
  end
37
38
 
38
39
  private
39
40
 
41
+ sig do
42
+ params(
43
+ msg: String,
44
+ blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
45
+ ).void
46
+ end
40
47
  def on(msg, &blk)
41
- @handlers[msg.to_s] = blk
48
+ @handlers[msg] = blk
42
49
  end
43
50
 
51
+ sig { params(request: T::Hash[Symbol, T.untyped]).void }
44
52
  def handle(request)
45
- result = @handlers[request[:method]]&.call(request)
46
- @writer.write(id: request[:id], result: result) if result
53
+ result = T.let(nil, T.untyped)
54
+ error = T.let(nil, T.nilable(StandardError))
55
+ handler = @handlers[request[:method]]
56
+
57
+ request_time = Benchmark.realtime do
58
+ if handler
59
+ begin
60
+ result = handler.call(request)
61
+ rescue StandardError => e
62
+ error = e
63
+ end
64
+
65
+ if error
66
+ @writer.write(
67
+ {
68
+ id: request[:id],
69
+ error: { code: Constant::ErrorCodes::INTERNAL_ERROR, message: error.inspect, data: request.to_json },
70
+ }
71
+ )
72
+ elsif result != VOID
73
+ @writer.write(id: request[:id], result: result)
74
+ end
75
+ end
76
+ end
77
+
78
+ @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
47
79
  end
48
80
 
81
+ sig { void }
49
82
  def shutdown
50
83
  $stderr.puts "Shutting down Ruby LSP..."
51
84
  store.clear
52
85
  end
53
86
 
87
+ sig { params(enabled_features: T::Array[String]).returns(Interface::InitializeResult) }
54
88
  def respond_with_capabilities(enabled_features)
55
89
  document_symbol_provider = if enabled_features.include?("documentSymbols")
56
90
  Interface::DocumentSymbolClientCapabilities.new(
@@ -69,8 +103,8 @@ module RubyLsp
69
103
  Interface::SemanticTokensRegistrationOptions.new(
70
104
  document_selector: { scheme: "file", language: "ruby" },
71
105
  legend: Interface::SemanticTokensLegend.new(
72
- token_types: Requests::SemanticHighlighting::TOKEN_TYPES,
73
- token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS
106
+ token_types: Requests::SemanticHighlighting::TOKEN_TYPES.keys,
107
+ token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys
74
108
  ),
75
109
  range: false,
76
110
  full: {
@@ -90,26 +124,35 @@ module RubyLsp
90
124
  folding_range_provider: folding_ranges_provider,
91
125
  semantic_tokens_provider: semantic_tokens_provider,
92
126
  document_formatting_provider: enabled_features.include?("formatting"),
127
+ document_highlight_provider: enabled_features.include?("documentHighlights"),
93
128
  code_action_provider: enabled_features.include?("codeActions")
94
129
  )
95
130
  )
96
131
  end
97
132
 
133
+ sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
98
134
  def respond_with_document_symbol(uri)
99
135
  store.cache_fetch(uri, :document_symbol) do |document|
100
- RubyLsp::Requests::DocumentSymbol.run(document)
136
+ RubyLsp::Requests::DocumentSymbol.new(document).run
101
137
  end
102
138
  end
103
139
 
140
+ sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::FoldingRange]) }
104
141
  def respond_with_folding_ranges(uri)
105
142
  store.cache_fetch(uri, :folding_ranges) do |document|
106
- Requests::FoldingRanges.run(document)
143
+ Requests::FoldingRanges.new(document).run
107
144
  end
108
145
  end
109
146
 
147
+ sig do
148
+ params(
149
+ uri: String,
150
+ positions: T::Array[Document::PositionShape]
151
+ ).returns(T::Array[T.nilable(RubyLsp::Requests::Support::SelectionRange)])
152
+ end
110
153
  def respond_with_selection_ranges(uri, positions)
111
154
  ranges = store.cache_fetch(uri, :selection_ranges) do |document|
112
- Requests::SelectionRanges.run(document)
155
+ Requests::SelectionRanges.new(document).run
113
156
  end
114
157
 
115
158
  # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
@@ -123,19 +166,25 @@ module RubyLsp
123
166
  end
124
167
  end
125
168
 
169
+ sig { params(uri: String).returns(LanguageServer::Protocol::Interface::SemanticTokens) }
126
170
  def respond_with_semantic_highlighting(uri)
127
171
  store.cache_fetch(uri, :semantic_highlighting) do |document|
128
- Requests::SemanticHighlighting.new(document, encoder: Requests::Support::SemanticTokenEncoder.new).run
172
+ T.cast(
173
+ Requests::SemanticHighlighting.new(document, encoder: Requests::Support::SemanticTokenEncoder.new).run,
174
+ LanguageServer::Protocol::Interface::SemanticTokens
175
+ )
129
176
  end
130
177
  end
131
178
 
179
+ sig { params(uri: String).returns(T.nilable(T::Array[LanguageServer::Protocol::Interface::TextEdit])) }
132
180
  def respond_with_formatting(uri)
133
- Requests::Formatting.run(uri, store.get(uri))
181
+ Requests::Formatting.new(uri, store.get(uri)).run
134
182
  end
135
183
 
184
+ sig { params(uri: String).void }
136
185
  def send_diagnostics(uri)
137
186
  response = store.cache_fetch(uri, :diagnostics) do |document|
138
- Requests::Diagnostics.run(uri, document)
187
+ Requests::Diagnostics.new(uri, document).run
139
188
  end
140
189
 
141
190
  @writer.write(
@@ -147,35 +196,56 @@ module RubyLsp
147
196
  )
148
197
  end
149
198
 
199
+ sig { params(uri: String).void }
200
+ def clear_diagnostics(uri)
201
+ @writer.write(
202
+ method: "textDocument/publishDiagnostics",
203
+ params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: [])
204
+ )
205
+ end
206
+
207
+ sig do
208
+ params(uri: String, range: T::Range[Integer]).returns(T::Array[LanguageServer::Protocol::Interface::CodeAction])
209
+ end
150
210
  def respond_with_code_actions(uri, range)
151
211
  store.cache_fetch(uri, :code_actions) do |document|
152
- Requests::CodeActions.run(uri, document, range)
212
+ Requests::CodeActions.new(uri, document, range).run
153
213
  end
154
214
  end
155
215
 
156
- def configure_options(initialization_options)
157
- @telemetry_enabled = initialization_options.fetch(:telemetryEnabled, false)
216
+ sig do
217
+ params(
218
+ uri: String,
219
+ position: Document::PositionShape
220
+ ).returns(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
221
+ end
222
+ def respond_with_document_highlight(uri, position)
223
+ Requests::DocumentHighlight.new(store.get(uri), position).run
158
224
  end
159
225
 
160
- def with_telemetry(request)
161
- return yield unless @telemetry_enabled && !IGNORED_FOR_TELEMETRY.include?(request[:method])
162
-
163
- result = nil
164
- request_time = Benchmark.realtime do
165
- result = yield
166
- end
226
+ sig do
227
+ params(
228
+ request: T::Hash[Symbol, T.untyped],
229
+ request_time: Float,
230
+ error: T.nilable(StandardError)
231
+ ).returns(T::Hash[Symbol, T.any(String, Float)])
232
+ end
233
+ def telemetry_params(request, request_time, error)
234
+ uri = request.dig(:params, :textDocument, :uri)
167
235
 
168
236
  params = {
169
237
  request: request[:method],
170
- requestTime: request_time,
171
238
  lspVersion: RubyLsp::VERSION,
239
+ requestTime: request_time,
172
240
  }
173
241
 
174
- uri = request.dig(:params, :textDocument, :uri)
175
- params[:uri] = uri if uri
242
+ if error
243
+ params[:errorClass] = error.class.name
244
+ params[:errorMessage] = error.message
245
+ end
176
246
 
177
- @writer.write(method: "telemetry/event", params: params)
178
- result
247
+ params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
248
+ params
179
249
  end
180
250
  end
181
251
  end
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "syntax_tree"
6
+ require "ruby-lsp"
7
+ require "ruby_lsp/cli"
@@ -1,21 +1,34 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
4
5
  module Requests
5
6
  # :nodoc:
6
7
  class BaseRequest < SyntaxTree::Visitor
7
- def self.run(document)
8
- new(document).run
9
- end
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
10
12
 
13
+ sig { params(document: Document).void }
11
14
  def initialize(document)
12
15
  @document = document
13
16
 
14
17
  super()
15
18
  end
16
19
 
17
- def run
18
- raise NotImplementedError, "#{self.class}#run must be implemented"
20
+ sig { abstract.returns(Object) }
21
+ def run; end
22
+
23
+ sig { params(node: SyntaxTree::Node).returns(LanguageServer::Protocol::Interface::Range) }
24
+ def range_from_syntax_tree_node(node)
25
+ loc = node.location
26
+
27
+ LanguageServer::Protocol::Interface::Range.new(
28
+ start: LanguageServer::Protocol::Interface::Position.new(line: loc.start_line - 1,
29
+ character: loc.start_column),
30
+ end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
31
+ )
19
32
  end
20
33
  end
21
34
  end
@@ -1,7 +1,10 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
4
5
  module Requests
6
+ # ![Code actions demo](../../misc/code_actions.gif)
7
+ #
5
8
  # The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
6
9
  # request informs the editor of RuboCop quick fixes that can be applied. These are accesible by hovering over a
7
10
  # specific diagnostic.
@@ -13,23 +16,32 @@ module RubyLsp
13
16
  # puts "Hello" # --> code action: quick fix indentation
14
17
  # end
15
18
  # ```
16
- class CodeActions
17
- def self.run(uri, document, range)
18
- new(uri, document, range).run
19
- end
19
+ class CodeActions < BaseRequest
20
+ extend T::Sig
20
21
 
22
+ sig do
23
+ params(
24
+ uri: String,
25
+ document: Document,
26
+ range: T::Range[Integer]
27
+ ).void
28
+ end
21
29
  def initialize(uri, document, range)
22
- @document = document
30
+ super(document)
31
+
23
32
  @uri = uri
24
33
  @range = range
25
34
  end
26
35
 
36
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::CodeAction], Object)) }
27
37
  def run
28
- diagnostics = Diagnostics.run(@uri, @document)
29
- corrections = diagnostics.select { |diagnostic| diagnostic.correctable? && diagnostic.in_range?(@range) }
38
+ diagnostics = Diagnostics.new(@uri, @document).run
39
+ corrections = diagnostics.select do |diagnostic|
40
+ diagnostic.correctable? && T.cast(diagnostic, Support::RuboCopDiagnostic).in_range?(@range)
41
+ end
30
42
  return [] if corrections.empty?
31
43
 
32
- corrections.map!(&:to_lsp_code_action)
44
+ T.cast(corrections, T::Array[Support::RuboCopDiagnostic]).map!(&:to_lsp_code_action)
33
45
  end
34
46
  end
35
47
  end
@@ -1,7 +1,12 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
4
+ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
5
+
3
6
  module RubyLsp
4
7
  module Requests
8
+ # ![Diagnostics demo](../../misc/diagnostics.gif)
9
+ #
5
10
  # The
6
11
  # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
7
12
  # request informs the editor of RuboCop offenses for a given file.
@@ -13,21 +18,34 @@ module RubyLsp
13
18
  # puts "Hello" # --> diagnostics: incorrect indentantion
14
19
  # end
15
20
  # ```
16
- class Diagnostics < RuboCopRequest
17
- def run
18
- return syntax_error_diagnostics if @document.syntax_errors?
21
+ class Diagnostics < BaseRequest
22
+ extend T::Sig
19
23
 
20
- super
24
+ sig { params(uri: String, document: Document).void }
25
+ def initialize(uri, document)
26
+ super(document)
21
27
 
22
- @diagnostics
28
+ @uri = uri
23
29
  end
24
30
 
25
- def file_finished(_file, offenses)
26
- @diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
31
+ sig do
32
+ override.returns(
33
+ T.any(
34
+ T.all(T::Array[Support::RuboCopDiagnostic], Object),
35
+ T.all(T::Array[Support::SyntaxErrorDiagnostic], Object),
36
+ )
37
+ )
38
+ end
39
+ def run
40
+ return syntax_error_diagnostics if @document.syntax_errors?
41
+ return [] unless defined?(Support::RuboCopDiagnosticsRunner)
42
+
43
+ Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
27
44
  end
28
45
 
29
46
  private
30
47
 
48
+ sig { returns(T::Array[Support::SyntaxErrorDiagnostic]) }
31
49
  def syntax_error_diagnostics
32
50
  @document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
33
51
  end
@@ -0,0 +1,113 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Document highlight demo](../../misc/document_highlight.gif)
7
+ #
8
+ # The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
9
+ # informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
10
+ # the cursor is on the `F` of the constant `FOO`, the editor should identify other occurences of `FOO`
11
+ # and highlight them.
12
+ #
13
+ # For writable elements like constants or variables, their read/write occurrences should be highlighted differently.
14
+ # This is achieved by sending different "kind" attributes to the editor (2 for read and 3 for write).
15
+ #
16
+ # # Example
17
+ #
18
+ # ```ruby
19
+ # FOO = 1 # should be highlighted as "write"
20
+ #
21
+ # def foo
22
+ # FOO # should be highlighted as "read"
23
+ # end
24
+ # ```
25
+ class DocumentHighlight < BaseRequest
26
+ extend T::Sig
27
+
28
+ VarNodes = T.type_alias do
29
+ T.any(
30
+ SyntaxTree::GVar,
31
+ SyntaxTree::Ident,
32
+ SyntaxTree::IVar,
33
+ SyntaxTree::Const,
34
+ SyntaxTree::CVar
35
+ )
36
+ end
37
+
38
+ sig { params(document: Document, position: Document::PositionShape).void }
39
+ def initialize(document, position)
40
+ @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
41
+ position = Document::Scanner.new(document.source).find_position(position)
42
+ @target = T.let(find(document.tree, position), T.nilable(VarNodes))
43
+
44
+ super(document)
45
+ end
46
+
47
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
48
+ def run
49
+ # no @target means the target is not highlightable
50
+ return [] unless @target
51
+
52
+ visit(@document.tree)
53
+ @highlights
54
+ end
55
+
56
+ sig { params(node: SyntaxTree::VarField).void }
57
+ def visit_var_field(node)
58
+ if matches_target?(node.value)
59
+ add_highlight(
60
+ node.value,
61
+ LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
62
+ )
63
+ end
64
+
65
+ super
66
+ end
67
+
68
+ sig { params(node: SyntaxTree::VarRef).void }
69
+ def visit_var_ref(node)
70
+ if matches_target?(node.value)
71
+ add_highlight(
72
+ node.value,
73
+ LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
74
+ )
75
+ end
76
+
77
+ super
78
+ end
79
+
80
+ private
81
+
82
+ sig { params(node: SyntaxTree::Node, position: Integer).returns(T.nilable(VarNodes)) }
83
+ def find(node, position)
84
+ matched =
85
+ node.child_nodes.compact.bsearch do |child|
86
+ if (child.location.start_char...child.location.end_char).cover?(position)
87
+ 0
88
+ else
89
+ position <=> child.location.start_char
90
+ end
91
+ end
92
+
93
+ case matched
94
+ when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
95
+ matched
96
+ when SyntaxTree::Node
97
+ find(matched, position)
98
+ end
99
+ end
100
+
101
+ sig { params(node: SyntaxTree::Node).returns(T::Boolean) }
102
+ def matches_target?(node)
103
+ node.is_a?(@target.class) && T.cast(node, VarNodes).value == T.must(@target).value
104
+ end
105
+
106
+ sig { params(node: SyntaxTree::Node, kind: Integer).void }
107
+ def add_highlight(node, kind)
108
+ range = range_from_syntax_tree_node(node)
109
+ @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
110
+ end
111
+ end
112
+ end
113
+ end