ruby-lsp 0.14.6 → 0.15.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +1 -16
  4. data/exe/ruby-lsp-check +13 -22
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
  6. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  7. data/lib/ruby_lsp/addon.rb +18 -5
  8. data/lib/ruby_lsp/base_server.rb +147 -0
  9. data/lib/ruby_lsp/document.rb +0 -5
  10. data/lib/ruby_lsp/{requests/support/dependency_detector.rb → global_state.rb} +30 -9
  11. data/lib/ruby_lsp/internal.rb +2 -1
  12. data/lib/ruby_lsp/listeners/code_lens.rb +13 -9
  13. data/lib/ruby_lsp/listeners/completion.rb +13 -14
  14. data/lib/ruby_lsp/listeners/definition.rb +4 -3
  15. data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
  16. data/lib/ruby_lsp/listeners/hover.rb +6 -5
  17. data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
  18. data/lib/ruby_lsp/load_sorbet.rb +62 -0
  19. data/lib/ruby_lsp/requests/code_lens.rb +4 -3
  20. data/lib/ruby_lsp/requests/completion.rb +15 -4
  21. data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
  22. data/lib/ruby_lsp/requests/definition.rb +11 -4
  23. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  24. data/lib/ruby_lsp/requests/hover.rb +4 -4
  25. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  26. data/lib/ruby_lsp/requests/support/common.rb +16 -5
  27. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  28. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  29. data/lib/ruby_lsp/requests.rb +2 -0
  30. data/lib/ruby_lsp/server.rb +756 -142
  31. data/lib/ruby_lsp/store.rb +0 -8
  32. data/lib/ruby_lsp/test_helper.rb +45 -0
  33. data/lib/ruby_lsp/utils.rb +68 -33
  34. metadata +8 -5
  35. data/lib/ruby_lsp/executor.rb +0 -614
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c364aee0d80e6187f66e8eb6d50d7cdd21ffb129e0a0b17c9dc7bd5d8a18cad
4
- data.tar.gz: 65964ec1d89f10adf9bcbf113efed6d43a977b669abe51182e90dd9870032623
3
+ metadata.gz: 9749ddfd5f25d0f6e3c0636a070d8977c19af973943e33deba4e49cba9b473d6
4
+ data.tar.gz: 1c3f663fd6487e8517154a2595be253659e2f2f9bd6a4df23c8b16a91e159c4d
5
5
  SHA512:
6
- metadata.gz: 971af8a3c02903597143a8ec49915eead6bc0f65a0175e4a443e381d671dd4a88f776a26f62af1bd69ee7dc3f478b36bef122febbfaed7656907eb1a6a7a3354
7
- data.tar.gz: 15148c1c3765390f9bb92dbafe7043ac15b22de6b5c415c424a063f4742026d92831d84bcde2013ab742c4a889dc16d666d4ec5d18863d0db8acf793ce87f0a6
6
+ metadata.gz: '039fc58aa31af82930cc2852819208fdd9045ed493cf368702f87e9ffd814487bf63fe522124fa3210336cfc72d806b828e2e338114a66913dbd688ed85980c3'
7
+ data.tar.gz: bb4d62e3ce21fe07cda085f0284b69c356a10694125454b8160bd0c5e87789481b2d4f8036cdb4cdc2b0667c8a78c7e7a1409307722a532122172fcff8c5450c
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.14.6
1
+ 0.15.0
data/exe/ruby-lsp CHANGED
@@ -68,22 +68,7 @@ if ENV["BUNDLE_GEMFILE"].nil?
68
68
  exit exec(env, "bundle exec ruby-lsp #{original_args.join(" ")}")
69
69
  end
70
70
 
71
- require "sorbet-runtime"
72
-
73
- begin
74
- T::Configuration.default_checked_level = :never
75
- # Suppresses call validation errors
76
- T::Configuration.call_validation_error_handler = ->(*) {}
77
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
78
- T::Configuration.inline_type_error_handler = ->(*) {}
79
- # Suppresses errors caused by incorrect parameter ordering
80
- T::Configuration.sig_validation_error_handler = ->(*) {}
81
- rescue
82
- # Need this rescue so that if another gem has
83
- # already set the checked level by the time we
84
- # get to it, we don't fail outright.
85
- nil
86
- end
71
+ require "ruby_lsp/load_sorbet"
87
72
 
88
73
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
89
74
  require "ruby_lsp/internal"
data/exe/ruby-lsp-check CHANGED
@@ -3,16 +3,7 @@
3
3
 
4
4
  # This executable checks if all automatic LSP requests run successfully on every Ruby file under the current directory
5
5
 
6
- require "sorbet-runtime"
7
-
8
- begin
9
- T::Configuration.default_checked_level = :never
10
- T::Configuration.call_validation_error_handler = ->(*) {}
11
- T::Configuration.inline_type_error_handler = ->(*) {}
12
- T::Configuration.sig_validation_error_handler = ->(*) {}
13
- rescue
14
- nil
15
- end
6
+ require "ruby_lsp/load_sorbet"
16
7
 
17
8
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
18
9
  require "ruby_lsp/internal"
@@ -24,30 +15,30 @@ files = Dir.glob("#{Dir.pwd}/**/*.rb")
24
15
  puts "Verifying that all automatic LSP requests execute successfully. This may take a while..."
25
16
 
26
17
  errors = {}
27
- store = RubyLsp::Store.new
28
- message_queue = Thread::Queue.new
29
- RubyLsp::Addon.load_addons(message_queue)
30
- executor = RubyLsp::Executor.new(store, message_queue)
18
+ server = RubyLsp::Server.new(test_mode: true)
31
19
 
32
20
  files.each_with_index do |file, index|
33
21
  uri = URI("file://#{file}")
34
- store.set(uri: uri, source: File.read(file), version: 1)
22
+ server.process_message({
23
+ method: "textDocument/didOpen",
24
+ params: { textDocument: { uri: uri, text: File.read(file), version: 1 } },
25
+ })
35
26
 
36
27
  # Executing any of the automatic requests will execute all of them, so here we just pick one
37
- result = executor.execute({
28
+ server.process_message({
29
+ id: 1,
38
30
  method: "textDocument/documentSymbol",
39
- params: { textDocument: { uri: uri.to_s } },
31
+ params: { textDocument: { uri: uri } },
40
32
  })
41
33
 
42
- error = result.error
43
- errors[file] = error if error
34
+ result = server.pop_response
35
+ errors[file] = result.message if result.is_a?(RubyLsp::Error)
44
36
  ensure
45
- store.delete(uri)
37
+ server.process_message({ method: "textDocument/didClose", params: { textDocument: { uri: uri } } })
38
+ server.pop_response
46
39
  print("\033[M\033[0KCompleted #{index + 1}/#{files.length}") unless ENV["CI"]
47
40
  end
48
-
49
41
  puts "\n"
50
- message_queue.close
51
42
 
52
43
  # Indexing
53
44
  puts "Verifying that indexing executes successfully. This may take a while..."
@@ -43,20 +43,6 @@ module RubyIndexer
43
43
  )
44
44
  end
45
45
 
46
- sig { void }
47
- def load_config
48
- return unless File.exist?(".index.yml")
49
-
50
- config = YAML.parse_file(".index.yml")
51
- return unless config
52
-
53
- config_hash = config.to_ruby
54
- validate_config!(config_hash)
55
- apply_config(config_hash)
56
- rescue Psych::SyntaxError => e
57
- raise e, "Syntax error while loading .index.yml configuration: #{e.message}"
58
- end
59
-
60
46
  sig { returns(T::Array[IndexablePath]) }
61
47
  def indexables
62
48
  excluded_gems = @excluded_gems - @included_gems
@@ -158,6 +144,17 @@ module RubyIndexer
158
144
  @magic_comment_regex ||= T.let(/^#\s*#{@excluded_magic_comments.join("|")}/, T.nilable(Regexp))
159
145
  end
160
146
 
147
+ sig { params(config: T::Hash[String, T.untyped]).void }
148
+ def apply_config(config)
149
+ validate_config!(config)
150
+
151
+ @excluded_gems.concat(config["excluded_gems"]) if config["excluded_gems"]
152
+ @included_gems.concat(config["included_gems"]) if config["included_gems"]
153
+ @excluded_patterns.concat(config["excluded_patterns"]) if config["excluded_patterns"]
154
+ @included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
155
+ @excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
156
+ end
157
+
161
158
  private
162
159
 
163
160
  sig { params(config: T::Hash[String, T.untyped]).void }
@@ -175,15 +172,6 @@ module RubyIndexer
175
172
  raise ArgumentError, errors.join("\n") if errors.any?
176
173
  end
177
174
 
178
- sig { params(config: T::Hash[String, T.untyped]).void }
179
- def apply_config(config)
180
- @excluded_gems.concat(config["excluded_gems"]) if config["excluded_gems"]
181
- @included_gems.concat(config["included_gems"]) if config["included_gems"]
182
- @excluded_patterns.concat(config["excluded_patterns"]) if config["excluded_patterns"]
183
- @included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
184
- @excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
185
- end
186
-
187
175
  sig { returns(T::Array[String]) }
188
176
  def initial_excluded_gems
189
177
  excluded, others = Bundler.definition.dependencies.partition do |dependency|
@@ -10,7 +10,7 @@ module RubyIndexer
10
10
  end
11
11
 
12
12
  def test_load_configuration_executes_configure_block
13
- @config.load_config
13
+ @config.apply_config({ "excluded_patterns" => ["**/test/fixtures/**/*.rb"] })
14
14
  indexables = @config.indexables
15
15
 
16
16
  assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
@@ -21,7 +21,6 @@ module RubyIndexer
21
21
  end
22
22
 
23
23
  def test_indexables_only_includes_gem_require_paths
24
- @config.load_config
25
24
  indexables = @config.indexables
26
25
 
27
26
  Bundler.locked_gems.specs.each do |lazy_spec|
@@ -35,7 +34,6 @@ module RubyIndexer
35
34
  end
36
35
 
37
36
  def test_indexables_does_not_include_default_gem_path_when_in_bundle
38
- @config.load_config
39
37
  indexables = @config.indexables
40
38
 
41
39
  assert(
@@ -44,7 +42,6 @@ module RubyIndexer
44
42
  end
45
43
 
46
44
  def test_indexables_includes_default_gems
47
- @config.load_config
48
45
  indexables = @config.indexables.map(&:full_path)
49
46
 
50
47
  assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
@@ -53,7 +50,6 @@ module RubyIndexer
53
50
  end
54
51
 
55
52
  def test_indexables_includes_project_files
56
- @config.load_config
57
53
  indexables = @config.indexables.map(&:full_path)
58
54
 
59
55
  Dir.glob("#{Dir.pwd}/lib/**/*.rb").each do |path|
@@ -66,7 +62,6 @@ module RubyIndexer
66
62
  def test_indexables_avoids_duplicates_if_bundle_path_is_inside_project
67
63
  Bundler.settings.set_global("path", "vendor/bundle")
68
64
  config = Configuration.new
69
- config.load_config
70
65
 
71
66
  assert_includes(config.instance_variable_get(:@excluded_patterns), "#{Dir.pwd}/vendor/bundle/**/*.rb")
72
67
  ensure
@@ -74,7 +69,6 @@ module RubyIndexer
74
69
  end
75
70
 
76
71
  def test_indexables_does_not_include_gems_own_installed_files
77
- @config.load_config
78
72
  indexables = @config.indexables
79
73
 
80
74
  assert(
@@ -95,17 +89,14 @@ module RubyIndexer
95
89
  end
96
90
 
97
91
  def test_paths_are_unique
98
- @config.load_config
99
92
  indexables = @config.indexables
100
93
 
101
94
  assert_equal(indexables.uniq.length, indexables.length)
102
95
  end
103
96
 
104
97
  def test_configuration_raises_for_unknown_keys
105
- Psych::Nodes::Document.any_instance.expects(:to_ruby).returns({ "unknown_config" => 123 })
106
-
107
98
  assert_raises(ArgumentError) do
108
- @config.load_config
99
+ @config.apply_config({ "unknown_config" => 123 })
109
100
  end
110
101
  end
111
102
 
@@ -121,22 +121,23 @@ module RubyLsp
121
121
  sig do
122
122
  overridable.params(
123
123
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
124
+ global_state: GlobalState,
124
125
  uri: URI::Generic,
125
126
  dispatcher: Prism::Dispatcher,
126
127
  ).void
127
128
  end
128
- def create_code_lens_listener(response_builder, uri, dispatcher); end
129
+ def create_code_lens_listener(response_builder, global_state, uri, dispatcher); end
129
130
 
130
131
  # Creates a new Hover listener. This method is invoked on every Hover request
131
132
  sig do
132
133
  overridable.params(
133
134
  response_builder: ResponseBuilders::Hover,
135
+ global_state: GlobalState,
134
136
  nesting: T::Array[String],
135
- index: RubyIndexer::Index,
136
137
  dispatcher: Prism::Dispatcher,
137
138
  ).void
138
139
  end
139
- def create_hover_listener(response_builder, nesting, index, dispatcher); end
140
+ def create_hover_listener(response_builder, global_state, nesting, dispatcher); end
140
141
 
141
142
  # Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
142
143
  sig do
@@ -159,12 +160,24 @@ module RubyLsp
159
160
  sig do
160
161
  overridable.params(
161
162
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
163
+ global_state: GlobalState,
162
164
  uri: URI::Generic,
163
165
  nesting: T::Array[String],
164
- index: RubyIndexer::Index,
165
166
  dispatcher: Prism::Dispatcher,
166
167
  ).void
167
168
  end
168
- def create_definition_listener(response_builder, uri, nesting, index, dispatcher); end
169
+ def create_definition_listener(response_builder, global_state, uri, nesting, dispatcher); end
170
+
171
+ # Creates a new Completion listener. This method is invoked on every Completion request
172
+ sig do
173
+ overridable.params(
174
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
175
+ global_state: GlobalState,
176
+ nesting: T::Array[String],
177
+ dispatcher: Prism::Dispatcher,
178
+ uri: URI::Generic,
179
+ ).void
180
+ end
181
+ def create_completion_listener(response_builder, global_state, nesting, dispatcher, uri); end
169
182
  end
170
183
  end
@@ -0,0 +1,147 @@
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
+ # We need to process shutdown and exit from the main thread in order to close queues and wait for other threads
63
+ # to finish. Everything else is pushed into the incoming queue
64
+ case method
65
+ when "shutdown"
66
+ $stderr.puts("Shutting down Ruby LSP...")
67
+
68
+ shutdown
69
+
70
+ @mutex.synchronize do
71
+ run_shutdown
72
+ @writer.write(Result.new(id: message[:id], response: nil).to_hash)
73
+ end
74
+ when "exit"
75
+ @mutex.synchronize do
76
+ status = @incoming_queue.closed? ? 0 : 1
77
+ $stderr.puts("Shutdown complete with status #{status}")
78
+ exit(status)
79
+ end
80
+ else
81
+ @incoming_queue << message
82
+ end
83
+ end
84
+ end
85
+
86
+ sig { void }
87
+ def run_shutdown
88
+ @incoming_queue.clear
89
+ @outgoing_queue.clear
90
+ @incoming_queue.close
91
+ @outgoing_queue.close
92
+ @cancelled_requests.clear
93
+
94
+ @worker.join
95
+ @outgoing_dispatcher.join
96
+ @store.clear
97
+ end
98
+
99
+ # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
100
+ sig { returns(T.untyped) }
101
+ def pop_response
102
+ @outgoing_queue.pop
103
+ end
104
+
105
+ sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
106
+ def process_message(message); end
107
+
108
+ sig { abstract.void }
109
+ def shutdown; end
110
+
111
+ sig { returns(Thread) }
112
+ def new_worker
113
+ Thread.new do
114
+ while (message = T.let(@incoming_queue.pop, T.nilable(T::Hash[Symbol, T.untyped])))
115
+ id = message[:id]
116
+
117
+ # Check if the request was cancelled before trying to process it
118
+ @mutex.synchronize do
119
+ if id && @cancelled_requests.include?(id)
120
+ send_message(Result.new(id: id, response: nil))
121
+ @cancelled_requests.delete(id)
122
+ next
123
+ end
124
+ end
125
+
126
+ process_message(message)
127
+ end
128
+ end
129
+ end
130
+
131
+ sig { params(message: T.any(Result, Error, Notification, Request)).void }
132
+ def send_message(message)
133
+ # When we're shutting down the server, there's a small race condition between closing the thread queues and
134
+ # finishing remaining requests. We may close the queue in the middle of processing a request, which will then fail
135
+ # when trying to send a response back
136
+ return if @outgoing_queue.closed?
137
+
138
+ @outgoing_queue << message
139
+ @current_request_id += 1 if message.is_a?(Request)
140
+ end
141
+
142
+ sig { params(id: Integer).void }
143
+ def send_empty_response(id)
144
+ send_message(Result.new(id: id, response: nil))
145
+ end
146
+ end
147
+ end
@@ -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
 
@@ -1,27 +1,41 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "singleton"
5
-
6
4
  module RubyLsp
7
- class DependencyDetector
8
- include Singleton
5
+ class GlobalState
9
6
  extend T::Sig
10
7
 
11
8
  sig { returns(String) }
12
- attr_reader :detected_formatter
9
+ attr_reader :test_library
13
10
 
14
11
  sig { returns(String) }
15
- attr_reader :detected_test_library
12
+ attr_accessor :formatter
16
13
 
17
14
  sig { returns(T::Boolean) }
18
15
  attr_reader :typechecker
19
16
 
17
+ sig { returns(RubyIndexer::Index) }
18
+ attr_reader :index
19
+
20
20
  sig { void }
21
21
  def initialize
22
- @detected_formatter = T.let(detect_formatter, String)
23
- @detected_test_library = T.let(detect_test_library, String)
22
+ @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
23
+
24
+ @formatter = T.let(detect_formatter, String)
25
+ @test_library = T.let(detect_test_library, String)
24
26
  @typechecker = T.let(detect_typechecker, T::Boolean)
27
+ @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
28
+ end
29
+
30
+ sig { params(options: T::Hash[Symbol, T.untyped]).void }
31
+ def apply_options(options)
32
+ workspace_uri = options.dig(:workspaceFolders, 0, :uri)
33
+ @workspace_uri = URI(workspace_uri) if workspace_uri
34
+ end
35
+
36
+ sig { returns(String) }
37
+ def workspace_path
38
+ T.must(@workspace_uri.to_standardized_path)
25
39
  end
26
40
 
27
41
  sig { returns(String) }
@@ -63,8 +77,15 @@ module RubyLsp
63
77
  def detect_typechecker
64
78
  return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
65
79
 
80
+ # We can't read the env from within `Bundle.with_original_env` so we need to set it here.
81
+ ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
66
82
  Bundler.with_original_env do
67
- Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
83
+ sorbet_static_detected = Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
84
+ # Don't show message while running tests, since it's noisy
85
+ if sorbet_static_detected && !ruby_lsp_env_is_test
86
+ $stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
87
+ end
88
+ sorbet_static_detected
68
89
  end
69
90
  rescue Bundler::GemfileNotFound
70
91
  false
@@ -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"
@@ -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]
@@ -156,7 +158,7 @@ module RubyLsp
156
158
  sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
157
159
  def add_test_code_lens(node, name:, command:, kind:)
158
160
  # 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
161
+ return unless SUPPORTED_TEST_LIBRARIES.include?(@global_state.test_library) && @path
160
162
 
161
163
  arguments = [
162
164
  @path,
@@ -208,7 +210,7 @@ module RubyLsp
208
210
  def generate_test_command(group_stack: [], spec_name: nil, method_name: nil)
209
211
  command = BASE_COMMAND + T.must(@path)
210
212
 
211
- case DependencyDetector.instance.detected_test_library
213
+ case @global_state.test_library
212
214
  when "minitest"
213
215
  last_dynamic_reference_index = group_stack.rindex(DYNAMIC_REFERENCE_MARKER)
214
216
  command += if last_dynamic_reference_index
@@ -264,12 +266,14 @@ module RubyLsp
264
266
 
265
267
  return unless name
266
268
 
267
- add_test_code_lens(
268
- node,
269
- name: name,
270
- command: generate_test_command(spec_name: name),
271
- kind: kind,
272
- )
269
+ if @path
270
+ add_test_code_lens(
271
+ node,
272
+ name: name,
273
+ command: generate_test_command(spec_name: name),
274
+ kind: kind,
275
+ )
276
+ end
273
277
  end
274
278
  end
275
279
  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
 
@@ -10,18 +10,19 @@ module RubyLsp
10
10
  sig do
11
11
  params(
12
12
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
13
+ global_state: GlobalState,
13
14
  uri: URI::Generic,
14
15
  nesting: T::Array[String],
15
- index: RubyIndexer::Index,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  typechecker_enabled: T::Boolean,
18
18
  ).void
19
19
  end
20
- def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
20
+ def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
21
21
  @response_builder = response_builder
22
+ @global_state = global_state
23
+ @index = T.let(global_state.index, RubyIndexer::Index)
22
24
  @uri = uri
23
25
  @nesting = nesting
24
- @index = index
25
26
  @typechecker_enabled = typechecker_enabled
26
27
 
27
28
  dispatcher.register(