ruby-lsp 0.14.6 → 0.16.5

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +1 -16
  5. data/exe/ruby-lsp-check +13 -22
  6. data/exe/ruby-lsp-doctor +9 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +14 -1
  8. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +32 -8
  10. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +26 -0
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  13. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  14. data/lib/ruby_lsp/addon.rb +18 -9
  15. data/lib/ruby_lsp/base_server.rb +149 -0
  16. data/lib/ruby_lsp/document.rb +6 -11
  17. data/lib/ruby_lsp/global_state.rb +180 -0
  18. data/lib/ruby_lsp/internal.rb +4 -1
  19. data/lib/ruby_lsp/listeners/code_lens.rb +22 -13
  20. data/lib/ruby_lsp/listeners/completion.rb +13 -14
  21. data/lib/ruby_lsp/listeners/definition.rb +14 -6
  22. data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
  23. data/lib/ruby_lsp/listeners/hover.rb +6 -5
  24. data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
  25. data/lib/ruby_lsp/load_sorbet.rb +62 -0
  26. data/lib/ruby_lsp/requests/code_lens.rb +3 -2
  27. data/lib/ruby_lsp/requests/completion.rb +15 -4
  28. data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
  29. data/lib/ruby_lsp/requests/definition.rb +11 -4
  30. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  31. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  32. data/lib/ruby_lsp/requests/formatting.rb +7 -43
  33. data/lib/ruby_lsp/requests/hover.rb +4 -4
  34. data/lib/ruby_lsp/requests/request.rb +2 -0
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +7 -4
  36. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  37. data/lib/ruby_lsp/requests/support/common.rb +16 -5
  38. data/lib/ruby_lsp/requests/support/formatter.rb +26 -0
  39. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
  40. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +47 -0
  41. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  42. data/lib/ruby_lsp/requests/support/{syntax_tree_formatting_runner.rb → syntax_tree_formatter.rb} +13 -6
  43. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  44. data/lib/ruby_lsp/requests.rb +3 -1
  45. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +36 -13
  46. data/lib/ruby_lsp/server.rb +763 -142
  47. data/lib/ruby_lsp/setup_bundler.rb +13 -1
  48. data/lib/ruby_lsp/store.rb +3 -15
  49. data/lib/ruby_lsp/test_helper.rb +52 -0
  50. data/lib/ruby_lsp/utils.rb +68 -33
  51. metadata +16 -13
  52. data/lib/ruby_lsp/executor.rb +0 -614
  53. data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -93
  54. data/lib/ruby_lsp/requests/support/formatter_runner.rb +0 -18
  55. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -34
  56. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -35
@@ -0,0 +1,149 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class BaseServer
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ abstract!
10
+
11
+ sig { params(test_mode: T::Boolean).void }
12
+ def initialize(test_mode: false)
13
+ @test_mode = T.let(test_mode, T::Boolean)
14
+ @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
15
+ @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
16
+ @incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
17
+ @outgoing_queue = T.let(Thread::Queue.new, Thread::Queue)
18
+ @cancelled_requests = T.let([], T::Array[Integer])
19
+ @mutex = T.let(Mutex.new, Mutex)
20
+ @worker = T.let(new_worker, Thread)
21
+ @current_request_id = T.let(1, Integer)
22
+ @store = T.let(Store.new, Store)
23
+ @outgoing_dispatcher = T.let(
24
+ Thread.new do
25
+ unless test_mode
26
+ while (message = @outgoing_queue.pop)
27
+ @mutex.synchronize { @writer.write(message.to_hash) }
28
+ end
29
+ end
30
+ end,
31
+ Thread,
32
+ )
33
+
34
+ Thread.main.priority = 1
35
+ end
36
+
37
+ sig { void }
38
+ def start
39
+ @reader.read do |message|
40
+ method = message[:method]
41
+
42
+ # We must parse the document under a mutex lock or else we might switch threads and accept text edits in the
43
+ # source. Altering the source reference during parsing will put the parser in an invalid internal state, since
44
+ # it started parsing with one source but then it changed in the middle. We don't want to do this for text
45
+ # synchronization notifications
46
+ @mutex.synchronize do
47
+ uri = message.dig(:params, :textDocument, :uri)
48
+
49
+ if uri
50
+ begin
51
+ parsed_uri = URI(uri)
52
+ message[:params][:textDocument][:uri] = parsed_uri
53
+
54
+ # We don't want to try to parse documents on text synchronization notifications
55
+ @store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
56
+ rescue Errno::ENOENT
57
+ # If we receive a request for a file that no longer exists, we don't want to fail
58
+ end
59
+ end
60
+ end
61
+
62
+ # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
63
+ # else is pushed into the incoming queue
64
+ case method
65
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
+ process_message(message)
67
+ when "shutdown"
68
+ $stderr.puts("Shutting down Ruby LSP...")
69
+
70
+ shutdown
71
+
72
+ @mutex.synchronize do
73
+ run_shutdown
74
+ @writer.write(Result.new(id: message[:id], response: nil).to_hash)
75
+ end
76
+ when "exit"
77
+ @mutex.synchronize do
78
+ status = @incoming_queue.closed? ? 0 : 1
79
+ $stderr.puts("Shutdown complete with status #{status}")
80
+ exit(status)
81
+ end
82
+ else
83
+ @incoming_queue << message
84
+ end
85
+ end
86
+ end
87
+
88
+ sig { void }
89
+ def run_shutdown
90
+ @incoming_queue.clear
91
+ @outgoing_queue.clear
92
+ @incoming_queue.close
93
+ @outgoing_queue.close
94
+ @cancelled_requests.clear
95
+
96
+ @worker.join
97
+ @outgoing_dispatcher.join
98
+ @store.clear
99
+ end
100
+
101
+ # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
102
+ sig { returns(T.untyped) }
103
+ def pop_response
104
+ @outgoing_queue.pop
105
+ end
106
+
107
+ sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
108
+ def process_message(message); end
109
+
110
+ sig { abstract.void }
111
+ def shutdown; end
112
+
113
+ sig { returns(Thread) }
114
+ def new_worker
115
+ Thread.new do
116
+ while (message = T.let(@incoming_queue.pop, T.nilable(T::Hash[Symbol, T.untyped])))
117
+ id = message[:id]
118
+
119
+ # Check if the request was cancelled before trying to process it
120
+ @mutex.synchronize do
121
+ if id && @cancelled_requests.include?(id)
122
+ send_message(Result.new(id: id, response: nil))
123
+ @cancelled_requests.delete(id)
124
+ next
125
+ end
126
+ end
127
+
128
+ process_message(message)
129
+ end
130
+ end
131
+ end
132
+
133
+ sig { params(message: T.any(Result, Error, Notification, Request)).void }
134
+ def send_message(message)
135
+ # When we're shutting down the server, there's a small race condition between closing the thread queues and
136
+ # finishing remaining requests. We may close the queue in the middle of processing a request, which will then fail
137
+ # when trying to send a response back
138
+ return if @outgoing_queue.closed?
139
+
140
+ @outgoing_queue << message
141
+ @current_request_id += 1 if message.is_a?(Request)
142
+ end
143
+
144
+ sig { params(id: Integer).void }
145
+ def send_empty_response(id)
146
+ send_message(Result.new(id: id, response: nil))
147
+ end
148
+ end
149
+ end
@@ -20,13 +20,13 @@ module RubyLsp
20
20
  sig { returns(URI::Generic) }
21
21
  attr_reader :uri
22
22
 
23
- sig { returns(String) }
23
+ sig { returns(Encoding) }
24
24
  attr_reader :encoding
25
25
 
26
- sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
27
- def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
27
+ def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
28
28
  @cache = T.let({}, T::Hash[String, T.untyped])
29
- @encoding = T.let(encoding, String)
29
+ @encoding = T.let(encoding, Encoding)
30
30
  @source = T.let(source, String)
31
31
  @version = T.let(version, Integer)
32
32
  @uri = T.let(uri, URI::Generic)
@@ -180,11 +180,6 @@ module RubyLsp
180
180
  end
181
181
  end
182
182
 
183
- sig { returns(T::Boolean) }
184
- def typechecker_enabled?
185
- DependencyDetector.instance.typechecker && sorbet_sigil_is_true_or_higher
186
- end
187
-
188
183
  class Scanner
189
184
  extend T::Sig
190
185
 
@@ -192,7 +187,7 @@ module RubyLsp
192
187
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
193
188
  SURROGATE_PAIR_START = T.let(0xFFFF, Integer)
194
189
 
195
- sig { params(source: String, encoding: String).void }
190
+ sig { params(source: String, encoding: Encoding).void }
196
191
  def initialize(source, encoding)
197
192
  @current_line = T.let(0, Integer)
198
193
  @pos = T.let(0, Integer)
@@ -214,7 +209,7 @@ module RubyLsp
214
209
  # need to adjust for surrogate pairs
215
210
  requested_position = @pos + position[:character]
216
211
 
217
- if @encoding == Constant::PositionEncodingKind::UTF16
212
+ if @encoding == Encoding::UTF_16LE
218
213
  requested_position -= utf_16_character_position_correction(@pos, requested_position)
219
214
  end
220
215
 
@@ -0,0 +1,180 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class GlobalState
6
+ extend T::Sig
7
+
8
+ sig { returns(String) }
9
+ attr_reader :test_library
10
+
11
+ sig { returns(String) }
12
+ attr_accessor :formatter
13
+
14
+ sig { returns(T::Boolean) }
15
+ attr_reader :typechecker
16
+
17
+ sig { returns(RubyIndexer::Index) }
18
+ attr_reader :index
19
+
20
+ sig { returns(Encoding) }
21
+ attr_reader :encoding
22
+
23
+ sig { returns(T::Boolean) }
24
+ attr_reader :supports_watching_files
25
+
26
+ sig { void }
27
+ def initialize
28
+ @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
29
+ @encoding = T.let(Encoding::UTF_8, Encoding)
30
+
31
+ @formatter = T.let("auto", String)
32
+ @linters = T.let([], T::Array[String])
33
+ @test_library = T.let("minitest", String)
34
+ @typechecker = T.let(true, T::Boolean)
35
+ @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
36
+ @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
37
+ @supports_watching_files = T.let(false, T::Boolean)
38
+ end
39
+
40
+ sig { params(identifier: String, instance: Requests::Support::Formatter).void }
41
+ def register_formatter(identifier, instance)
42
+ @supported_formatters[identifier] = instance
43
+ end
44
+
45
+ sig { returns(T.nilable(Requests::Support::Formatter)) }
46
+ def active_formatter
47
+ @supported_formatters[@formatter]
48
+ end
49
+
50
+ sig { returns(T::Array[Requests::Support::Formatter]) }
51
+ def active_linters
52
+ @linters.filter_map { |name| @supported_formatters[name] }
53
+ end
54
+
55
+ sig { params(options: T::Hash[Symbol, T.untyped]).void }
56
+ def apply_options(options)
57
+ dependencies = gather_dependencies
58
+ workspace_uri = options.dig(:workspaceFolders, 0, :uri)
59
+ @workspace_uri = URI(workspace_uri) if workspace_uri
60
+
61
+ specified_formatter = options.dig(:initializationOptions, :formatter)
62
+ @formatter = specified_formatter if specified_formatter
63
+ @formatter = detect_formatter(dependencies) if @formatter == "auto"
64
+
65
+ specified_linters = options.dig(:initializationOptions, :linters)
66
+ @linters = specified_linters || detect_linters(dependencies)
67
+ @test_library = detect_test_library(dependencies)
68
+ @typechecker = detect_typechecker(dependencies)
69
+
70
+ encodings = options.dig(:capabilities, :general, :positionEncodings)
71
+ @encoding = if !encodings || encodings.empty?
72
+ Encoding::UTF_16LE
73
+ elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
74
+ Encoding::UTF_8
75
+ elsif encodings.include?(Constant::PositionEncodingKind::UTF16)
76
+ Encoding::UTF_16LE
77
+ else
78
+ Encoding::UTF_32
79
+ end
80
+
81
+ file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
82
+ if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
83
+ @supports_watching_files = true
84
+ end
85
+ end
86
+
87
+ sig { returns(String) }
88
+ def workspace_path
89
+ T.must(@workspace_uri.to_standardized_path)
90
+ end
91
+
92
+ sig { returns(String) }
93
+ def encoding_name
94
+ case @encoding
95
+ when Encoding::UTF_8
96
+ Constant::PositionEncodingKind::UTF8
97
+ when Encoding::UTF_16LE
98
+ Constant::PositionEncodingKind::UTF16
99
+ else
100
+ Constant::PositionEncodingKind::UTF32
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ sig { params(dependencies: T::Array[String]).returns(String) }
107
+ def detect_formatter(dependencies)
108
+ # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
109
+ if dependencies.any?(/^rubocop/)
110
+ "rubocop"
111
+ elsif dependencies.any?(/^syntax_tree$/)
112
+ "syntax_tree"
113
+ else
114
+ "none"
115
+ end
116
+ end
117
+
118
+ # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
119
+ # single linter. To have multiple linters running, the user must configure them manually
120
+ sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
121
+ def detect_linters(dependencies)
122
+ linters = []
123
+ linters << "rubocop" if dependencies.any?(/^rubocop/)
124
+ linters
125
+ end
126
+
127
+ sig { params(dependencies: T::Array[String]).returns(String) }
128
+ def detect_test_library(dependencies)
129
+ if dependencies.any?(/^rspec/)
130
+ "rspec"
131
+ # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
132
+ # by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
133
+ # activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
134
+ # of bin/rails to support these cases.
135
+ elsif File.exist?(File.join(workspace_path, "bin/rails"))
136
+ "rails"
137
+ # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
138
+ elsif dependencies.any?(/^minitest$/)
139
+ "minitest"
140
+ elsif dependencies.any?(/^test-unit/)
141
+ "test-unit"
142
+ else
143
+ "unknown"
144
+ end
145
+ end
146
+
147
+ sig { params(dependencies: T::Array[String]).returns(T::Boolean) }
148
+ def detect_typechecker(dependencies)
149
+ return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
150
+
151
+ # We can't read the env from within `Bundle.with_original_env` so we need to set it here.
152
+ ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
153
+ Bundler.with_original_env do
154
+ sorbet_static_detected = dependencies.any?(/^sorbet-static/)
155
+ # Don't show message while running tests, since it's noisy
156
+ if sorbet_static_detected && !ruby_lsp_env_is_test
157
+ $stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
158
+ end
159
+ sorbet_static_detected
160
+ end
161
+ rescue Bundler::GemfileNotFound
162
+ false
163
+ end
164
+
165
+ sig { returns(T::Array[String]) }
166
+ def gather_dependencies
167
+ Bundler.with_original_env { Bundler.default_gemfile }
168
+ Bundler.locked_gems.dependencies.keys + gemspec_dependencies
169
+ rescue Bundler::GemfileNotFound
170
+ []
171
+ end
172
+
173
+ sig { returns(T::Array[String]) }
174
+ def gemspec_dependencies
175
+ Bundler.locked_gems.sources
176
+ .grep(Bundler::Source::Gemspec)
177
+ .flat_map { _1.gemspec&.dependencies&.map(&:name) }
178
+ end
179
+ end
180
+ end
@@ -20,12 +20,13 @@ require "prism/visitor"
20
20
  require "language_server-protocol"
21
21
 
22
22
  require "ruby-lsp"
23
+ require "ruby_lsp/base_server"
23
24
  require "ruby_indexer/ruby_indexer"
24
25
  require "core_ext/uri"
25
26
  require "ruby_lsp/utils"
26
27
  require "ruby_lsp/parameter_scope"
28
+ require "ruby_lsp/global_state"
27
29
  require "ruby_lsp/server"
28
- require "ruby_lsp/executor"
29
30
  require "ruby_lsp/requests"
30
31
  require "ruby_lsp/response_builders"
31
32
  require "ruby_lsp/document"
@@ -33,3 +34,5 @@ require "ruby_lsp/ruby_document"
33
34
  require "ruby_lsp/store"
34
35
  require "ruby_lsp/addon"
35
36
  require "ruby_lsp/requests/support/rubocop_runner"
37
+ require "ruby_lsp/requests/support/rubocop_formatter"
38
+ require "ruby_lsp/requests/support/syntax_tree_formatter"
@@ -27,12 +27,14 @@ module RubyLsp
27
27
  sig do
28
28
  params(
29
29
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
30
+ global_state: GlobalState,
30
31
  uri: URI::Generic,
31
32
  dispatcher: Prism::Dispatcher,
32
33
  ).void
33
34
  end
34
- def initialize(response_builder, uri, dispatcher)
35
+ def initialize(response_builder, global_state, uri, dispatcher)
35
36
  @response_builder = response_builder
37
+ @global_state = global_state
36
38
  @uri = T.let(uri, URI::Generic)
37
39
  @path = T.let(uri.to_standardized_path, T.nilable(String))
38
40
  # visibility_stack is a stack of [current_visibility, previous_visibility]
@@ -66,17 +68,22 @@ module RubyLsp
66
68
  command: generate_test_command(group_stack: @group_stack),
67
69
  kind: :group,
68
70
  )
69
- end
70
71
 
71
- @group_id_stack.push(@group_id)
72
- @group_id += 1
72
+ @group_id_stack.push(@group_id)
73
+ @group_id += 1
74
+ end
73
75
  end
74
76
 
75
77
  sig { params(node: Prism::ClassNode).void }
76
78
  def on_class_node_leave(node)
77
79
  @visibility_stack.pop
78
80
  @group_stack.pop
79
- @group_id_stack.pop
81
+
82
+ class_name = node.constant_path.slice
83
+
84
+ if @path && class_name.end_with?("Test")
85
+ @group_id_stack.pop
86
+ end
80
87
  end
81
88
 
82
89
  sig { params(node: Prism::DefNode).void }
@@ -156,7 +163,7 @@ module RubyLsp
156
163
  sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
157
164
  def add_test_code_lens(node, name:, command:, kind:)
158
165
  # don't add code lenses if the test library is not supported or unknown
159
- return unless SUPPORTED_TEST_LIBRARIES.include?(DependencyDetector.instance.detected_test_library) && @path
166
+ return unless SUPPORTED_TEST_LIBRARIES.include?(@global_state.test_library) && @path
160
167
 
161
168
  arguments = [
162
169
  @path,
@@ -208,7 +215,7 @@ module RubyLsp
208
215
  def generate_test_command(group_stack: [], spec_name: nil, method_name: nil)
209
216
  command = BASE_COMMAND + T.must(@path)
210
217
 
211
- case DependencyDetector.instance.detected_test_library
218
+ case @global_state.test_library
212
219
  when "minitest"
213
220
  last_dynamic_reference_index = group_stack.rindex(DYNAMIC_REFERENCE_MARKER)
214
221
  command += if last_dynamic_reference_index
@@ -264,12 +271,14 @@ module RubyLsp
264
271
 
265
272
  return unless name
266
273
 
267
- add_test_code_lens(
268
- node,
269
- name: name,
270
- command: generate_test_command(spec_name: name),
271
- kind: kind,
272
- )
274
+ if @path
275
+ add_test_code_lens(
276
+ node,
277
+ name: name,
278
+ command: generate_test_command(spec_name: name),
279
+ kind: kind,
280
+ )
281
+ end
273
282
  end
274
283
  end
275
284
  end
@@ -10,16 +10,17 @@ module RubyLsp
10
10
  sig do
11
11
  params(
12
12
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
13
- index: RubyIndexer::Index,
13
+ global_state: GlobalState,
14
14
  nesting: T::Array[String],
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  uri: URI::Generic,
18
18
  ).void
19
19
  end
20
- def initialize(response_builder, index, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
20
+ def initialize(response_builder, global_state, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
21
21
  @response_builder = response_builder
22
- @index = index
22
+ @global_state = global_state
23
+ @index = T.let(global_state.index, RubyIndexer::Index)
23
24
  @nesting = nesting
24
25
  @typechecker_enabled = typechecker_enabled
25
26
  @uri = uri
@@ -35,7 +36,7 @@ module RubyLsp
35
36
  # Handle completion on regular constant references (e.g. `Bar`)
36
37
  sig { params(node: Prism::ConstantReadNode).void }
37
38
  def on_constant_read_node_enter(node)
38
- return if DependencyDetector.instance.typechecker
39
+ return if @global_state.typechecker
39
40
 
40
41
  name = constant_name(node)
41
42
  return if name.nil?
@@ -56,7 +57,7 @@ module RubyLsp
56
57
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
57
58
  sig { params(node: Prism::ConstantPathNode).void }
58
59
  def on_constant_path_node_enter(node)
59
- return if DependencyDetector.instance.typechecker
60
+ return if @global_state.typechecker
60
61
 
61
62
  name = constant_name(node)
62
63
  return if name.nil?
@@ -77,7 +78,10 @@ module RubyLsp
77
78
 
78
79
  real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
79
80
 
80
- candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
81
+ candidates = @index.prefix_search(
82
+ "#{real_namespace}::#{incomplete_name}",
83
+ top_level_reference ? [] : @nesting,
84
+ )
81
85
  candidates.each do |entries|
82
86
  # The only time we may have a private constant reference from outside of the namespace is if we're dealing
83
87
  # with ConstantPath and the entry name doesn't start with the current nesting
@@ -124,7 +128,9 @@ module RubyLsp
124
128
 
125
129
  return unless path_node_to_complete.is_a?(Prism::StringNode)
126
130
 
127
- @index.search_require_paths(path_node_to_complete.content).map!(&:require_path).sort!.each do |path|
131
+ matched_indexable_paths = @index.search_require_paths(path_node_to_complete.content)
132
+
133
+ matched_indexable_paths.map!(&:require_path).sort!.each do |path|
128
134
  @response_builder << build_completion(T.must(path), path_node_to_complete)
129
135
  end
130
136
  end
@@ -284,13 +290,6 @@ module RubyLsp
284
290
  new_text: insertion_text,
285
291
  ),
286
292
  kind: kind,
287
- label_details: Interface::CompletionItemLabelDetails.new(
288
- description: entries.map(&:file_name).join(","),
289
- ),
290
- documentation: Interface::MarkupContent.new(
291
- kind: "markdown",
292
- value: markdown_from_index_entries(real_name, entries),
293
- ),
294
293
  )
295
294
  end
296
295
 
@@ -7,21 +7,24 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER = 10
11
+
10
12
  sig do
11
13
  params(
12
14
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
15
+ global_state: GlobalState,
13
16
  uri: URI::Generic,
14
17
  nesting: T::Array[String],
15
- index: RubyIndexer::Index,
16
18
  dispatcher: Prism::Dispatcher,
17
19
  typechecker_enabled: T::Boolean,
18
20
  ).void
19
21
  end
20
- def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
22
+ def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
21
23
  @response_builder = response_builder
24
+ @global_state = global_state
25
+ @index = T.let(global_state.index, RubyIndexer::Index)
22
26
  @uri = uri
23
27
  @nesting = nesting
24
- @index = index
25
28
  @typechecker_enabled = typechecker_enabled
26
29
 
27
30
  dispatcher.register(
@@ -63,12 +66,17 @@ module RubyLsp
63
66
 
64
67
  sig { params(node: Prism::CallNode).void }
65
68
  def handle_method_definition(node)
66
- return unless self_receiver?(node)
67
-
68
69
  message = node.message
69
70
  return unless message
70
71
 
71
- methods = @index.resolve_method(message, @nesting.join("::"))
72
+ methods = if self_receiver?(node)
73
+ @index.resolve_method(message, @nesting.join("::"))
74
+ else
75
+ # If the method doesn't have a receiver, then we provide a few candidates to jump to
76
+ # But we don't want to provide too many candidates, as it can be overwhelming
77
+ @index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
78
+ end
79
+
72
80
  return unless methods
73
81
 
74
82
  methods.each do |target_method|