ruby-lsp 0.11.2 → 0.12.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 +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
|
|