ruby-lsp 0.14.6 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
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|