ruby-lsp 0.14.6 → 0.16.4
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/README.md +1 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +1 -16
- data/exe/ruby-lsp-check +13 -22
- data/exe/ruby-lsp-doctor +9 -0
- data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +14 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +4 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
- data/lib/ruby_indexer/test/configuration_test.rb +2 -11
- data/lib/ruby_lsp/addon.rb +18 -9
- data/lib/ruby_lsp/base_server.rb +149 -0
- data/lib/ruby_lsp/document.rb +6 -11
- data/lib/ruby_lsp/global_state.rb +169 -0
- data/lib/ruby_lsp/internal.rb +4 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +22 -13
- 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 +3 -2
- 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/diagnostics.rb +6 -12
- data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
- data/lib/ruby_lsp/requests/formatting.rb +7 -43
- data/lib/ruby_lsp/requests/hover.rb +4 -4
- data/lib/ruby_lsp/requests/request.rb +2 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- 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/formatter.rb +26 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +47 -0
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/support/{syntax_tree_formatting_runner.rb → syntax_tree_formatter.rb} +13 -6
- data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
- data/lib/ruby_lsp/requests.rb +3 -1
- data/lib/ruby_lsp/server.rb +763 -142
- data/lib/ruby_lsp/setup_bundler.rb +13 -1
- data/lib/ruby_lsp/store.rb +3 -15
- data/lib/ruby_lsp/test_helper.rb +52 -0
- data/lib/ruby_lsp/utils.rb +68 -33
- metadata +11 -9
- data/lib/ruby_lsp/executor.rb +0 -614
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -93
- data/lib/ruby_lsp/requests/support/formatter_runner.rb +0 -18
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -34
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94d4dfcda701bdf2ea1dd04e2a271572a139fe3d716e1bd535e2c05bcc9b3d23
|
4
|
+
data.tar.gz: b79dfc816bf027e733fa5f44db3e2d1039ae860f9dee88fe996ab1a27089d467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9de1f73feba2284c75294542a0e9ae1196f092004fe83ef815b2e08a1b404daceebc0b3f6f8f59149f936c8f7414821942556bee522776a909c071765dcc3443
|
7
|
+
data.tar.gz: b7617a6d508a94e5a06b8d39acb4d2bcc28d02832b19ab8567ed58b6b297aa3fc450dd0a116257fe91fc9545683550456c0d712fc32dc3f3ff716e2cdca2985e
|
data/README.md
CHANGED
@@ -29,7 +29,7 @@ The Ruby LSP features include
|
|
29
29
|
- Debugging support
|
30
30
|
- Running and debugging tests through VS Code's UI
|
31
31
|
- Go to definition for classes, modules, constants and required files
|
32
|
-
- Showing
|
32
|
+
- Showing documentation on hover for classes, modules and constants
|
33
33
|
- Completion for classes, modules, constants and require paths
|
34
34
|
- Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
|
35
35
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.16.4
|
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..."
|
data/exe/ruby-lsp-doctor
CHANGED
@@ -4,6 +4,15 @@
|
|
4
4
|
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
5
5
|
require "ruby_lsp/internal"
|
6
6
|
|
7
|
+
if File.exist?(".index.yml")
|
8
|
+
begin
|
9
|
+
config = YAML.parse_file(".index.yml").to_ruby
|
10
|
+
rescue => e
|
11
|
+
abort("Error parsing config: #{e.message}")
|
12
|
+
end
|
13
|
+
RubyIndexer.configuration.apply_config(config)
|
14
|
+
end
|
15
|
+
|
7
16
|
index = RubyIndexer::Index.new
|
8
17
|
|
9
18
|
puts "Globbing for indexable files"
|
@@ -154,6 +154,8 @@ module RubyIndexer
|
|
154
154
|
handle_attribute(node, reader: true, writer: true)
|
155
155
|
when :include
|
156
156
|
handle_include(node)
|
157
|
+
when :prepend
|
158
|
+
handle_prepend(node)
|
157
159
|
end
|
158
160
|
end
|
159
161
|
|
@@ -355,6 +357,16 @@ module RubyIndexer
|
|
355
357
|
|
356
358
|
sig { params(node: Prism::CallNode).void }
|
357
359
|
def handle_include(node)
|
360
|
+
handle_module_operation(node, :included_modules)
|
361
|
+
end
|
362
|
+
|
363
|
+
sig { params(node: Prism::CallNode).void }
|
364
|
+
def handle_prepend(node)
|
365
|
+
handle_module_operation(node, :prepended_modules)
|
366
|
+
end
|
367
|
+
|
368
|
+
sig { params(node: Prism::CallNode, operation: Symbol).void }
|
369
|
+
def handle_module_operation(node, operation)
|
358
370
|
return unless @current_owner
|
359
371
|
|
360
372
|
arguments = node.arguments&.arguments
|
@@ -369,7 +381,8 @@ module RubyIndexer
|
|
369
381
|
# If a constant path reference is dynamic or missing parts, we can't
|
370
382
|
# index it
|
371
383
|
end
|
372
|
-
@current_owner.included_modules.
|
384
|
+
collection = operation == :included_modules ? @current_owner.included_modules : @current_owner.prepended_modules
|
385
|
+
collection.concat(names)
|
373
386
|
end
|
374
387
|
end
|
375
388
|
end
|
@@ -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|
|
@@ -43,6 +43,9 @@ module RubyIndexer
|
|
43
43
|
sig { returns(T::Array[String]) }
|
44
44
|
attr_accessor :included_modules
|
45
45
|
|
46
|
+
sig { returns(T::Array[String]) }
|
47
|
+
attr_accessor :prepended_modules
|
48
|
+
|
46
49
|
sig do
|
47
50
|
params(
|
48
51
|
name: String,
|
@@ -54,6 +57,7 @@ module RubyIndexer
|
|
54
57
|
def initialize(name, file_path, location, comments)
|
55
58
|
super(name, file_path, location, comments)
|
56
59
|
@included_modules = T.let([], T::Array[String])
|
60
|
+
@prepended_modules = T.let([], T::Array[String])
|
57
61
|
end
|
58
62
|
|
59
63
|
sig { returns(String) }
|
@@ -327,5 +327,51 @@ module RubyIndexer
|
|
327
327
|
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
328
328
|
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.included_modules)
|
329
329
|
end
|
330
|
+
|
331
|
+
def test_keeping_track_of_prepended_modules
|
332
|
+
index(<<~RUBY)
|
333
|
+
class Foo
|
334
|
+
# valid syntaxes that we can index
|
335
|
+
prepend A1
|
336
|
+
self.prepend A2
|
337
|
+
prepend A3, A4
|
338
|
+
self.prepend A5, A6
|
339
|
+
|
340
|
+
# valid syntaxes that we cannot index because of their dynamic nature
|
341
|
+
prepend some_variable_or_method_call
|
342
|
+
self.prepend some_variable_or_method_call
|
343
|
+
|
344
|
+
def something
|
345
|
+
prepend A7 # We should not index this because of this dynamic nature
|
346
|
+
end
|
347
|
+
|
348
|
+
# Valid inner class syntax definition with its own modules prepended
|
349
|
+
class Qux
|
350
|
+
prepend Corge
|
351
|
+
self.prepend Corge
|
352
|
+
prepend Baz
|
353
|
+
|
354
|
+
prepend some_variable_or_method_call
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class ConstantPathReferences
|
359
|
+
prepend Foo::Bar
|
360
|
+
self.prepend Foo::Bar2
|
361
|
+
|
362
|
+
prepend dynamic::Bar
|
363
|
+
prepend Foo::
|
364
|
+
end
|
365
|
+
RUBY
|
366
|
+
|
367
|
+
foo = T.must(@index["Foo"][0])
|
368
|
+
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.prepended_modules)
|
369
|
+
|
370
|
+
qux = T.must(@index["Foo::Qux"][0])
|
371
|
+
assert_equal(["Corge", "Corge", "Baz"], qux.prepended_modules)
|
372
|
+
|
373
|
+
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
374
|
+
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.prepended_modules)
|
375
|
+
end
|
330
376
|
end
|
331
377
|
end
|
@@ -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
@@ -50,8 +50,8 @@ module RubyLsp
|
|
50
50
|
end
|
51
51
|
|
52
52
|
# Discovers and loads all addons. Returns the list of activated addons
|
53
|
-
sig { params(
|
54
|
-
def load_addons(
|
53
|
+
sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[Addon]) }
|
54
|
+
def load_addons(global_state, outgoing_queue)
|
55
55
|
# Require all addons entry points, which should be placed under
|
56
56
|
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
57
57
|
Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
|
@@ -67,7 +67,7 @@ module RubyLsp
|
|
67
67
|
# Activate each one of the discovered addons. If any problems occur in the addons, we don't want to
|
68
68
|
# fail to boot the server
|
69
69
|
addons.each do |addon|
|
70
|
-
addon.activate(
|
70
|
+
addon.activate(global_state, outgoing_queue)
|
71
71
|
rescue => e
|
72
72
|
addon.add_error(e)
|
73
73
|
end
|
@@ -105,8 +105,8 @@ module RubyLsp
|
|
105
105
|
|
106
106
|
# Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
107
107
|
# reading information into memory or even spawning a separate process
|
108
|
-
sig { abstract.params(
|
109
|
-
def activate(
|
108
|
+
sig { abstract.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
|
109
|
+
def activate(global_state, outgoing_queue); end
|
110
110
|
|
111
111
|
# Each addon should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
|
112
112
|
# child process
|
@@ -132,11 +132,10 @@ module RubyLsp
|
|
132
132
|
overridable.params(
|
133
133
|
response_builder: ResponseBuilders::Hover,
|
134
134
|
nesting: T::Array[String],
|
135
|
-
index: RubyIndexer::Index,
|
136
135
|
dispatcher: Prism::Dispatcher,
|
137
136
|
).void
|
138
137
|
end
|
139
|
-
def create_hover_listener(response_builder, nesting,
|
138
|
+
def create_hover_listener(response_builder, nesting, dispatcher); end
|
140
139
|
|
141
140
|
# Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
|
142
141
|
sig do
|
@@ -161,10 +160,20 @@ module RubyLsp
|
|
161
160
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
162
161
|
uri: URI::Generic,
|
163
162
|
nesting: T::Array[String],
|
164
|
-
index: RubyIndexer::Index,
|
165
163
|
dispatcher: Prism::Dispatcher,
|
166
164
|
).void
|
167
165
|
end
|
168
|
-
def create_definition_listener(response_builder, uri, nesting,
|
166
|
+
def create_definition_listener(response_builder, uri, nesting, dispatcher); end
|
167
|
+
|
168
|
+
# Creates a new Completion listener. This method is invoked on every Completion request
|
169
|
+
sig do
|
170
|
+
overridable.params(
|
171
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
172
|
+
nesting: T::Array[String],
|
173
|
+
dispatcher: Prism::Dispatcher,
|
174
|
+
uri: URI::Generic,
|
175
|
+
).void
|
176
|
+
end
|
177
|
+
def create_completion_listener(response_builder, nesting, dispatcher, uri); end
|
169
178
|
end
|
170
179
|
end
|
@@ -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", "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
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -20,13 +20,13 @@ module RubyLsp
|
|
20
20
|
sig { returns(URI::Generic) }
|
21
21
|
attr_reader :uri
|
22
22
|
|
23
|
-
sig { returns(
|
23
|
+
sig { returns(Encoding) }
|
24
24
|
attr_reader :encoding
|
25
25
|
|
26
|
-
sig { params(source: String, version: Integer, uri: URI::Generic, encoding:
|
27
|
-
def initialize(source:, version:, uri:, encoding:
|
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,
|
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:
|
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 ==
|
212
|
+
if @encoding == Encoding::UTF_16LE
|
218
213
|
requested_position -= utf_16_character_position_correction(@pos, requested_position)
|
219
214
|
end
|
220
215
|
|