ruby-lsp 0.8.0 → 0.8.1
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 +41 -33
- data/lib/core_ext/uri.rb +6 -11
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
- data/lib/ruby_indexer/ruby_indexer.rb +19 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
- data/lib/ruby_indexer/test/configuration_test.rb +35 -0
- data/lib/ruby_indexer/test/constant_test.rb +108 -0
- data/lib/ruby_indexer/test/index_test.rb +94 -0
- data/lib/ruby_indexer/test/test_case.rb +42 -0
- data/lib/ruby_lsp/executor.rb +110 -6
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/requests/definition.rb +51 -4
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
- data/lib/ruby_lsp/setup_bundler.rb +9 -5
- data/lib/ruby_lsp/store.rb +15 -9
- metadata +26 -3
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class ConfigurationTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@config = Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_load_configuration_executes_configure_block
|
13
|
+
@config.load_config
|
14
|
+
files_to_index = @config.files_to_index
|
15
|
+
|
16
|
+
assert(files_to_index.none? { |path| path.include?("test/fixtures") })
|
17
|
+
assert(files_to_index.none? { |path| path.include?("minitest-reporters") })
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_paths_are_unique
|
21
|
+
@config.load_config
|
22
|
+
files_to_index = @config.files_to_index
|
23
|
+
|
24
|
+
assert_equal(files_to_index.uniq.length, files_to_index.length)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_configuration_raises_for_unknown_keys
|
28
|
+
Psych::Nodes::Document.any_instance.expects(:to_ruby).returns({ "unknown_config" => 123 })
|
29
|
+
|
30
|
+
assert_raises(ArgumentError) do
|
31
|
+
@config.load_config
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class ConstantTest < TestCase
|
8
|
+
def test_constant_writes
|
9
|
+
index(<<~RUBY)
|
10
|
+
FOO = 1
|
11
|
+
|
12
|
+
class ::Bar
|
13
|
+
FOO = 2
|
14
|
+
end
|
15
|
+
RUBY
|
16
|
+
|
17
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-6")
|
18
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-8")
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_constant_or_writes
|
22
|
+
index(<<~RUBY)
|
23
|
+
FOO ||= 1
|
24
|
+
|
25
|
+
class ::Bar
|
26
|
+
FOO ||= 2
|
27
|
+
end
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-8")
|
31
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-10")
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_constant_path_writes
|
35
|
+
index(<<~RUBY)
|
36
|
+
class A
|
37
|
+
FOO = 1
|
38
|
+
::BAR = 1
|
39
|
+
|
40
|
+
module B
|
41
|
+
FOO = 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
A::BAZ = 1
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-8")
|
49
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-10")
|
50
|
+
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-10")
|
51
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-9")
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_constant_path_or_writes
|
55
|
+
index(<<~RUBY)
|
56
|
+
class A
|
57
|
+
FOO ||= 1
|
58
|
+
::BAR ||= 1
|
59
|
+
end
|
60
|
+
|
61
|
+
A::BAZ ||= 1
|
62
|
+
RUBY
|
63
|
+
|
64
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-10")
|
65
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-12")
|
66
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-11")
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_comments_for_constants
|
70
|
+
index(<<~RUBY)
|
71
|
+
# FOO comment
|
72
|
+
FOO = 1
|
73
|
+
|
74
|
+
class A
|
75
|
+
# A::FOO comment
|
76
|
+
FOO = 1
|
77
|
+
|
78
|
+
# ::BAR comment
|
79
|
+
::BAR = 1
|
80
|
+
end
|
81
|
+
|
82
|
+
# A::BAZ comment
|
83
|
+
A::BAZ = 1
|
84
|
+
RUBY
|
85
|
+
|
86
|
+
foo_comment = @index["FOO"].first.comments.join("\n")
|
87
|
+
assert_equal("# FOO comment\n", foo_comment)
|
88
|
+
|
89
|
+
a_foo_comment = @index["A::FOO"].first.comments.join("\n")
|
90
|
+
assert_equal("# A::FOO comment\n", a_foo_comment)
|
91
|
+
|
92
|
+
bar_comment = @index["BAR"].first.comments.join("\n")
|
93
|
+
assert_equal("# ::BAR comment\n", bar_comment)
|
94
|
+
|
95
|
+
a_baz_comment = @index["A::BAZ"].first.comments.join("\n")
|
96
|
+
assert_equal("# A::BAZ comment\n", a_baz_comment)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_variable_path_constants_are_ignored
|
100
|
+
index(<<~RUBY)
|
101
|
+
var::FOO = 1
|
102
|
+
self.class::FOO = 1
|
103
|
+
RUBY
|
104
|
+
|
105
|
+
assert_no_entry
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class IndexTest < TestCase
|
8
|
+
def test_deleting_one_entry_for_a_class
|
9
|
+
@index.index_single("/fake/path/foo.rb", <<~RUBY)
|
10
|
+
class Foo
|
11
|
+
end
|
12
|
+
RUBY
|
13
|
+
@index.index_single("/fake/path/other_foo.rb", <<~RUBY)
|
14
|
+
class Foo
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
|
18
|
+
entries = @index["Foo"]
|
19
|
+
assert_equal(2, entries.length)
|
20
|
+
|
21
|
+
@index.delete("/fake/path/other_foo.rb")
|
22
|
+
entries = @index["Foo"]
|
23
|
+
assert_equal(1, entries.length)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_deleting_all_entries_for_a_class
|
27
|
+
@index.index_single("/fake/path/foo.rb", <<~RUBY)
|
28
|
+
class Foo
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
|
32
|
+
entries = @index["Foo"]
|
33
|
+
assert_equal(1, entries.length)
|
34
|
+
|
35
|
+
@index.delete("/fake/path/foo.rb")
|
36
|
+
entries = @index["Foo"]
|
37
|
+
assert_nil(entries)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_index_resolve
|
41
|
+
@index.index_single("/fake/path/foo.rb", <<~RUBY)
|
42
|
+
class Bar; end
|
43
|
+
|
44
|
+
module Foo
|
45
|
+
class Bar
|
46
|
+
end
|
47
|
+
|
48
|
+
class Baz
|
49
|
+
class Something
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
RUBY
|
54
|
+
|
55
|
+
entries = @index.resolve("Something", ["Foo", "Baz"])
|
56
|
+
refute_empty(entries)
|
57
|
+
assert_equal("Foo::Baz::Something", entries.first.name)
|
58
|
+
|
59
|
+
entries = @index.resolve("Bar", ["Foo"])
|
60
|
+
refute_empty(entries)
|
61
|
+
assert_equal("Foo::Bar", entries.first.name)
|
62
|
+
|
63
|
+
entries = @index.resolve("Bar", ["Foo", "Baz"])
|
64
|
+
refute_empty(entries)
|
65
|
+
assert_equal("Foo::Bar", entries.first.name)
|
66
|
+
|
67
|
+
entries = @index.resolve("Foo::Bar", ["Foo", "Baz"])
|
68
|
+
refute_empty(entries)
|
69
|
+
assert_equal("Foo::Bar", entries.first.name)
|
70
|
+
|
71
|
+
assert_nil(@index.resolve("DoesNotExist", ["Foo"]))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_accessing_with_colon_colon_prefix
|
75
|
+
@index.index_single("/fake/path/foo.rb", <<~RUBY)
|
76
|
+
class Bar; end
|
77
|
+
|
78
|
+
module Foo
|
79
|
+
class Bar
|
80
|
+
end
|
81
|
+
|
82
|
+
class Baz
|
83
|
+
class Something
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
RUBY
|
88
|
+
|
89
|
+
entries = @index["::Foo::Baz::Something"]
|
90
|
+
refute_empty(entries)
|
91
|
+
assert_equal("Foo::Baz::Something", entries.first.name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class TestCase < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@index = Index.new
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def index(source)
|
15
|
+
@index.index_single("/fake/path/foo.rb", source)
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_entry(expected_name, type, expected_location)
|
19
|
+
entries = @index[expected_name]
|
20
|
+
refute_empty(entries, "Expected #{expected_name} to be indexed")
|
21
|
+
|
22
|
+
entry = entries.first
|
23
|
+
assert_instance_of(type, entry, "Expected #{expected_name} to be a #{type}")
|
24
|
+
|
25
|
+
location = entry.location
|
26
|
+
location_string =
|
27
|
+
"#{entry.file_path}:#{location.start_line - 1}-#{location.start_column}" \
|
28
|
+
":#{location.end_line - 1}-#{location.end_column}"
|
29
|
+
|
30
|
+
assert_equal(expected_location, location_string)
|
31
|
+
end
|
32
|
+
|
33
|
+
def refute_entry(expected_name)
|
34
|
+
entries = @index[expected_name]
|
35
|
+
assert_nil(entries, "Expected #{expected_name} to not be indexed")
|
36
|
+
end
|
37
|
+
|
38
|
+
def assert_no_entry
|
39
|
+
assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -15,6 +15,7 @@ module RubyLsp
|
|
15
15
|
@store = store
|
16
16
|
@test_library = T.let(DependencyDetector.detected_test_library, String)
|
17
17
|
@message_queue = message_queue
|
18
|
+
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
18
19
|
end
|
19
20
|
|
20
21
|
sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
|
@@ -57,6 +58,14 @@ module RubyLsp
|
|
57
58
|
warn(errored_extensions.map(&:backtraces).join("\n\n"))
|
58
59
|
end
|
59
60
|
|
61
|
+
if @store.experimental_features
|
62
|
+
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
63
|
+
# stuck indexing files
|
64
|
+
RubyIndexer.configuration.load_config
|
65
|
+
@index.index_all
|
66
|
+
end_progress("indexing-progress")
|
67
|
+
end
|
68
|
+
|
60
69
|
check_formatter_is_available
|
61
70
|
|
62
71
|
warn("Ruby LSP is ready")
|
@@ -169,25 +178,59 @@ module RubyLsp
|
|
169
178
|
completion(uri, request.dig(:params, :position))
|
170
179
|
when "textDocument/definition"
|
171
180
|
definition(uri, request.dig(:params, :position))
|
181
|
+
when "workspace/didChangeWatchedFiles"
|
182
|
+
did_change_watched_files(request.dig(:params, :changes))
|
172
183
|
when "rubyLsp/textDocument/showSyntaxTree"
|
173
184
|
show_syntax_tree(uri, request.dig(:params, :range))
|
174
185
|
end
|
175
186
|
end
|
176
187
|
|
188
|
+
sig { params(changes: T::Array[{ uri: String, type: Integer }]).returns(Object) }
|
189
|
+
def did_change_watched_files(changes)
|
190
|
+
changes.each do |change|
|
191
|
+
# File change events include folders, but we're only interested in files
|
192
|
+
uri = URI(change[:uri])
|
193
|
+
file_path = uri.to_standardized_path
|
194
|
+
next if file_path.nil? || File.directory?(file_path)
|
195
|
+
|
196
|
+
case change[:type]
|
197
|
+
when Constant::FileChangeType::CREATED
|
198
|
+
@index.index_single(file_path)
|
199
|
+
when Constant::FileChangeType::CHANGED
|
200
|
+
@index.delete(file_path)
|
201
|
+
@index.index_single(file_path)
|
202
|
+
when Constant::FileChangeType::DELETED
|
203
|
+
@index.delete(file_path)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
VOID
|
208
|
+
end
|
209
|
+
|
177
210
|
sig { params(uri: URI::Generic, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
|
178
211
|
def show_syntax_tree(uri, range)
|
179
212
|
{ ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
|
180
213
|
end
|
181
214
|
|
182
|
-
sig
|
215
|
+
sig do
|
216
|
+
params(
|
217
|
+
uri: URI::Generic,
|
218
|
+
position: Document::PositionShape,
|
219
|
+
).returns(T.nilable(T.any(T::Array[Interface::Location], Interface::Location)))
|
220
|
+
end
|
183
221
|
def definition(uri, position)
|
184
222
|
document = @store.get(uri)
|
185
223
|
return if document.syntax_error?
|
186
224
|
|
187
|
-
target,
|
225
|
+
target, parent, nesting = document.locate_node(
|
226
|
+
position,
|
227
|
+
node_types: [SyntaxTree::Command, SyntaxTree::Const, SyntaxTree::ConstPathRef],
|
228
|
+
)
|
229
|
+
|
230
|
+
target = parent if target.is_a?(SyntaxTree::Const) && parent.is_a?(SyntaxTree::ConstPathRef)
|
188
231
|
|
189
232
|
emitter = EventEmitter.new
|
190
|
-
base_listener = Requests::Definition.new(uri, emitter, @message_queue)
|
233
|
+
base_listener = Requests::Definition.new(uri, nesting, @index, emitter, @message_queue)
|
191
234
|
emitter.emit_for_target(target)
|
192
235
|
base_listener.response
|
193
236
|
end
|
@@ -436,6 +479,37 @@ module RubyLsp
|
|
436
479
|
listener.response
|
437
480
|
end
|
438
481
|
|
482
|
+
sig { params(id: String, title: String).void }
|
483
|
+
def begin_progress(id, title)
|
484
|
+
return unless @store.supports_progress
|
485
|
+
|
486
|
+
@message_queue << Request.new(
|
487
|
+
message: "window/workDoneProgress/create",
|
488
|
+
params: Interface::WorkDoneProgressCreateParams.new(token: id),
|
489
|
+
)
|
490
|
+
|
491
|
+
@message_queue << Notification.new(
|
492
|
+
message: "$/progress",
|
493
|
+
params: Interface::ProgressParams.new(
|
494
|
+
token: id,
|
495
|
+
value: Interface::WorkDoneProgressBegin.new(kind: "begin", title: title),
|
496
|
+
),
|
497
|
+
)
|
498
|
+
end
|
499
|
+
|
500
|
+
sig { params(id: String).void }
|
501
|
+
def end_progress(id)
|
502
|
+
return unless @store.supports_progress
|
503
|
+
|
504
|
+
@message_queue << Notification.new(
|
505
|
+
message: "$/progress",
|
506
|
+
params: Interface::ProgressParams.new(
|
507
|
+
token: id,
|
508
|
+
value: Interface::WorkDoneProgressEnd.new(kind: "end"),
|
509
|
+
),
|
510
|
+
)
|
511
|
+
end
|
512
|
+
|
439
513
|
sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
|
440
514
|
def initialize_request(options)
|
441
515
|
@store.clear
|
@@ -449,6 +523,7 @@ module RubyLsp
|
|
449
523
|
encodings.first
|
450
524
|
end
|
451
525
|
|
526
|
+
@store.supports_progress = options.dig(:capabilities, :window, :workDoneProgress) || true
|
452
527
|
formatter = options.dig(:initializationOptions, :formatter) || "auto"
|
453
528
|
@store.formatter = if formatter == "auto"
|
454
529
|
DependencyDetector.detected_formatter
|
@@ -457,9 +532,7 @@ module RubyLsp
|
|
457
532
|
end
|
458
533
|
|
459
534
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
460
|
-
|
461
|
-
# Uncomment the line below and use the variable to gate features behind the experimental flag
|
462
|
-
# experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
535
|
+
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
463
536
|
|
464
537
|
enabled_features = case configured_features
|
465
538
|
when Array
|
@@ -541,6 +614,37 @@ module RubyLsp
|
|
541
614
|
)
|
542
615
|
end
|
543
616
|
|
617
|
+
if @store.experimental_features
|
618
|
+
# Dynamically registered capabilities
|
619
|
+
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
620
|
+
|
621
|
+
# Not every client supports dynamic registration or file watching
|
622
|
+
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
623
|
+
@message_queue << Request.new(
|
624
|
+
message: "client/registerCapability",
|
625
|
+
params: Interface::RegistrationParams.new(
|
626
|
+
registrations: [
|
627
|
+
# Register watching Ruby files
|
628
|
+
Interface::Registration.new(
|
629
|
+
id: "workspace/didChangeWatchedFiles",
|
630
|
+
method: "workspace/didChangeWatchedFiles",
|
631
|
+
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
632
|
+
watchers: [
|
633
|
+
Interface::FileSystemWatcher.new(
|
634
|
+
glob_pattern: "**/*.rb",
|
635
|
+
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
636
|
+
),
|
637
|
+
],
|
638
|
+
),
|
639
|
+
),
|
640
|
+
],
|
641
|
+
),
|
642
|
+
)
|
643
|
+
end
|
644
|
+
|
645
|
+
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
646
|
+
end
|
647
|
+
|
544
648
|
Interface::InitializeResult.new(
|
545
649
|
capabilities: Interface::ServerCapabilities.new(
|
546
650
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "syntax_tree"
|
6
|
+
require "yarp"
|
6
7
|
require "language_server-protocol"
|
7
8
|
require "benchmark"
|
8
9
|
require "bundler"
|
@@ -10,6 +11,7 @@ require "uri"
|
|
10
11
|
require "cgi"
|
11
12
|
|
12
13
|
require "ruby-lsp"
|
14
|
+
require "ruby_indexer/ruby_indexer"
|
13
15
|
require "core_ext/uri"
|
14
16
|
require "ruby_lsp/utils"
|
15
17
|
require "ruby_lsp/server"
|
@@ -20,18 +20,41 @@ module RubyLsp
|
|
20
20
|
extend T::Sig
|
21
21
|
extend T::Generic
|
22
22
|
|
23
|
-
ResponseType = type_member { { fixed: T.nilable(Interface::Location) } }
|
23
|
+
ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
|
24
24
|
|
25
25
|
sig { override.returns(ResponseType) }
|
26
26
|
attr_reader :response
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
HAS_TYPECHECKER = T.let(DependencyDetector.typechecker?, T::Boolean)
|
29
|
+
|
30
|
+
sig do
|
31
|
+
params(
|
32
|
+
uri: URI::Generic,
|
33
|
+
nesting: T::Array[String],
|
34
|
+
index: RubyIndexer::Index,
|
35
|
+
emitter: EventEmitter,
|
36
|
+
message_queue: Thread::Queue,
|
37
|
+
).void
|
38
|
+
end
|
39
|
+
def initialize(uri, nesting, index, emitter, message_queue)
|
30
40
|
super(emitter, message_queue)
|
31
41
|
|
32
42
|
@uri = uri
|
43
|
+
@nesting = nesting
|
44
|
+
@index = index
|
33
45
|
@response = T.let(nil, ResponseType)
|
34
|
-
emitter.register(self, :on_command)
|
46
|
+
emitter.register(self, :on_command, :on_const, :on_const_path_ref)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(node: SyntaxTree::ConstPathRef).void }
|
50
|
+
def on_const_path_ref(node)
|
51
|
+
name = full_constant_name(node)
|
52
|
+
find_in_index(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { params(node: SyntaxTree::Const).void }
|
56
|
+
def on_const(node)
|
57
|
+
find_in_index(node.value)
|
35
58
|
end
|
36
59
|
|
37
60
|
sig { params(node: SyntaxTree::Command).void }
|
@@ -79,6 +102,30 @@ module RubyLsp
|
|
79
102
|
|
80
103
|
private
|
81
104
|
|
105
|
+
sig { params(value: String).void }
|
106
|
+
def find_in_index(value)
|
107
|
+
entries = @index.resolve(value, @nesting)
|
108
|
+
return unless entries
|
109
|
+
|
110
|
+
workspace_path = T.must(WORKSPACE_URI.to_standardized_path)
|
111
|
+
|
112
|
+
@response = entries.filter_map do |entry|
|
113
|
+
location = entry.location
|
114
|
+
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
115
|
+
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
116
|
+
# in the project, even if the files are typed false
|
117
|
+
next if HAS_TYPECHECKER && entry.file_path.start_with?(workspace_path)
|
118
|
+
|
119
|
+
Interface::Location.new(
|
120
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
121
|
+
range: Interface::Range.new(
|
122
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
123
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
124
|
+
),
|
125
|
+
)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
82
129
|
sig { params(file: String).returns(T.nilable(String)) }
|
83
130
|
def find_file_in_load_path(file)
|
84
131
|
return unless file.include?("/")
|
@@ -67,12 +67,10 @@ module RubyLsp
|
|
67
67
|
|
68
68
|
line = T.must(current_line)
|
69
69
|
|
70
|
-
# If the
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
line[@position[:character]] == "|"
|
75
|
-
|
70
|
+
# If the user inserts the closing pipe manually to the end of the block argument, we need to avoid adding
|
71
|
+
# an additional one and remove the previous one. This also helps to remove the user who accidentally
|
72
|
+
# inserts another pipe after the autocompleted one.
|
73
|
+
if line[..@position[:character]] =~ /(do|{)\s+\|[^|]*\|\|$/
|
76
74
|
@edits << Interface::TextEdit.new(
|
77
75
|
range: Interface::Range.new(
|
78
76
|
start: Interface::Position.new(
|
@@ -36,6 +36,11 @@ module RubyLsp
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
sig { returns(T::Boolean) }
|
40
|
+
def typechecker?
|
41
|
+
direct_dependency?(/^sorbet/) || direct_dependency?(/^sorbet-static-and-runtime/)
|
42
|
+
end
|
43
|
+
|
39
44
|
sig { params(gem_pattern: Regexp).returns(T::Boolean) }
|
40
45
|
def direct_dependency?(gem_pattern)
|
41
46
|
Bundler.with_original_env { Bundler.default_gemfile } &&
|
@@ -17,9 +17,10 @@ module RubyLsp
|
|
17
17
|
|
18
18
|
class BundleNotLocked < StandardError; end
|
19
19
|
|
20
|
-
sig { params(project_path: String).void }
|
21
|
-
def initialize(project_path)
|
20
|
+
sig { params(project_path: String, branch: T.nilable(String)).void }
|
21
|
+
def initialize(project_path, branch: nil)
|
22
22
|
@project_path = project_path
|
23
|
+
@branch = branch
|
23
24
|
|
24
25
|
# Custom bundle paths
|
25
26
|
@custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
|
@@ -119,7 +120,9 @@ module RubyLsp
|
|
119
120
|
end
|
120
121
|
|
121
122
|
unless @dependencies["ruby-lsp"]
|
122
|
-
|
123
|
+
ruby_lsp_entry = +'gem "ruby-lsp", require: false, group: :development'
|
124
|
+
ruby_lsp_entry << ", github: \"Shopify/ruby-lsp\", branch: \"#{@branch}\"" if @branch
|
125
|
+
parts << ruby_lsp_entry
|
123
126
|
end
|
124
127
|
|
125
128
|
unless @dependencies["debug"]
|
@@ -156,11 +159,12 @@ module RubyLsp
|
|
156
159
|
# `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
|
157
160
|
# want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
|
158
161
|
path = Bundler.settings["path"]
|
162
|
+
expanded_path = File.expand_path(path, Dir.pwd) if path
|
159
163
|
|
160
164
|
# Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
|
161
165
|
env = {}
|
162
166
|
env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
|
163
|
-
env["BUNDLE_PATH"] =
|
167
|
+
env["BUNDLE_PATH"] = expanded_path if expanded_path
|
164
168
|
|
165
169
|
# If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
|
166
170
|
# produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
|
@@ -187,7 +191,7 @@ module RubyLsp
|
|
187
191
|
# Add bundle update
|
188
192
|
warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
|
189
193
|
system(env, command)
|
190
|
-
[bundle_gemfile.to_s,
|
194
|
+
[bundle_gemfile.to_s, expanded_path]
|
191
195
|
end
|
192
196
|
end
|
193
197
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -13,34 +13,40 @@ module RubyLsp
|
|
13
13
|
sig { returns(String) }
|
14
14
|
attr_accessor :formatter
|
15
15
|
|
16
|
+
sig { returns(T::Boolean) }
|
17
|
+
attr_accessor :supports_progress
|
18
|
+
|
19
|
+
sig { returns(T::Boolean) }
|
20
|
+
attr_accessor :experimental_features
|
21
|
+
|
16
22
|
sig { void }
|
17
23
|
def initialize
|
18
24
|
@state = T.let({}, T::Hash[String, Document])
|
19
25
|
@encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
|
20
26
|
@formatter = T.let("auto", String)
|
27
|
+
@supports_progress = T.let(true, T::Boolean)
|
28
|
+
@experimental_features = T.let(false, T::Boolean)
|
21
29
|
end
|
22
30
|
|
23
31
|
sig { params(uri: URI::Generic).returns(Document) }
|
24
32
|
def get(uri)
|
25
|
-
|
26
|
-
return T.must(@state[T.must(uri.opaque)]) unless path
|
27
|
-
|
28
|
-
document = @state[path]
|
33
|
+
document = @state[uri.to_s]
|
29
34
|
return document unless document.nil?
|
30
35
|
|
31
|
-
|
32
|
-
|
36
|
+
path = T.must(uri.to_standardized_path)
|
37
|
+
set(uri: uri, source: File.binread(path), version: 0)
|
38
|
+
T.must(@state[uri.to_s])
|
33
39
|
end
|
34
40
|
|
35
41
|
sig { params(uri: URI::Generic, source: String, version: Integer).void }
|
36
42
|
def set(uri:, source:, version:)
|
37
43
|
document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
|
38
|
-
@state[uri.
|
44
|
+
@state[uri.to_s] = document
|
39
45
|
end
|
40
46
|
|
41
47
|
sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
|
42
48
|
def push_edits(uri:, edits:, version:)
|
43
|
-
T.must(@state[uri.
|
49
|
+
T.must(@state[uri.to_s]).push_edits(edits, version: version)
|
44
50
|
end
|
45
51
|
|
46
52
|
sig { void }
|
@@ -55,7 +61,7 @@ module RubyLsp
|
|
55
61
|
|
56
62
|
sig { params(uri: URI::Generic).void }
|
57
63
|
def delete(uri)
|
58
|
-
@state.delete(uri.
|
64
|
+
@state.delete(uri.to_s)
|
59
65
|
end
|
60
66
|
|
61
67
|
sig do
|