ruby-lsp 0.0.3 → 0.2.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 (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