ruby-lsp 0.9.4 → 0.10.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/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -3
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +1 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +16 -16
- data/lib/ruby_indexer/test/constant_test.rb +11 -11
- data/lib/ruby_indexer/test/index_test.rb +1 -1
- data/lib/ruby_lsp/check_docs.rb +2 -1
- data/lib/ruby_lsp/event_emitter.rb +2 -0
- data/lib/ruby_lsp/executor.rb +23 -11
- data/lib/ruby_lsp/extension.rb +12 -0
- data/lib/ruby_lsp/listener.rb +43 -4
- data/lib/ruby_lsp/requests/code_lens.rb +15 -13
- data/lib/ruby_lsp/requests/completion.rb +168 -0
- data/lib/ruby_lsp/requests/definition.rb +43 -32
- data/lib/ruby_lsp/requests/document_highlight.rb +3 -3
- data/lib/ruby_lsp/requests/document_link.rb +6 -4
- data/lib/ruby_lsp/requests/document_symbol.rb +10 -9
- data/lib/ruby_lsp/requests/hover.rb +16 -33
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +4 -4
- data/lib/ruby_lsp/requests/support/common.rb +33 -0
- data/lib/ruby_lsp/requests.rb +2 -2
- data/lib/ruby_lsp/setup_bundler.rb +22 -11
- metadata +7 -7
- data/lib/ruby_lsp/requests/path_completion.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36eafde3135250941805bf340b782dfab3002839b66ba4722d261edd7fe1356a
|
4
|
+
data.tar.gz: f8dbfe5eb36489223c21d2e816f91324d0928895632a4344c16ecc756c0cb279
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24312d8a758e8388c02165d8f0ee4a8f8ccb93bb71efbd3ed3a4eeeadeb750373856cdcd3842f2cb1138ef7067c7c497260adeee347cc37f81e960a1febb7a44
|
7
|
+
data.tar.gz: c637ef4a50da10f0a7d81a15e9fc7af209e6a056642c93b315e0410fbfe732a8f42cd841ca3d70f66fac79d54474253a8febfe60f487c970632e64fd88ce103b
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.1
|
@@ -28,7 +28,7 @@ module RubyIndexer
|
|
28
28
|
@files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
|
29
29
|
|
30
30
|
# Holds all require paths for every indexed item so that we can provide autocomplete for requires
|
31
|
-
@require_paths_tree = T.let(PrefixTree[
|
31
|
+
@require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])
|
32
32
|
end
|
33
33
|
|
34
34
|
sig { params(indexable: IndexablePath).void }
|
@@ -73,7 +73,7 @@ module RubyIndexer
|
|
73
73
|
@entries[fully_qualified_name.delete_prefix("::")]
|
74
74
|
end
|
75
75
|
|
76
|
-
sig { params(query: String).returns(T::Array[
|
76
|
+
sig { params(query: String).returns(T::Array[IndexablePath]) }
|
77
77
|
def search_require_paths(query)
|
78
78
|
@require_paths_tree.search(query)
|
79
79
|
end
|
@@ -147,7 +147,7 @@ module RubyIndexer
|
|
147
147
|
visitor.run
|
148
148
|
|
149
149
|
require_path = indexable_path.require_path
|
150
|
-
@require_paths_tree.insert(require_path,
|
150
|
+
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
151
151
|
rescue Errno::EISDIR
|
152
152
|
# If `path` is a directory, just ignore it and continue indexing
|
153
153
|
end
|
@@ -57,7 +57,7 @@ module RubyIndexer
|
|
57
57
|
end
|
58
58
|
def add_constant(node)
|
59
59
|
comments = collect_comments(node)
|
60
|
-
@index << Index::Entry::Constant.new(fully_qualify_name(node.name), @file_path, node.location, comments)
|
60
|
+
@index << Index::Entry::Constant.new(fully_qualify_name(node.name.to_s), @file_path, node.location, comments)
|
61
61
|
end
|
62
62
|
|
63
63
|
sig do
|
@@ -11,7 +11,7 @@ module RubyIndexer
|
|
11
11
|
end
|
12
12
|
RUBY
|
13
13
|
|
14
|
-
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-
|
14
|
+
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_class_with_statements
|
@@ -21,7 +21,7 @@ module RubyIndexer
|
|
21
21
|
end
|
22
22
|
RUBY
|
23
23
|
|
24
|
-
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-
|
24
|
+
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-3")
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_colon_colon_class
|
@@ -30,7 +30,7 @@ module RubyIndexer
|
|
30
30
|
end
|
31
31
|
RUBY
|
32
32
|
|
33
|
-
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-
|
33
|
+
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_colon_colon_class_inside_class
|
@@ -41,8 +41,8 @@ module RubyIndexer
|
|
41
41
|
end
|
42
42
|
RUBY
|
43
43
|
|
44
|
-
assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-
|
45
|
-
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-
|
44
|
+
assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-3")
|
45
|
+
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_namespaced_class
|
@@ -51,7 +51,7 @@ module RubyIndexer
|
|
51
51
|
end
|
52
52
|
RUBY
|
53
53
|
|
54
|
-
assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-
|
54
|
+
assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
55
55
|
end
|
56
56
|
|
57
57
|
def test_dynamically_namespaced_class
|
@@ -69,7 +69,7 @@ module RubyIndexer
|
|
69
69
|
end
|
70
70
|
RUBY
|
71
71
|
|
72
|
-
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-
|
72
|
+
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
73
73
|
end
|
74
74
|
|
75
75
|
def test_module_with_statements
|
@@ -79,7 +79,7 @@ module RubyIndexer
|
|
79
79
|
end
|
80
80
|
RUBY
|
81
81
|
|
82
|
-
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-
|
82
|
+
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-3")
|
83
83
|
end
|
84
84
|
|
85
85
|
def test_colon_colon_module
|
@@ -88,7 +88,7 @@ module RubyIndexer
|
|
88
88
|
end
|
89
89
|
RUBY
|
90
90
|
|
91
|
-
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-
|
91
|
+
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
92
92
|
end
|
93
93
|
|
94
94
|
def test_namespaced_module
|
@@ -97,7 +97,7 @@ module RubyIndexer
|
|
97
97
|
end
|
98
98
|
RUBY
|
99
99
|
|
100
|
-
assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-
|
100
|
+
assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
101
101
|
end
|
102
102
|
|
103
103
|
def test_dynamically_namespaced_module
|
@@ -124,11 +124,11 @@ module RubyIndexer
|
|
124
124
|
end
|
125
125
|
RUBY
|
126
126
|
|
127
|
-
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-
|
128
|
-
assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-
|
129
|
-
assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-
|
130
|
-
assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-
|
131
|
-
assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-
|
127
|
+
assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-3")
|
128
|
+
assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
129
|
+
assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-5")
|
130
|
+
assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-7")
|
131
|
+
assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-9")
|
132
132
|
end
|
133
133
|
|
134
134
|
def test_deleting_from_index_based_on_file_path
|
@@ -137,7 +137,7 @@ module RubyIndexer
|
|
137
137
|
end
|
138
138
|
RUBY
|
139
139
|
|
140
|
-
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-
|
140
|
+
assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
141
141
|
|
142
142
|
@index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
|
143
143
|
refute_entry("Foo")
|
@@ -14,8 +14,8 @@ module RubyIndexer
|
|
14
14
|
end
|
15
15
|
RUBY
|
16
16
|
|
17
|
-
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-
|
18
|
-
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-
|
17
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-7")
|
18
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-9")
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_constant_or_writes
|
@@ -27,8 +27,8 @@ module RubyIndexer
|
|
27
27
|
end
|
28
28
|
RUBY
|
29
29
|
|
30
|
-
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-
|
31
|
-
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-
|
30
|
+
assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-9")
|
31
|
+
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-11")
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_constant_path_writes
|
@@ -45,10 +45,10 @@ module RubyIndexer
|
|
45
45
|
A::BAZ = 1
|
46
46
|
RUBY
|
47
47
|
|
48
|
-
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-
|
49
|
-
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-
|
50
|
-
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-
|
51
|
-
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-
|
48
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-9")
|
49
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-11")
|
50
|
+
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-11")
|
51
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-10")
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_constant_path_or_writes
|
@@ -61,9 +61,9 @@ module RubyIndexer
|
|
61
61
|
A::BAZ ||= 1
|
62
62
|
RUBY
|
63
63
|
|
64
|
-
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-
|
65
|
-
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-
|
66
|
-
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-
|
64
|
+
assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-11")
|
65
|
+
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-13")
|
66
|
+
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-12")
|
67
67
|
end
|
68
68
|
|
69
69
|
def test_comments_for_constants
|
@@ -136,7 +136,7 @@ module RubyIndexer
|
|
136
136
|
end
|
137
137
|
RUBY
|
138
138
|
|
139
|
-
assert_equal(["path/foo", "path/other_foo"], @index.search_require_paths("path"))
|
139
|
+
assert_equal(["path/foo", "path/other_foo"], @index.search_require_paths("path").map(&:require_path))
|
140
140
|
end
|
141
141
|
|
142
142
|
def test_searching_for_entries_based_on_prefix
|
data/lib/ruby_lsp/check_docs.rb
CHANGED
@@ -53,7 +53,8 @@ module RubyLsp
|
|
53
53
|
# documented
|
54
54
|
features = ObjectSpace.each_object(Class).filter_map do |k|
|
55
55
|
klass = T.unsafe(k)
|
56
|
-
klass if klass < RubyLsp::Requests::BaseRequest ||
|
56
|
+
klass if klass < RubyLsp::Requests::BaseRequest ||
|
57
|
+
(klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener)
|
57
58
|
end
|
58
59
|
|
59
60
|
missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
@@ -48,6 +48,8 @@ module RubyLsp
|
|
48
48
|
@listeners[:on_const_path_ref]&.each { |l| T.unsafe(l).on_const_path_ref(node) }
|
49
49
|
when SyntaxTree::Const
|
50
50
|
@listeners[:on_const]&.each { |l| T.unsafe(l).on_const(node) }
|
51
|
+
when SyntaxTree::TopConstRef
|
52
|
+
@listeners[:on_top_const_ref]&.each { |l| T.unsafe(l).on_top_const_ref(node) }
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -103,9 +103,6 @@ module RubyLsp
|
|
103
103
|
semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue)
|
104
104
|
emitter.visit(document.tree) if document.parsed?
|
105
105
|
|
106
|
-
code_lens.merge_external_listeners_responses!
|
107
|
-
document_symbol.merge_external_listeners_responses!
|
108
|
-
|
109
106
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
110
107
|
# we actually received
|
111
108
|
document.cache_set("textDocument/documentSymbol", document_symbol.response)
|
@@ -299,7 +296,6 @@ module RubyLsp
|
|
299
296
|
# Emit events for all listeners
|
300
297
|
emitter.emit_for_target(target)
|
301
298
|
|
302
|
-
hover.merge_external_listeners_responses!
|
303
299
|
hover.response
|
304
300
|
end
|
305
301
|
|
@@ -478,12 +474,18 @@ module RubyLsp
|
|
478
474
|
return unless document.parsed?
|
479
475
|
|
480
476
|
char_position = document.create_scanner.find_char_position(position)
|
481
|
-
matched, parent = document.locate(
|
482
|
-
T.must(document.tree),
|
483
|
-
char_position,
|
484
|
-
node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
|
485
|
-
)
|
486
477
|
|
478
|
+
# When the user types in the first letter of a constant name, we actually receive the position of the next
|
479
|
+
# immediate character. We check to see if the character is uppercase and then remove the offset to try to locate
|
480
|
+
# the node, as it could not be a constant
|
481
|
+
target_node_types = if ("A".."Z").cover?(document.source[char_position - 1])
|
482
|
+
char_position -= 1
|
483
|
+
[SyntaxTree::Const, SyntaxTree::ConstPathRef, SyntaxTree::TopConstRef]
|
484
|
+
else
|
485
|
+
[SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode]
|
486
|
+
end
|
487
|
+
|
488
|
+
matched, parent, nesting = document.locate(T.must(document.tree), char_position, node_types: target_node_types)
|
487
489
|
return unless matched && parent
|
488
490
|
|
489
491
|
target = case matched
|
@@ -504,12 +506,19 @@ module RubyLsp
|
|
504
506
|
return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
|
505
507
|
|
506
508
|
path_node
|
509
|
+
when SyntaxTree::Const, SyntaxTree::ConstPathRef
|
510
|
+
if (parent.is_a?(SyntaxTree::ConstPathRef) || parent.is_a?(SyntaxTree::TopConstRef)) &&
|
511
|
+
matched.is_a?(SyntaxTree::Const)
|
512
|
+
parent
|
513
|
+
else
|
514
|
+
matched
|
515
|
+
end
|
507
516
|
end
|
508
517
|
|
509
518
|
return unless target
|
510
519
|
|
511
520
|
emitter = EventEmitter.new
|
512
|
-
listener = Requests::
|
521
|
+
listener = Requests::Completion.new(@index, nesting, emitter, @message_queue)
|
513
522
|
emitter.emit_for_target(target)
|
514
523
|
listener.response
|
515
524
|
end
|
@@ -645,7 +654,10 @@ module RubyLsp
|
|
645
654
|
completion_provider = if enabled_features["completion"]
|
646
655
|
Interface::CompletionOptions.new(
|
647
656
|
resolve_provider: false,
|
648
|
-
trigger_characters: ["/"],
|
657
|
+
trigger_characters: ["/", *"A".."Z"],
|
658
|
+
completion_item: {
|
659
|
+
labelDetailsSupport: true,
|
660
|
+
},
|
649
661
|
)
|
650
662
|
end
|
651
663
|
|
data/lib/ruby_lsp/extension.rb
CHANGED
@@ -133,5 +133,17 @@ module RubyLsp
|
|
133
133
|
).returns(T.nilable(Listener[T::Array[Interface::DocumentSymbol]]))
|
134
134
|
end
|
135
135
|
def create_document_symbol_listener(emitter, message_queue); end
|
136
|
+
|
137
|
+
# Creates a new Definition listener. This method is invoked on every Definition request
|
138
|
+
sig do
|
139
|
+
overridable.params(
|
140
|
+
uri: URI::Generic,
|
141
|
+
nesting: T::Array[String],
|
142
|
+
index: RubyIndexer::Index,
|
143
|
+
emitter: EventEmitter,
|
144
|
+
message_queue: Thread::Queue,
|
145
|
+
).returns(T.nilable(Listener[T.nilable(T.any(T::Array[Interface::Location], Interface::Location))]))
|
146
|
+
end
|
147
|
+
def create_definition_listener(uri, nesting, index, emitter, message_queue); end
|
136
148
|
end
|
137
149
|
end
|
data/lib/ruby_lsp/listener.rb
CHANGED
@@ -18,13 +18,42 @@ module RubyLsp
|
|
18
18
|
def initialize(emitter, message_queue)
|
19
19
|
@emitter = emitter
|
20
20
|
@message_queue = message_queue
|
21
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(ResponseType) }
|
24
|
+
def response
|
25
|
+
_response
|
22
26
|
end
|
23
27
|
|
24
28
|
# Override this method with an attr_reader that returns the response of your listener. The listener should
|
25
29
|
# accumulate results in a @response variable and then provide the reader so that it is accessible
|
26
30
|
sig { abstract.returns(ResponseType) }
|
27
|
-
def
|
31
|
+
def _response; end
|
32
|
+
end
|
33
|
+
|
34
|
+
# ExtensibleListener is an abstract class to be used by requests that accept extensions.
|
35
|
+
class ExtensibleListener < Listener
|
36
|
+
extend T::Sig
|
37
|
+
extend T::Generic
|
38
|
+
|
39
|
+
ResponseType = type_member
|
40
|
+
|
41
|
+
abstract!
|
42
|
+
|
43
|
+
# When inheriting from ExtensibleListener, the `super` of constructor must be called **after** the subclass's own
|
44
|
+
# ivars have been initialized. This is because the constructor of ExtensibleListener calls
|
45
|
+
# `initialize_external_listener` which may depend on the subclass's ivars.
|
46
|
+
sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void }
|
47
|
+
def initialize(emitter, message_queue)
|
48
|
+
super
|
49
|
+
@response_merged = T.let(false, T::Boolean)
|
50
|
+
@external_listeners = T.let(
|
51
|
+
Extension.extensions.filter_map do |ext|
|
52
|
+
initialize_external_listener(ext)
|
53
|
+
end,
|
54
|
+
T::Array[RubyLsp::Listener[ResponseType]],
|
55
|
+
)
|
56
|
+
end
|
28
57
|
|
29
58
|
# Merge responses from all external listeners into the base listener's response. We do this to return a single
|
30
59
|
# response to the editor including the results of all extensions
|
@@ -33,11 +62,21 @@ module RubyLsp
|
|
33
62
|
@external_listeners.each { |l| merge_response!(l) }
|
34
63
|
end
|
35
64
|
|
65
|
+
sig { returns(ResponseType) }
|
66
|
+
def response
|
67
|
+
merge_external_listeners_responses! unless @response_merged
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
sig do
|
72
|
+
abstract.params(extension: RubyLsp::Extension).returns(T.nilable(RubyLsp::Listener[ResponseType]))
|
73
|
+
end
|
74
|
+
def initialize_external_listener(extension); end
|
75
|
+
|
36
76
|
# Does nothing by default. Requests that accept extensions should override this method to define how to merge
|
37
77
|
# responses coming from external listeners
|
38
|
-
sig {
|
78
|
+
sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) }
|
39
79
|
def merge_response!(other)
|
40
|
-
self
|
41
80
|
end
|
42
81
|
end
|
43
82
|
end
|
@@ -18,7 +18,7 @@ module RubyLsp
|
|
18
18
|
# class Test < Minitest::Test
|
19
19
|
# end
|
20
20
|
# ```
|
21
|
-
class CodeLens <
|
21
|
+
class CodeLens < ExtensibleListener
|
22
22
|
extend T::Sig
|
23
23
|
extend T::Generic
|
24
24
|
|
@@ -29,23 +29,20 @@ module RubyLsp
|
|
29
29
|
SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
|
30
30
|
|
31
31
|
sig { override.returns(ResponseType) }
|
32
|
-
attr_reader :
|
32
|
+
attr_reader :_response
|
33
33
|
|
34
34
|
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue, test_library: String).void }
|
35
35
|
def initialize(uri, emitter, message_queue, test_library)
|
36
|
-
super(emitter, message_queue)
|
37
|
-
|
38
36
|
@uri = T.let(uri, URI::Generic)
|
39
|
-
@external_listeners.concat(
|
40
|
-
Extension.extensions.filter_map { |ext| ext.create_code_lens_listener(uri, emitter, message_queue) },
|
41
|
-
)
|
42
37
|
@test_library = T.let(test_library, String)
|
43
|
-
@
|
38
|
+
@_response = T.let([], ResponseType)
|
44
39
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
45
40
|
# visibility_stack is a stack of [current_visibility, previous_visibility]
|
46
41
|
@visibility_stack = T.let([["public", "public"]], T::Array[T::Array[T.nilable(String)]])
|
47
42
|
@class_stack = T.let([], T::Array[String])
|
48
43
|
|
44
|
+
super(emitter, message_queue)
|
45
|
+
|
49
46
|
emitter.register(
|
50
47
|
self,
|
51
48
|
:on_class,
|
@@ -149,9 +146,14 @@ module RubyLsp
|
|
149
146
|
end
|
150
147
|
end
|
151
148
|
|
149
|
+
sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
|
150
|
+
def initialize_external_listener(extension)
|
151
|
+
extension.create_code_lens_listener(@uri, @emitter, @message_queue)
|
152
|
+
end
|
153
|
+
|
152
154
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
153
155
|
def merge_response!(other)
|
154
|
-
@
|
156
|
+
@_response.concat(other.response)
|
155
157
|
self
|
156
158
|
end
|
157
159
|
|
@@ -174,7 +176,7 @@ module RubyLsp
|
|
174
176
|
},
|
175
177
|
]
|
176
178
|
|
177
|
-
@
|
179
|
+
@_response << create_code_lens(
|
178
180
|
node,
|
179
181
|
title: "Run",
|
180
182
|
command_name: "rubyLsp.runTest",
|
@@ -182,7 +184,7 @@ module RubyLsp
|
|
182
184
|
data: { type: "test", kind: kind },
|
183
185
|
)
|
184
186
|
|
185
|
-
@
|
187
|
+
@_response << create_code_lens(
|
186
188
|
node,
|
187
189
|
title: "Run In Terminal",
|
188
190
|
command_name: "rubyLsp.runTestInTerminal",
|
@@ -190,7 +192,7 @@ module RubyLsp
|
|
190
192
|
data: { type: "test_in_terminal", kind: kind },
|
191
193
|
)
|
192
194
|
|
193
|
-
@
|
195
|
+
@_response << create_code_lens(
|
194
196
|
node,
|
195
197
|
title: "Debug",
|
196
198
|
command_name: "rubyLsp.debugTest",
|
@@ -239,7 +241,7 @@ module RubyLsp
|
|
239
241
|
|
240
242
|
sig { params(node: SyntaxTree::Command, remote: String).void }
|
241
243
|
def add_open_gem_remote_code_lens(node, remote)
|
242
|
-
@
|
244
|
+
@_response << create_code_lens(
|
243
245
|
node,
|
244
246
|
title: "Open remote",
|
245
247
|
command_name: "rubyLsp.openLink",
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
|
9
|
+
# suggests possible completions according to what the developer is typing. Currently, completion is support for
|
10
|
+
# - require paths
|
11
|
+
# - classes, modules and constant names
|
12
|
+
#
|
13
|
+
# # Example
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
|
17
|
+
#
|
18
|
+
# RubyLsp::Requests:: # --> completion: suggests `Completion`, `Hover`, ...
|
19
|
+
# ```
|
20
|
+
class Completion < Listener
|
21
|
+
extend T::Sig
|
22
|
+
extend T::Generic
|
23
|
+
|
24
|
+
ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
|
25
|
+
|
26
|
+
sig { override.returns(ResponseType) }
|
27
|
+
attr_reader :_response
|
28
|
+
|
29
|
+
sig do
|
30
|
+
params(
|
31
|
+
index: RubyIndexer::Index,
|
32
|
+
nesting: T::Array[String],
|
33
|
+
emitter: EventEmitter,
|
34
|
+
message_queue: Thread::Queue,
|
35
|
+
).void
|
36
|
+
end
|
37
|
+
def initialize(index, nesting, emitter, message_queue)
|
38
|
+
super(emitter, message_queue)
|
39
|
+
@_response = T.let([], ResponseType)
|
40
|
+
@index = index
|
41
|
+
@nesting = nesting
|
42
|
+
|
43
|
+
emitter.register(self, :on_tstring_content, :on_const_path_ref, :on_const, :on_top_const_ref)
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(node: SyntaxTree::TStringContent).void }
|
47
|
+
def on_tstring_content(node)
|
48
|
+
@index.search_require_paths(node.value).map!(&:require_path).sort!.each do |path|
|
49
|
+
@_response << build_completion(T.must(path), node)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Handle completion on regular constant references (e.g. `Bar`)
|
54
|
+
sig { params(node: SyntaxTree::Const).void }
|
55
|
+
def on_const(node)
|
56
|
+
return if DependencyDetector::HAS_TYPECHECKER
|
57
|
+
|
58
|
+
name = node.value
|
59
|
+
candidates = @index.prefix_search(name, @nesting)
|
60
|
+
candidates.each do |entries|
|
61
|
+
@_response << build_entry_completion(name, node, entries, top_level?(T.must(entries.first).name, candidates))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
66
|
+
sig { params(node: SyntaxTree::ConstPathRef).void }
|
67
|
+
def on_const_path_ref(node)
|
68
|
+
return if DependencyDetector::HAS_TYPECHECKER
|
69
|
+
|
70
|
+
name = full_constant_name(node)
|
71
|
+
candidates = @index.prefix_search(name, @nesting)
|
72
|
+
candidates.each do |entries|
|
73
|
+
@_response << build_entry_completion(name, node, entries, top_level?(T.must(entries.first).name, candidates))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Handle completion on top level constant references (e.g. `::Bar`)
|
78
|
+
sig { params(node: SyntaxTree::TopConstRef).void }
|
79
|
+
def on_top_const_ref(node)
|
80
|
+
return if DependencyDetector::HAS_TYPECHECKER
|
81
|
+
|
82
|
+
name = full_constant_name(node)
|
83
|
+
candidates = @index.prefix_search(name, [])
|
84
|
+
candidates.each { |entries| @_response << build_entry_completion(name, node, entries, true) }
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
sig { params(label: String, node: SyntaxTree::TStringContent).returns(Interface::CompletionItem) }
|
90
|
+
def build_completion(label, node)
|
91
|
+
Interface::CompletionItem.new(
|
92
|
+
label: label,
|
93
|
+
text_edit: Interface::TextEdit.new(
|
94
|
+
range: range_from_syntax_tree_node(node),
|
95
|
+
new_text: label,
|
96
|
+
),
|
97
|
+
kind: Constant::CompletionItemKind::REFERENCE,
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
sig do
|
102
|
+
params(
|
103
|
+
name: String,
|
104
|
+
node: SyntaxTree::Node,
|
105
|
+
entries: T::Array[RubyIndexer::Index::Entry],
|
106
|
+
top_level: T::Boolean,
|
107
|
+
).returns(Interface::CompletionItem)
|
108
|
+
end
|
109
|
+
def build_entry_completion(name, node, entries, top_level)
|
110
|
+
first_entry = T.must(entries.first)
|
111
|
+
kind = case first_entry
|
112
|
+
when RubyIndexer::Index::Entry::Class
|
113
|
+
Constant::CompletionItemKind::CLASS
|
114
|
+
when RubyIndexer::Index::Entry::Module
|
115
|
+
Constant::CompletionItemKind::MODULE
|
116
|
+
when RubyIndexer::Index::Entry::Constant
|
117
|
+
Constant::CompletionItemKind::CONSTANT
|
118
|
+
else
|
119
|
+
Constant::CompletionItemKind::REFERENCE
|
120
|
+
end
|
121
|
+
|
122
|
+
insertion_text = first_entry.name.dup
|
123
|
+
|
124
|
+
# If we have two entries with the same name inside the current namespace and the user selects the top level
|
125
|
+
# option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
|
126
|
+
# If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
|
127
|
+
# then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
|
128
|
+
# to complete to `::Bar`.
|
129
|
+
insertion_text.prepend("::") if top_level
|
130
|
+
|
131
|
+
# If the user is searching for a constant inside the current namespace, then we prefer completing the short name
|
132
|
+
# of that constant. E.g.:
|
133
|
+
#
|
134
|
+
# module Foo
|
135
|
+
# class Bar
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
139
|
+
# end
|
140
|
+
@nesting.each { |namespace| insertion_text.delete_prefix!("#{namespace}::") }
|
141
|
+
|
142
|
+
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
143
|
+
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
144
|
+
# the right entries in the index
|
145
|
+
Interface::CompletionItem.new(
|
146
|
+
label: first_entry.name,
|
147
|
+
filter_text: top_level ? "::#{first_entry.name}" : first_entry.name,
|
148
|
+
text_edit: Interface::TextEdit.new(
|
149
|
+
range: range_from_syntax_tree_node(node),
|
150
|
+
new_text: insertion_text,
|
151
|
+
),
|
152
|
+
kind: kind,
|
153
|
+
label_details: Interface::CompletionItemLabelDetails.new(
|
154
|
+
description: entries.map(&:file_name).join(","),
|
155
|
+
),
|
156
|
+
documentation: markdown_from_index_entries(first_entry.name, entries),
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if the `entry_name` has potential conflicts in `candidates`, so that we use a top level reference instead
|
161
|
+
# of a short name
|
162
|
+
sig { params(entry_name: String, candidates: T::Array[T::Array[RubyIndexer::Index::Entry]]).returns(T::Boolean) }
|
163
|
+
def top_level?(entry_name, candidates)
|
164
|
+
candidates.any? { |entries| T.must(entries.first).name == "#{@nesting.join("::")}::#{entry_name}" }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -17,14 +17,14 @@ module RubyLsp
|
|
17
17
|
# require "some_gem/file" # <- Request go to definition on this string will take you to the file
|
18
18
|
# Product.new # <- Request go to definition on this class name will take you to its declaration.
|
19
19
|
# ```
|
20
|
-
class Definition <
|
20
|
+
class Definition < ExtensibleListener
|
21
21
|
extend T::Sig
|
22
22
|
extend T::Generic
|
23
23
|
|
24
24
|
ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
|
25
25
|
|
26
26
|
sig { override.returns(ResponseType) }
|
27
|
-
attr_reader :
|
27
|
+
attr_reader :_response
|
28
28
|
|
29
29
|
sig do
|
30
30
|
params(
|
@@ -36,15 +36,37 @@ module RubyLsp
|
|
36
36
|
).void
|
37
37
|
end
|
38
38
|
def initialize(uri, nesting, index, emitter, message_queue)
|
39
|
-
super(emitter, message_queue)
|
40
|
-
|
41
39
|
@uri = uri
|
42
40
|
@nesting = nesting
|
43
41
|
@index = index
|
44
|
-
@
|
42
|
+
@_response = T.let(nil, ResponseType)
|
43
|
+
|
44
|
+
super(emitter, message_queue)
|
45
|
+
|
45
46
|
emitter.register(self, :on_command, :on_const, :on_const_path_ref)
|
46
47
|
end
|
47
48
|
|
49
|
+
sig { override.params(ext: Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
|
50
|
+
def initialize_external_listener(ext)
|
51
|
+
ext.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
55
|
+
def merge_response!(other)
|
56
|
+
other_response = other._response
|
57
|
+
|
58
|
+
case @_response
|
59
|
+
when Interface::Location
|
60
|
+
@_response = [@_response, *other_response]
|
61
|
+
when Array
|
62
|
+
@_response.concat(Array(other_response))
|
63
|
+
when nil
|
64
|
+
@_response = other_response
|
65
|
+
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
48
70
|
sig { params(node: SyntaxTree::ConstPathRef).void }
|
49
71
|
def on_const_path_ref(node)
|
50
72
|
name = full_constant_name(node)
|
@@ -67,14 +89,16 @@ module RubyLsp
|
|
67
89
|
string = argument.parts.first
|
68
90
|
return unless string.is_a?(SyntaxTree::TStringContent)
|
69
91
|
|
70
|
-
required_file = "#{string.value}.rb"
|
71
|
-
|
72
92
|
case message
|
73
93
|
when "require"
|
74
|
-
|
94
|
+
entry = @index.search_require_paths(string.value).find do |indexable_path|
|
95
|
+
indexable_path.require_path == string.value
|
96
|
+
end
|
97
|
+
|
98
|
+
if entry
|
99
|
+
candidate = entry.full_path
|
75
100
|
|
76
|
-
|
77
|
-
@response = Interface::Location.new(
|
101
|
+
@_response = Interface::Location.new(
|
78
102
|
uri: URI::Generic.from_path(path: candidate).to_s,
|
79
103
|
range: Interface::Range.new(
|
80
104
|
start: Interface::Position.new(line: 0, character: 0),
|
@@ -83,19 +107,18 @@ module RubyLsp
|
|
83
107
|
)
|
84
108
|
end
|
85
109
|
when "require_relative"
|
110
|
+
required_file = "#{string.value}.rb"
|
86
111
|
path = @uri.to_standardized_path
|
87
112
|
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
88
113
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
89
114
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
)
|
98
|
-
end
|
115
|
+
@_response = Interface::Location.new(
|
116
|
+
uri: URI::Generic.from_path(path: candidate).to_s,
|
117
|
+
range: Interface::Range.new(
|
118
|
+
start: Interface::Position.new(line: 0, character: 0),
|
119
|
+
end: Interface::Position.new(line: 0, character: 0),
|
120
|
+
),
|
121
|
+
)
|
99
122
|
end
|
100
123
|
end
|
101
124
|
|
@@ -112,7 +135,7 @@ module RubyLsp
|
|
112
135
|
nil
|
113
136
|
end
|
114
137
|
|
115
|
-
@
|
138
|
+
@_response = entries.filter_map do |entry|
|
116
139
|
location = entry.location
|
117
140
|
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
118
141
|
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
@@ -133,18 +156,6 @@ module RubyLsp
|
|
133
156
|
)
|
134
157
|
end
|
135
158
|
end
|
136
|
-
|
137
|
-
sig { params(file: String).returns(T.nilable(String)) }
|
138
|
-
def find_file_in_load_path(file)
|
139
|
-
return unless file.include?("/")
|
140
|
-
|
141
|
-
$LOAD_PATH.each do |p|
|
142
|
-
found = Dir.glob("**/#{file}", base: p).first
|
143
|
-
return "#{p}/#{found}" if found
|
144
|
-
end
|
145
|
-
|
146
|
-
nil
|
147
|
-
end
|
148
159
|
end
|
149
160
|
end
|
150
161
|
end
|
@@ -28,7 +28,7 @@ module RubyLsp
|
|
28
28
|
ResponseType = type_member { { fixed: T::Array[Interface::DocumentHighlight] } }
|
29
29
|
|
30
30
|
sig { override.returns(ResponseType) }
|
31
|
-
attr_reader :
|
31
|
+
attr_reader :_response
|
32
32
|
|
33
33
|
sig do
|
34
34
|
params(
|
@@ -41,7 +41,7 @@ module RubyLsp
|
|
41
41
|
def initialize(target, parent, emitter, message_queue)
|
42
42
|
super(emitter, message_queue)
|
43
43
|
|
44
|
-
@
|
44
|
+
@_response = T.let([], T::Array[Interface::DocumentHighlight])
|
45
45
|
|
46
46
|
return unless target && parent
|
47
47
|
|
@@ -83,7 +83,7 @@ module RubyLsp
|
|
83
83
|
sig { params(match: Support::HighlightTarget::HighlightMatch).void }
|
84
84
|
def add_highlight(match)
|
85
85
|
range = range_from_syntax_tree_node(match.node)
|
86
|
-
@
|
86
|
+
@_response << Interface::DocumentHighlight.new(range: range, kind: match.type)
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
@@ -73,7 +73,7 @@ module RubyLsp
|
|
73
73
|
end
|
74
74
|
|
75
75
|
sig { override.returns(ResponseType) }
|
76
|
-
attr_reader :
|
76
|
+
attr_reader :_response
|
77
77
|
|
78
78
|
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
|
79
79
|
def initialize(uri, emitter, message_queue)
|
@@ -84,7 +84,7 @@ module RubyLsp
|
|
84
84
|
path = uri.to_standardized_path
|
85
85
|
version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
|
86
86
|
@gem_version = T.let(version_match && version_match[0], T.nilable(String))
|
87
|
-
@
|
87
|
+
@_response = T.let([], T::Array[Interface::DocumentLink])
|
88
88
|
|
89
89
|
emitter.register(self, :on_comment)
|
90
90
|
end
|
@@ -95,11 +95,13 @@ module RubyLsp
|
|
95
95
|
return unless match
|
96
96
|
|
97
97
|
uri = T.cast(URI(T.must(match[0])), URI::Source)
|
98
|
-
gem_version =
|
98
|
+
gem_version = resolve_version(uri)
|
99
|
+
return if gem_version.nil?
|
100
|
+
|
99
101
|
file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
|
100
102
|
return if file_path.nil?
|
101
103
|
|
102
|
-
@
|
104
|
+
@_response << Interface::DocumentLink.new(
|
103
105
|
range: range_from_syntax_tree_node(node),
|
104
106
|
target: "file://#{file_path}##{uri.line_number}",
|
105
107
|
tooltip: "Jump to #{file_path}##{uri.line_number}",
|
@@ -26,7 +26,7 @@ module RubyLsp
|
|
26
26
|
# end
|
27
27
|
# end
|
28
28
|
# ```
|
29
|
-
class DocumentSymbol <
|
29
|
+
class DocumentSymbol < ExtensibleListener
|
30
30
|
extend T::Sig
|
31
31
|
extend T::Generic
|
32
32
|
|
@@ -47,22 +47,18 @@ module RubyLsp
|
|
47
47
|
end
|
48
48
|
|
49
49
|
sig { override.returns(T::Array[Interface::DocumentSymbol]) }
|
50
|
-
attr_reader :
|
50
|
+
attr_reader :_response
|
51
51
|
|
52
52
|
sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void }
|
53
53
|
def initialize(emitter, message_queue)
|
54
|
-
super
|
55
|
-
|
56
54
|
@root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
|
57
|
-
@
|
55
|
+
@_response = T.let(@root.children, T::Array[Interface::DocumentSymbol])
|
58
56
|
@stack = T.let(
|
59
57
|
[@root],
|
60
58
|
T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
|
61
59
|
)
|
62
60
|
|
63
|
-
|
64
|
-
Extension.extensions.filter_map { |ext| ext.create_document_symbol_listener(emitter, message_queue) },
|
65
|
-
)
|
61
|
+
super
|
66
62
|
|
67
63
|
emitter.register(
|
68
64
|
self,
|
@@ -79,10 +75,15 @@ module RubyLsp
|
|
79
75
|
)
|
80
76
|
end
|
81
77
|
|
78
|
+
sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
|
79
|
+
def initialize_external_listener(extension)
|
80
|
+
extension.create_document_symbol_listener(@emitter, @message_queue)
|
81
|
+
end
|
82
|
+
|
82
83
|
# Merges responses from other listeners
|
83
84
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
84
85
|
def merge_response!(other)
|
85
|
-
@
|
86
|
+
@_response.concat(other.response)
|
86
87
|
self
|
87
88
|
end
|
88
89
|
|
@@ -13,7 +13,7 @@ module RubyLsp
|
|
13
13
|
# ```ruby
|
14
14
|
# String # -> Hovering over the class reference will show all declaration locations and the documentation
|
15
15
|
# ```
|
16
|
-
class Hover <
|
16
|
+
class Hover < ExtensibleListener
|
17
17
|
extend T::Sig
|
18
18
|
extend T::Generic
|
19
19
|
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
)
|
31
31
|
|
32
32
|
sig { override.returns(ResponseType) }
|
33
|
-
attr_reader :
|
33
|
+
attr_reader :_response
|
34
34
|
|
35
35
|
sig do
|
36
36
|
params(
|
@@ -41,27 +41,29 @@ module RubyLsp
|
|
41
41
|
).void
|
42
42
|
end
|
43
43
|
def initialize(index, nesting, emitter, message_queue)
|
44
|
-
super(emitter, message_queue)
|
45
|
-
|
46
44
|
@nesting = nesting
|
47
45
|
@index = index
|
48
|
-
@
|
49
|
-
|
50
|
-
)
|
51
|
-
@response = T.let(nil, ResponseType)
|
46
|
+
@_response = T.let(nil, ResponseType)
|
47
|
+
|
48
|
+
super(emitter, message_queue)
|
52
49
|
emitter.register(self, :on_const_path_ref, :on_const)
|
53
50
|
end
|
54
51
|
|
52
|
+
sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
|
53
|
+
def initialize_external_listener(extension)
|
54
|
+
extension.create_hover_listener(@emitter, @message_queue)
|
55
|
+
end
|
56
|
+
|
55
57
|
# Merges responses from other hover listeners
|
56
58
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
57
59
|
def merge_response!(other)
|
58
60
|
other_response = other.response
|
59
61
|
return self unless other_response
|
60
62
|
|
61
|
-
if @
|
62
|
-
@
|
63
|
+
if @_response.nil?
|
64
|
+
@_response = other.response
|
63
65
|
else
|
64
|
-
@
|
66
|
+
@_response.contents.value << "\n\n" << other_response.contents.value
|
65
67
|
end
|
66
68
|
|
67
69
|
self
|
@@ -89,29 +91,10 @@ module RubyLsp
|
|
89
91
|
entries = @index.resolve(name, @nesting)
|
90
92
|
return unless entries
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
entries.each do |entry|
|
96
|
-
loc = entry.location
|
97
|
-
|
98
|
-
# We always handle locations as zero based. However, for file links in Markdown we need them to be one based,
|
99
|
-
# which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to columns. The
|
100
|
-
# format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
|
101
|
-
uri = URI::Generic.from_path(
|
102
|
-
path: entry.file_path,
|
103
|
-
fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
|
104
|
-
)
|
105
|
-
|
106
|
-
definitions << "[#{entry.file_name}](#{uri})"
|
107
|
-
content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
|
108
|
-
end
|
109
|
-
|
110
|
-
contents = Interface::MarkupContent.new(
|
111
|
-
kind: "markdown",
|
112
|
-
value: "#{title}\n\n**Definitions**: #{definitions.join(" | ")}\n\n#{content}",
|
94
|
+
@_response = Interface::Hover.new(
|
95
|
+
range: range_from_syntax_tree_node(node),
|
96
|
+
contents: markdown_from_index_entries(name, entries),
|
113
97
|
)
|
114
|
-
@response = Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
|
115
98
|
end
|
116
99
|
end
|
117
100
|
end
|
@@ -27,13 +27,13 @@ module RubyLsp
|
|
27
27
|
RESCUE_STRING_LENGTH = T.let("rescue".length, Integer)
|
28
28
|
|
29
29
|
sig { override.returns(ResponseType) }
|
30
|
-
attr_reader :
|
30
|
+
attr_reader :_response
|
31
31
|
|
32
32
|
sig { params(range: T::Range[Integer], emitter: EventEmitter, message_queue: Thread::Queue).void }
|
33
33
|
def initialize(range, emitter, message_queue)
|
34
34
|
super(emitter, message_queue)
|
35
35
|
|
36
|
-
@
|
36
|
+
@_response = T.let([], ResponseType)
|
37
37
|
@range = range
|
38
38
|
|
39
39
|
emitter.register(self, :on_rescue)
|
@@ -47,7 +47,7 @@ module RubyLsp
|
|
47
47
|
loc = node.location
|
48
48
|
return unless visible?(node, @range)
|
49
49
|
|
50
|
-
@
|
50
|
+
@_response << Interface::InlayHint.new(
|
51
51
|
position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
|
52
52
|
label: "StandardError",
|
53
53
|
padding_left: true,
|
@@ -105,7 +105,7 @@ module RubyLsp
|
|
105
105
|
end
|
106
106
|
|
107
107
|
sig { override.returns(ResponseType) }
|
108
|
-
attr_reader :
|
108
|
+
attr_reader :_response
|
109
109
|
|
110
110
|
sig do
|
111
111
|
params(
|
@@ -117,7 +117,7 @@ module RubyLsp
|
|
117
117
|
def initialize(emitter, message_queue, range: nil)
|
118
118
|
super(emitter, message_queue)
|
119
119
|
|
120
|
-
@
|
120
|
+
@_response = T.let([], ResponseType)
|
121
121
|
@range = range
|
122
122
|
@special_methods = T.let(nil, T.nilable(T::Array[String]))
|
123
123
|
|
@@ -174,7 +174,7 @@ module RubyLsp
|
|
174
174
|
# When finding a module or class definition, we will have already pushed a token related to this constant. We
|
175
175
|
# need to look at the previous two tokens and if they match this locatione exactly, avoid pushing another token
|
176
176
|
# on top of the previous one
|
177
|
-
return if @
|
177
|
+
return if @_response.last(2).any? { |token| token.location == node.location }
|
178
178
|
|
179
179
|
add_token(node.location, :namespace)
|
180
180
|
end
|
@@ -327,7 +327,7 @@ module RubyLsp
|
|
327
327
|
def add_token(location, type, modifiers = [])
|
328
328
|
length = location.end_char - location.start_char
|
329
329
|
modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
|
330
|
-
@
|
330
|
+
@_response.push(
|
331
331
|
SemanticToken.new(
|
332
332
|
location: location,
|
333
333
|
length: length,
|
@@ -74,6 +74,39 @@ module RubyLsp
|
|
74
74
|
data: data,
|
75
75
|
)
|
76
76
|
end
|
77
|
+
|
78
|
+
sig { params(title: String, entries: T::Array[RubyIndexer::Index::Entry]).returns(Interface::MarkupContent) }
|
79
|
+
def markdown_from_index_entries(title, entries)
|
80
|
+
markdown_title = "```ruby\n#{title}\n```"
|
81
|
+
definitions = []
|
82
|
+
content = +""
|
83
|
+
entries.each do |entry|
|
84
|
+
loc = entry.location
|
85
|
+
|
86
|
+
# We always handle locations as zero based. However, for file links in Markdown we need them to be one
|
87
|
+
# based, which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to
|
88
|
+
# columns. The format for VS Code file URIs is
|
89
|
+
# `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
|
90
|
+
uri = URI::Generic.from_path(
|
91
|
+
path: entry.file_path,
|
92
|
+
fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
|
93
|
+
)
|
94
|
+
|
95
|
+
definitions << "[#{entry.file_name}](#{uri})"
|
96
|
+
content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
Interface::MarkupContent.new(
|
100
|
+
kind: "markdown",
|
101
|
+
value: <<~MARKDOWN.chomp,
|
102
|
+
#{markdown_title}
|
103
|
+
|
104
|
+
**Definitions**: #{definitions.join(" | ")}
|
105
|
+
|
106
|
+
#{content}
|
107
|
+
MARKDOWN
|
108
|
+
)
|
109
|
+
end
|
77
110
|
end
|
78
111
|
end
|
79
112
|
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -17,7 +17,7 @@ module RubyLsp
|
|
17
17
|
# - [CodeActionResolve](rdoc-ref:RubyLsp::Requests::CodeActionResolve)
|
18
18
|
# - [DocumentHighlight](rdoc-ref:RubyLsp::Requests::DocumentHighlight)
|
19
19
|
# - [InlayHint](rdoc-ref:RubyLsp::Requests::InlayHints)
|
20
|
-
# - [
|
20
|
+
# - [Completion](rdoc-ref:RubyLsp::Requests::Completion)
|
21
21
|
# - [CodeLens](rdoc-ref:RubyLsp::Requests::CodeLens)
|
22
22
|
# - [Definition](rdoc-ref:RubyLsp::Requests::Definition)
|
23
23
|
# - [ShowSyntaxTree](rdoc-ref:RubyLsp::Requests::ShowSyntaxTree)
|
@@ -38,7 +38,7 @@ module RubyLsp
|
|
38
38
|
autoload :CodeActionResolve, "ruby_lsp/requests/code_action_resolve"
|
39
39
|
autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
|
40
40
|
autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
|
41
|
-
autoload :
|
41
|
+
autoload :Completion, "ruby_lsp/requests/completion"
|
42
42
|
autoload :CodeLens, "ruby_lsp/requests/code_lens"
|
43
43
|
autoload :Definition, "ruby_lsp/requests/definition"
|
44
44
|
autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
|
@@ -175,20 +175,24 @@ module RubyLsp
|
|
175
175
|
# weren't a part of the Gemfile, then we need to run `bundle install` for the first time to generate the
|
176
176
|
# Gemfile.lock with them included or else Bundler will complain that they're missing. We can only update if the
|
177
177
|
# custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
|
178
|
-
command = +""
|
179
178
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
179
|
+
# When not updating, we run `(bundle check || bundle install)`
|
180
|
+
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
|
181
|
+
command = +"(bundle check"
|
182
|
+
|
183
|
+
if should_bundle_update?
|
184
184
|
# If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
|
185
|
-
command
|
185
|
+
command.prepend("(")
|
186
|
+
command << " && bundle update "
|
186
187
|
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
|
187
|
-
command << "debug
|
188
|
+
command << "debug" unless @dependencies["debug"]
|
189
|
+
command << ")"
|
188
190
|
|
189
191
|
@last_updated_path.write(Time.now.iso8601)
|
190
192
|
end
|
191
193
|
|
194
|
+
command << " || bundle install) "
|
195
|
+
|
192
196
|
# Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
|
193
197
|
# responses
|
194
198
|
command << "1>&2"
|
@@ -200,10 +204,17 @@ module RubyLsp
|
|
200
204
|
end
|
201
205
|
|
202
206
|
sig { returns(T::Boolean) }
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
+
def should_bundle_update?
|
208
|
+
# If both `ruby-lsp` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it will produce
|
209
|
+
# version control changes
|
210
|
+
return false if @dependencies["ruby-lsp"] && @dependencies["debug"]
|
211
|
+
|
212
|
+
# If the custom lockfile doesn't include either the `ruby-lsp` or `debug`, we need to run bundle install before
|
213
|
+
# updating
|
214
|
+
return false if custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
|
215
|
+
|
216
|
+
# If the last updated file doesn't exist or was updated more than 4 hours ago, we should update
|
217
|
+
!@last_updated_path.exist? || Time.parse(@last_updated_path.read) < (Time.now - FOUR_HOURS)
|
207
218
|
end
|
208
219
|
end
|
209
220
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -64,20 +64,20 @@ dependencies:
|
|
64
64
|
requirements:
|
65
65
|
- - ">="
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '0.
|
67
|
+
version: '0.11'
|
68
68
|
- - "<"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: '0.
|
70
|
+
version: '0.12'
|
71
71
|
type: :runtime
|
72
72
|
prerelease: false
|
73
73
|
version_requirements: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - ">="
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: '0.
|
77
|
+
version: '0.11'
|
78
78
|
- - "<"
|
79
79
|
- !ruby/object:Gem::Version
|
80
|
-
version: '0.
|
80
|
+
version: '0.12'
|
81
81
|
description: An opinionated language server for Ruby
|
82
82
|
email:
|
83
83
|
- ruby@shopify.com
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- lib/ruby_lsp/requests/code_action_resolve.rb
|
120
120
|
- lib/ruby_lsp/requests/code_actions.rb
|
121
121
|
- lib/ruby_lsp/requests/code_lens.rb
|
122
|
+
- lib/ruby_lsp/requests/completion.rb
|
122
123
|
- lib/ruby_lsp/requests/definition.rb
|
123
124
|
- lib/ruby_lsp/requests/diagnostics.rb
|
124
125
|
- lib/ruby_lsp/requests/document_highlight.rb
|
@@ -129,7 +130,6 @@ files:
|
|
129
130
|
- lib/ruby_lsp/requests/hover.rb
|
130
131
|
- lib/ruby_lsp/requests/inlay_hints.rb
|
131
132
|
- lib/ruby_lsp/requests/on_type_formatting.rb
|
132
|
-
- lib/ruby_lsp/requests/path_completion.rb
|
133
133
|
- lib/ruby_lsp/requests/selection_ranges.rb
|
134
134
|
- lib/ruby_lsp/requests/semantic_highlighting.rb
|
135
135
|
- lib/ruby_lsp/requests/show_syntax_tree.rb
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module RubyLsp
|
5
|
-
module Requests
|
6
|
-
# 
|
7
|
-
#
|
8
|
-
# The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
|
9
|
-
# request looks up Ruby files in the $LOAD_PATH to suggest path completion inside `require` statements.
|
10
|
-
#
|
11
|
-
# # Example
|
12
|
-
#
|
13
|
-
# ```ruby
|
14
|
-
# require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
|
15
|
-
# ```
|
16
|
-
class PathCompletion < Listener
|
17
|
-
extend T::Sig
|
18
|
-
extend T::Generic
|
19
|
-
|
20
|
-
ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
|
21
|
-
|
22
|
-
sig { override.returns(ResponseType) }
|
23
|
-
attr_reader :response
|
24
|
-
|
25
|
-
sig { params(index: RubyIndexer::Index, emitter: EventEmitter, message_queue: Thread::Queue).void }
|
26
|
-
def initialize(index, emitter, message_queue)
|
27
|
-
super(emitter, message_queue)
|
28
|
-
@response = T.let([], ResponseType)
|
29
|
-
@index = index
|
30
|
-
|
31
|
-
emitter.register(self, :on_tstring_content)
|
32
|
-
end
|
33
|
-
|
34
|
-
sig { params(node: SyntaxTree::TStringContent).void }
|
35
|
-
def on_tstring_content(node)
|
36
|
-
@index.search_require_paths(node.value).sort!.each do |path|
|
37
|
-
@response << build_completion(path, node)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
sig { params(label: String, node: SyntaxTree::TStringContent).returns(Interface::CompletionItem) }
|
44
|
-
def build_completion(label, node)
|
45
|
-
Interface::CompletionItem.new(
|
46
|
-
label: label,
|
47
|
-
text_edit: Interface::TextEdit.new(
|
48
|
-
range: range_from_syntax_tree_node(node),
|
49
|
-
new_text: label,
|
50
|
-
),
|
51
|
-
kind: Constant::CompletionItemKind::REFERENCE,
|
52
|
-
)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|