ruby-lsp 0.14.6 → 0.15.0

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