ruby-lsp 0.11.2 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +11 -2
- data/exe/ruby-lsp-check +2 -1
- data/exe/ruby-lsp-doctor +15 -0
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +125 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +10 -2
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +205 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +23 -106
- data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -6
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +101 -49
- data/lib/ruby_indexer/ruby_indexer.rb +4 -3
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -16
- data/lib/ruby_indexer/test/constant_test.rb +99 -36
- data/lib/ruby_indexer/test/index_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +73 -0
- data/lib/ruby_indexer/test/test_case.rb +5 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +14 -14
- data/lib/ruby_lsp/executor.rb +89 -53
- data/lib/ruby_lsp/internal.rb +7 -2
- data/lib/ruby_lsp/listener.rb +6 -6
- data/lib/ruby_lsp/requests/base_request.rb +1 -9
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +47 -31
- data/lib/ruby_lsp/requests/completion.rb +83 -32
- data/lib/ruby_lsp/requests/definition.rb +21 -15
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +508 -31
- data/lib/ruby_lsp/requests/document_link.rb +24 -17
- data/lib/ruby_lsp/requests/document_symbol.rb +42 -42
- data/lib/ruby_lsp/requests/folding_ranges.rb +83 -77
- data/lib/ruby_lsp/requests/hover.rb +22 -17
- data/lib/ruby_lsp/requests/inlay_hints.rb +6 -6
- data/lib/ruby_lsp/requests/selection_ranges.rb +13 -105
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +92 -92
- data/lib/ruby_lsp/requests/support/annotation.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +5 -5
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +21 -7
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +19 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +10 -7
- data/lib/ruby_lsp/requests/support/sorbet.rb +28 -28
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
- data/lib/ruby_lsp/requests.rb +0 -1
- data/lib/ruby_lsp/setup_bundler.rb +26 -17
- metadata +20 -17
- data/lib/ruby_lsp/event_emitter.rb +0 -351
- data/lib/ruby_lsp/requests/support/highlight_target.rb +0 -118
@@ -30,42 +30,53 @@ module RubyLsp
|
|
30
30
|
params(
|
31
31
|
index: RubyIndexer::Index,
|
32
32
|
nesting: T::Array[String],
|
33
|
-
|
33
|
+
dispatcher: Prism::Dispatcher,
|
34
34
|
message_queue: Thread::Queue,
|
35
35
|
).void
|
36
36
|
end
|
37
|
-
def initialize(index, nesting,
|
38
|
-
super(
|
37
|
+
def initialize(index, nesting, dispatcher, message_queue)
|
38
|
+
super(dispatcher, message_queue)
|
39
39
|
@_response = T.let([], ResponseType)
|
40
40
|
@index = index
|
41
41
|
@nesting = nesting
|
42
42
|
|
43
|
-
|
43
|
+
dispatcher.register(
|
44
|
+
self,
|
45
|
+
:on_string_node_enter,
|
46
|
+
:on_constant_path_node_enter,
|
47
|
+
:on_constant_read_node_enter,
|
48
|
+
)
|
44
49
|
end
|
45
50
|
|
46
|
-
sig { params(node:
|
47
|
-
def
|
51
|
+
sig { params(node: Prism::StringNode).void }
|
52
|
+
def on_string_node_enter(node)
|
48
53
|
@index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
|
49
54
|
@_response << build_completion(T.must(path), node)
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
53
58
|
# Handle completion on regular constant references (e.g. `Bar`)
|
54
|
-
sig { params(node:
|
55
|
-
def
|
59
|
+
sig { params(node: Prism::ConstantReadNode).void }
|
60
|
+
def on_constant_read_node_enter(node)
|
56
61
|
return if DependencyDetector.instance.typechecker
|
57
62
|
|
58
63
|
name = node.slice
|
59
64
|
candidates = @index.prefix_search(name, @nesting)
|
60
65
|
candidates.each do |entries|
|
61
66
|
complete_name = T.must(entries.first).name
|
62
|
-
@_response << build_entry_completion(
|
67
|
+
@_response << build_entry_completion(
|
68
|
+
complete_name,
|
69
|
+
name,
|
70
|
+
node,
|
71
|
+
entries,
|
72
|
+
top_level?(complete_name),
|
73
|
+
)
|
63
74
|
end
|
64
75
|
end
|
65
76
|
|
66
77
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
67
|
-
sig { params(node:
|
68
|
-
def
|
78
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
79
|
+
def on_constant_path_node_enter(node)
|
69
80
|
return if DependencyDetector.instance.typechecker
|
70
81
|
|
71
82
|
name = node.slice
|
@@ -80,7 +91,7 @@ module RubyLsp
|
|
80
91
|
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
81
92
|
# order to find which possible constants match the desired search
|
82
93
|
*namespace, incomplete_name = name.split("::")
|
83
|
-
aliased_namespace = namespace.join("::")
|
94
|
+
aliased_namespace = T.must(namespace).join("::")
|
84
95
|
namespace_entries = @index.resolve(aliased_namespace, @nesting)
|
85
96
|
return unless namespace_entries
|
86
97
|
|
@@ -99,16 +110,17 @@ module RubyLsp
|
|
99
110
|
|
100
111
|
@_response << build_entry_completion(
|
101
112
|
full_name,
|
113
|
+
name,
|
102
114
|
node,
|
103
115
|
entries,
|
104
|
-
top_level_reference || top_level?(T.must(entries.first).name
|
116
|
+
top_level_reference || top_level?(T.must(entries.first).name),
|
105
117
|
)
|
106
118
|
end
|
107
119
|
end
|
108
120
|
|
109
121
|
private
|
110
122
|
|
111
|
-
sig { params(label: String, node:
|
123
|
+
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
112
124
|
def build_completion(label, node)
|
113
125
|
Interface::CompletionItem.new(
|
114
126
|
label: label,
|
@@ -122,33 +134,38 @@ module RubyLsp
|
|
122
134
|
|
123
135
|
sig do
|
124
136
|
params(
|
125
|
-
|
126
|
-
|
127
|
-
|
137
|
+
real_name: String,
|
138
|
+
incomplete_name: String,
|
139
|
+
node: Prism::Node,
|
140
|
+
entries: T::Array[RubyIndexer::Entry],
|
128
141
|
top_level: T::Boolean,
|
129
142
|
).returns(Interface::CompletionItem)
|
130
143
|
end
|
131
|
-
def build_entry_completion(
|
144
|
+
def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
|
132
145
|
first_entry = T.must(entries.first)
|
133
146
|
kind = case first_entry
|
134
|
-
when RubyIndexer::
|
147
|
+
when RubyIndexer::Entry::Class
|
135
148
|
Constant::CompletionItemKind::CLASS
|
136
|
-
when RubyIndexer::
|
149
|
+
when RubyIndexer::Entry::Module
|
137
150
|
Constant::CompletionItemKind::MODULE
|
138
|
-
when RubyIndexer::
|
151
|
+
when RubyIndexer::Entry::Constant
|
139
152
|
Constant::CompletionItemKind::CONSTANT
|
140
153
|
else
|
141
154
|
Constant::CompletionItemKind::REFERENCE
|
142
155
|
end
|
143
156
|
|
144
|
-
insertion_text =
|
157
|
+
insertion_text = real_name.dup
|
158
|
+
filter_text = real_name.dup
|
145
159
|
|
146
160
|
# If we have two entries with the same name inside the current namespace and the user selects the top level
|
147
161
|
# option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
|
148
162
|
# If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
|
149
163
|
# then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
|
150
164
|
# to complete to `::Bar`.
|
151
|
-
|
165
|
+
if top_level
|
166
|
+
insertion_text.prepend("::")
|
167
|
+
filter_text.prepend("::")
|
168
|
+
end
|
152
169
|
|
153
170
|
# If the user is searching for a constant inside the current namespace, then we prefer completing the short name
|
154
171
|
# of that constant. E.g.:
|
@@ -159,14 +176,28 @@ module RubyLsp
|
|
159
176
|
#
|
160
177
|
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
161
178
|
# end
|
162
|
-
@nesting.each
|
179
|
+
@nesting.each do |namespace|
|
180
|
+
prefix = "#{namespace}::"
|
181
|
+
shortened_name = insertion_text.delete_prefix(prefix)
|
182
|
+
|
183
|
+
# If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
|
184
|
+
conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
|
185
|
+
break if real_name != conflict_name && @index[conflict_name]
|
186
|
+
|
187
|
+
insertion_text = shortened_name
|
188
|
+
|
189
|
+
# If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
|
190
|
+
# `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
|
191
|
+
# typing
|
192
|
+
filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
|
193
|
+
end
|
163
194
|
|
164
195
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
165
196
|
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
166
197
|
# the right entries in the index
|
167
198
|
Interface::CompletionItem.new(
|
168
|
-
label:
|
169
|
-
filter_text:
|
199
|
+
label: real_name,
|
200
|
+
filter_text: filter_text,
|
170
201
|
text_edit: Interface::TextEdit.new(
|
171
202
|
range: range_from_node(node),
|
172
203
|
new_text: insertion_text,
|
@@ -175,15 +206,35 @@ module RubyLsp
|
|
175
206
|
label_details: Interface::CompletionItemLabelDetails.new(
|
176
207
|
description: entries.map(&:file_name).join(","),
|
177
208
|
),
|
178
|
-
documentation: markdown_from_index_entries(
|
209
|
+
documentation: markdown_from_index_entries(real_name, entries),
|
179
210
|
)
|
180
211
|
end
|
181
212
|
|
182
|
-
# Check if
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
213
|
+
# Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
|
214
|
+
# For example:
|
215
|
+
#
|
216
|
+
# ```ruby
|
217
|
+
# class Bar; end
|
218
|
+
#
|
219
|
+
# module Foo
|
220
|
+
# class Bar; end
|
221
|
+
#
|
222
|
+
# # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
|
223
|
+
# # completion, but instead need to suggest `::Bar`
|
224
|
+
# B
|
225
|
+
# end
|
226
|
+
# ```
|
227
|
+
sig { params(entry_name: String).returns(T::Boolean) }
|
228
|
+
def top_level?(entry_name)
|
229
|
+
@nesting.length.downto(0).each do |i|
|
230
|
+
prefix = T.must(@nesting[0...i]).join("::")
|
231
|
+
full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
|
232
|
+
next if full_name == entry_name
|
233
|
+
|
234
|
+
return true if @index[full_name]
|
235
|
+
end
|
236
|
+
|
237
|
+
false
|
187
238
|
end
|
188
239
|
end
|
189
240
|
end
|
@@ -31,23 +31,29 @@ module RubyLsp
|
|
31
31
|
uri: URI::Generic,
|
32
32
|
nesting: T::Array[String],
|
33
33
|
index: RubyIndexer::Index,
|
34
|
-
|
34
|
+
dispatcher: Prism::Dispatcher,
|
35
35
|
message_queue: Thread::Queue,
|
36
36
|
).void
|
37
37
|
end
|
38
|
-
def initialize(uri, nesting, index,
|
38
|
+
def initialize(uri, nesting, index, dispatcher, message_queue)
|
39
39
|
@uri = uri
|
40
40
|
@nesting = nesting
|
41
41
|
@index = index
|
42
42
|
@_response = T.let(nil, ResponseType)
|
43
43
|
|
44
|
-
super(
|
44
|
+
super(dispatcher, message_queue)
|
45
45
|
|
46
|
-
|
46
|
+
dispatcher.register(
|
47
|
+
self,
|
48
|
+
:on_call_node_enter,
|
49
|
+
:on_constant_read_node_enter,
|
50
|
+
:on_constant_path_node_enter,
|
51
|
+
)
|
47
52
|
end
|
53
|
+
|
48
54
|
sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
|
49
55
|
def initialize_external_listener(addon)
|
50
|
-
addon.create_definition_listener(@uri, @nesting, @index, @
|
56
|
+
addon.create_definition_listener(@uri, @nesting, @index, @dispatcher, @message_queue)
|
51
57
|
end
|
52
58
|
|
53
59
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
@@ -66,19 +72,19 @@ module RubyLsp
|
|
66
72
|
self
|
67
73
|
end
|
68
74
|
|
69
|
-
sig { params(node:
|
70
|
-
def
|
75
|
+
sig { params(node: Prism::CallNode).void }
|
76
|
+
def on_call_node_enter(node)
|
71
77
|
message = node.name
|
72
|
-
return unless message ==
|
78
|
+
return unless message == :require || message == :require_relative
|
73
79
|
|
74
80
|
arguments = node.arguments
|
75
81
|
return unless arguments
|
76
82
|
|
77
83
|
argument = arguments.arguments.first
|
78
|
-
return unless argument.is_a?(
|
84
|
+
return unless argument.is_a?(Prism::StringNode)
|
79
85
|
|
80
86
|
case message
|
81
|
-
when
|
87
|
+
when :require
|
82
88
|
entry = @index.search_require_paths(argument.content).find do |indexable_path|
|
83
89
|
indexable_path.require_path == argument.content
|
84
90
|
end
|
@@ -94,7 +100,7 @@ module RubyLsp
|
|
94
100
|
),
|
95
101
|
)
|
96
102
|
end
|
97
|
-
when
|
103
|
+
when :require_relative
|
98
104
|
required_file = "#{argument.content}.rb"
|
99
105
|
path = @uri.to_standardized_path
|
100
106
|
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
@@ -110,13 +116,13 @@ module RubyLsp
|
|
110
116
|
end
|
111
117
|
end
|
112
118
|
|
113
|
-
sig { params(node:
|
114
|
-
def
|
119
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
120
|
+
def on_constant_path_node_enter(node)
|
115
121
|
find_in_index(node.slice)
|
116
122
|
end
|
117
123
|
|
118
|
-
sig { params(node:
|
119
|
-
def
|
124
|
+
sig { params(node: Prism::ConstantReadNode).void }
|
125
|
+
def on_constant_read_node_enter(node)
|
120
126
|
find_in_index(node.slice)
|
121
127
|
end
|
122
128
|
|