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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +1 -16
- data/exe/ruby-lsp-check +13 -22
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
- data/lib/ruby_indexer/test/configuration_test.rb +2 -11
- data/lib/ruby_lsp/addon.rb +18 -5
- data/lib/ruby_lsp/base_server.rb +147 -0
- data/lib/ruby_lsp/document.rb +0 -5
- data/lib/ruby_lsp/{requests/support/dependency_detector.rb → global_state.rb} +30 -9
- data/lib/ruby_lsp/internal.rb +2 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +13 -9
- data/lib/ruby_lsp/listeners/completion.rb +13 -14
- data/lib/ruby_lsp/listeners/definition.rb +4 -3
- data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
- data/lib/ruby_lsp/listeners/hover.rb +6 -5
- data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
- data/lib/ruby_lsp/load_sorbet.rb +62 -0
- data/lib/ruby_lsp/requests/code_lens.rb +4 -3
- data/lib/ruby_lsp/requests/completion.rb +15 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
- data/lib/ruby_lsp/requests/definition.rb +11 -4
- data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
- data/lib/ruby_lsp/requests/hover.rb +4 -4
- data/lib/ruby_lsp/requests/signature_help.rb +4 -3
- data/lib/ruby_lsp/requests/support/common.rb +16 -5
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +756 -142
- data/lib/ruby_lsp/store.rb +0 -8
- data/lib/ruby_lsp/test_helper.rb +45 -0
- data/lib/ruby_lsp/utils.rb +68 -33
- metadata +8 -5
- data/lib/ruby_lsp/executor.rb +0 -614
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9749ddfd5f25d0f6e3c0636a070d8977c19af973943e33deba4e49cba9b473d6
|
4
|
+
data.tar.gz: 1c3f663fd6487e8517154a2595be253659e2f2f9bd6a4df23c8b16a91e159c4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '039fc58aa31af82930cc2852819208fdd9045ed493cf368702f87e9ffd814487bf63fe522124fa3210336cfc72d806b828e2e338114a66913dbd688ed85980c3'
|
7
|
+
data.tar.gz: bb4d62e3ce21fe07cda085f0284b69c356a10694125454b8160bd0c5e87789481b2d4f8036cdb4cdc2b0667c8a78c7e7a1409307722a532122172fcff8c5450c
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
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 "
|
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 "
|
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
|
-
|
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
|
-
|
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
|
-
|
28
|
+
server.process_message({
|
29
|
+
id: 1,
|
38
30
|
method: "textDocument/documentSymbol",
|
39
|
-
params: { textDocument: { uri: uri
|
31
|
+
params: { textDocument: { uri: uri } },
|
40
32
|
})
|
41
33
|
|
42
|
-
|
43
|
-
errors[file] =
|
34
|
+
result = server.pop_response
|
35
|
+
errors[file] = result.message if result.is_a?(RubyLsp::Error)
|
44
36
|
ensure
|
45
|
-
|
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.
|
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.
|
99
|
+
@config.apply_config({ "unknown_config" => 123 })
|
109
100
|
end
|
110
101
|
end
|
111
102
|
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -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,
|
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,
|
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
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -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
|
8
|
-
include Singleton
|
5
|
+
class GlobalState
|
9
6
|
extend T::Sig
|
10
7
|
|
11
8
|
sig { returns(String) }
|
12
|
-
attr_reader :
|
9
|
+
attr_reader :test_library
|
13
10
|
|
14
11
|
sig { returns(String) }
|
15
|
-
|
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
|
-
@
|
23
|
-
|
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
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -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?(
|
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
|
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
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
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,
|
20
|
+
def initialize(response_builder, global_state, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
|
21
21
|
@response_builder = response_builder
|
22
|
-
@
|
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
|
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
|
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(
|
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)
|
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,
|
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(
|