ruby-lsp 0.12.3 → 0.12.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/VERSION +1 -1
- data/exe/ruby-lsp-doctor +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/{visitor.rb → collector.rb} +107 -58
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +8 -3
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +6 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +22 -4
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +53 -0
- data/lib/ruby_indexer/test/method_test.rb +14 -0
- data/lib/ruby_lsp/document.rb +7 -9
- data/lib/ruby_lsp/executor.rb +29 -24
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/requests/completion.rb +54 -5
- data/lib/ruby_lsp/requests/definition.rb +52 -25
- data/lib/ruby_lsp/requests/hover.rb +20 -0
- data/lib/ruby_lsp/requests/inlay_hints.rb +39 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -1
- data/lib/ruby_lsp/requests/support/common.rb +22 -2
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -11
- data/lib/ruby_lsp/ruby_document.rb +14 -0
- data/lib/ruby_lsp/setup_bundler.rb +2 -0
- data/lib/ruby_lsp/store.rb +1 -3
- data/lib/ruby_lsp/utils.rb +9 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cb6c373890a19400025a75be13971ff5e5e33772bd74a178784a035ba94c168
|
4
|
+
data.tar.gz: d52ad65673ea9f1dfde13cfa6beec0f8c860a75b7f08453238597d584227b56f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba64686636c26776797fc6e7e70156f4c7f304dbe27e98c73c2a60a7255ae7260159838d68c0364e98bc58d26c404118ad9e3d1cfe13766a2221cbc4d2ac6a7c
|
7
|
+
data.tar.gz: fcf587173133668739ddc639d913ce34877cc1c0d3451b2ddc49254940ef8ba49815cae57dce2c6de423d3d3fb0db1059f1b8d2d19cd9d1de3af72fb26819759
|
data/README.md
CHANGED
@@ -52,6 +52,7 @@ The Ruby LSP provides an addon system that allows other gems to enhance the base
|
|
52
52
|
features. This is the mechanism that powers addons like
|
53
53
|
|
54
54
|
- [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
|
55
|
+
- [Ruby LSP RSpec](https://github.com/st0012/ruby-lsp-rspec)
|
55
56
|
|
56
57
|
For instructions on how to create addons, see the [addons documentation](ADDONS.md).
|
57
58
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.12.
|
1
|
+
0.12.5
|
data/exe/ruby-lsp-doctor
CHANGED
@@ -10,6 +10,6 @@ RubyIndexer.configuration.indexables.each do |indexable|
|
|
10
10
|
puts "indexing: #{indexable.full_path}"
|
11
11
|
content = File.read(indexable.full_path)
|
12
12
|
result = Prism.parse(content)
|
13
|
-
|
14
|
-
result.value
|
13
|
+
collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
|
14
|
+
collector.collect(result.value)
|
15
15
|
end
|
@@ -2,9 +2,11 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
|
-
class
|
5
|
+
class Collector
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
LEAVE_EVENT = T.let(Object.new.freeze, Object)
|
9
|
+
|
8
10
|
sig { params(index: Index, parse_result: Prism::ParseResult, file_path: String).void }
|
9
11
|
def initialize(index, parse_result, file_path)
|
10
12
|
@index = index
|
@@ -16,22 +18,63 @@ module RubyIndexer
|
|
16
18
|
end,
|
17
19
|
T::Hash[Integer, Prism::Comment],
|
18
20
|
)
|
21
|
+
@queue = T.let([], T::Array[Object])
|
22
|
+
@current_owner = T.let(nil, T.nilable(Entry::Namespace))
|
19
23
|
|
20
24
|
super()
|
21
25
|
end
|
22
26
|
|
23
|
-
sig {
|
24
|
-
def
|
25
|
-
|
27
|
+
sig { params(node: Prism::Node).void }
|
28
|
+
def collect(node)
|
29
|
+
@queue = [node]
|
30
|
+
|
31
|
+
until @queue.empty?
|
32
|
+
node_or_event = @queue.shift
|
33
|
+
|
34
|
+
case node_or_event
|
35
|
+
when Prism::ProgramNode
|
36
|
+
@queue << node_or_event.statements
|
37
|
+
when Prism::StatementsNode
|
38
|
+
T.unsafe(@queue).prepend(*node_or_event.body)
|
39
|
+
when Prism::ClassNode
|
40
|
+
add_class_entry(node_or_event)
|
41
|
+
when Prism::ModuleNode
|
42
|
+
add_module_entry(node_or_event)
|
43
|
+
when Prism::MultiWriteNode
|
44
|
+
handle_multi_write_node(node_or_event)
|
45
|
+
when Prism::ConstantPathWriteNode
|
46
|
+
handle_constant_path_write_node(node_or_event)
|
47
|
+
when Prism::ConstantPathOrWriteNode
|
48
|
+
handle_constant_path_or_write_node(node_or_event)
|
49
|
+
when Prism::ConstantPathOperatorWriteNode
|
50
|
+
handle_constant_path_operator_write_node(node_or_event)
|
51
|
+
when Prism::ConstantPathAndWriteNode
|
52
|
+
handle_constant_path_and_write_node(node_or_event)
|
53
|
+
when Prism::ConstantWriteNode
|
54
|
+
handle_constant_write_node(node_or_event)
|
55
|
+
when Prism::ConstantOrWriteNode
|
56
|
+
name = fully_qualify_name(node_or_event.name.to_s)
|
57
|
+
add_constant(node_or_event, name)
|
58
|
+
when Prism::ConstantAndWriteNode
|
59
|
+
name = fully_qualify_name(node_or_event.name.to_s)
|
60
|
+
add_constant(node_or_event, name)
|
61
|
+
when Prism::ConstantOperatorWriteNode
|
62
|
+
name = fully_qualify_name(node_or_event.name.to_s)
|
63
|
+
add_constant(node_or_event, name)
|
64
|
+
when Prism::CallNode
|
65
|
+
handle_call_node(node_or_event)
|
66
|
+
when Prism::DefNode
|
67
|
+
handle_def_node(node_or_event)
|
68
|
+
when LEAVE_EVENT
|
69
|
+
@stack.pop
|
70
|
+
end
|
71
|
+
end
|
26
72
|
end
|
27
73
|
|
28
|
-
|
29
|
-
def visit_module_node(node)
|
30
|
-
add_module_entry(node)
|
31
|
-
end
|
74
|
+
private
|
32
75
|
|
33
|
-
sig {
|
34
|
-
def
|
76
|
+
sig { params(node: Prism::MultiWriteNode).void }
|
77
|
+
def handle_multi_write_node(node)
|
35
78
|
value = node.value
|
36
79
|
values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
|
37
80
|
|
@@ -50,8 +93,8 @@ module RubyIndexer
|
|
50
93
|
end
|
51
94
|
end
|
52
95
|
|
53
|
-
sig {
|
54
|
-
def
|
96
|
+
sig { params(node: Prism::ConstantPathWriteNode).void }
|
97
|
+
def handle_constant_path_write_node(node)
|
55
98
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
56
99
|
target = node.target
|
57
100
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -60,8 +103,8 @@ module RubyIndexer
|
|
60
103
|
add_constant(node, name)
|
61
104
|
end
|
62
105
|
|
63
|
-
sig {
|
64
|
-
def
|
106
|
+
sig { params(node: Prism::ConstantPathOrWriteNode).void }
|
107
|
+
def handle_constant_path_or_write_node(node)
|
65
108
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
66
109
|
target = node.target
|
67
110
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -70,8 +113,8 @@ module RubyIndexer
|
|
70
113
|
add_constant(node, name)
|
71
114
|
end
|
72
115
|
|
73
|
-
sig {
|
74
|
-
def
|
116
|
+
sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
|
117
|
+
def handle_constant_path_operator_write_node(node)
|
75
118
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
76
119
|
target = node.target
|
77
120
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -80,8 +123,8 @@ module RubyIndexer
|
|
80
123
|
add_constant(node, name)
|
81
124
|
end
|
82
125
|
|
83
|
-
sig {
|
84
|
-
def
|
126
|
+
sig { params(node: Prism::ConstantPathAndWriteNode).void }
|
127
|
+
def handle_constant_path_and_write_node(node)
|
85
128
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
86
129
|
target = node.target
|
87
130
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -90,50 +133,44 @@ module RubyIndexer
|
|
90
133
|
add_constant(node, name)
|
91
134
|
end
|
92
135
|
|
93
|
-
sig {
|
94
|
-
def
|
95
|
-
name = fully_qualify_name(node.name.to_s)
|
96
|
-
add_constant(node, name)
|
97
|
-
end
|
98
|
-
|
99
|
-
sig { override.params(node: Prism::ConstantOrWriteNode).void }
|
100
|
-
def visit_constant_or_write_node(node)
|
101
|
-
name = fully_qualify_name(node.name.to_s)
|
102
|
-
add_constant(node, name)
|
103
|
-
end
|
104
|
-
|
105
|
-
sig { override.params(node: Prism::ConstantAndWriteNode).void }
|
106
|
-
def visit_constant_and_write_node(node)
|
136
|
+
sig { params(node: Prism::ConstantWriteNode).void }
|
137
|
+
def handle_constant_write_node(node)
|
107
138
|
name = fully_qualify_name(node.name.to_s)
|
108
139
|
add_constant(node, name)
|
109
140
|
end
|
110
141
|
|
111
|
-
sig {
|
112
|
-
def
|
113
|
-
name = fully_qualify_name(node.name.to_s)
|
114
|
-
add_constant(node, name)
|
115
|
-
end
|
116
|
-
|
117
|
-
sig { override.params(node: Prism::CallNode).void }
|
118
|
-
def visit_call_node(node)
|
142
|
+
sig { params(node: Prism::CallNode).void }
|
143
|
+
def handle_call_node(node)
|
119
144
|
message = node.message
|
120
145
|
handle_private_constant(node) if message == "private_constant"
|
121
146
|
end
|
122
147
|
|
123
|
-
sig {
|
124
|
-
def
|
148
|
+
sig { params(node: Prism::DefNode).void }
|
149
|
+
def handle_def_node(node)
|
125
150
|
method_name = node.name.to_s
|
126
151
|
comments = collect_comments(node)
|
127
152
|
case node.receiver
|
128
153
|
when nil
|
129
|
-
@index << Entry::InstanceMethod.new(
|
154
|
+
@index << Entry::InstanceMethod.new(
|
155
|
+
method_name,
|
156
|
+
@file_path,
|
157
|
+
node.location,
|
158
|
+
comments,
|
159
|
+
node.parameters,
|
160
|
+
@current_owner,
|
161
|
+
)
|
130
162
|
when Prism::SelfNode
|
131
|
-
@index << Entry::SingletonMethod.new(
|
163
|
+
@index << Entry::SingletonMethod.new(
|
164
|
+
method_name,
|
165
|
+
@file_path,
|
166
|
+
node.location,
|
167
|
+
comments,
|
168
|
+
node.parameters,
|
169
|
+
@current_owner,
|
170
|
+
)
|
132
171
|
end
|
133
172
|
end
|
134
173
|
|
135
|
-
private
|
136
|
-
|
137
174
|
sig { params(node: Prism::CallNode).void }
|
138
175
|
def handle_private_constant(node)
|
139
176
|
arguments = node.arguments&.arguments
|
@@ -189,12 +226,12 @@ module RubyIndexer
|
|
189
226
|
|
190
227
|
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
|
191
228
|
# indexed too
|
192
|
-
|
229
|
+
@queue.prepend(value)
|
193
230
|
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
|
194
231
|
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
195
232
|
Prism::ConstantPathAndWriteNode
|
196
233
|
|
197
|
-
|
234
|
+
@queue.prepend(value)
|
198
235
|
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
|
199
236
|
else
|
200
237
|
Entry::Constant.new(name, @file_path, node.location, comments)
|
@@ -204,20 +241,26 @@ module RubyIndexer
|
|
204
241
|
sig { params(node: Prism::ModuleNode).void }
|
205
242
|
def add_module_entry(node)
|
206
243
|
name = node.constant_path.location.slice
|
207
|
-
|
244
|
+
unless /^[A-Z:]/.match?(name)
|
245
|
+
@queue << node.body
|
246
|
+
return
|
247
|
+
end
|
208
248
|
|
209
249
|
comments = collect_comments(node)
|
210
|
-
|
211
|
-
@index <<
|
250
|
+
@current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
|
251
|
+
@index << @current_owner
|
212
252
|
@stack << name
|
213
|
-
|
214
|
-
@stack.pop
|
253
|
+
@queue.prepend(node.body, LEAVE_EVENT)
|
215
254
|
end
|
216
255
|
|
217
256
|
sig { params(node: Prism::ClassNode).void }
|
218
257
|
def add_class_entry(node)
|
219
258
|
name = node.constant_path.location.slice
|
220
|
-
|
259
|
+
|
260
|
+
unless /^[A-Z:]/.match?(name)
|
261
|
+
@queue << node.body
|
262
|
+
return
|
263
|
+
end
|
221
264
|
|
222
265
|
comments = collect_comments(node)
|
223
266
|
|
@@ -227,10 +270,16 @@ module RubyIndexer
|
|
227
270
|
superclass.slice
|
228
271
|
end
|
229
272
|
|
230
|
-
@
|
273
|
+
@current_owner = Entry::Class.new(
|
274
|
+
fully_qualify_name(name),
|
275
|
+
@file_path,
|
276
|
+
node.location,
|
277
|
+
comments,
|
278
|
+
parent_class,
|
279
|
+
)
|
280
|
+
@index << @current_owner
|
231
281
|
@stack << name
|
232
|
-
|
233
|
-
@stack.pop
|
282
|
+
@queue.prepend(node.body, LEAVE_EVENT)
|
234
283
|
end
|
235
284
|
|
236
285
|
sig { params(node: Prism::Node).returns(T::Array[String]) }
|
@@ -249,7 +298,7 @@ module RubyIndexer
|
|
249
298
|
|
250
299
|
comment_content.delete_prefix!("#")
|
251
300
|
comment_content.delete_prefix!(" ")
|
252
|
-
comments.
|
301
|
+
comments.prepend(comment_content)
|
253
302
|
end
|
254
303
|
|
255
304
|
comments
|
@@ -71,8 +71,13 @@ module RubyIndexer
|
|
71
71
|
|
72
72
|
Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
|
73
73
|
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
|
74
|
-
# entry is expensive, we memoize it
|
75
|
-
|
74
|
+
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
|
75
|
+
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the Dir.pwd, but
|
76
|
+
# each one of them belongs to a different $LOAD_PATH entry
|
77
|
+
if load_path_entry.nil? || !path.start_with?(load_path_entry)
|
78
|
+
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
|
79
|
+
end
|
80
|
+
|
76
81
|
IndexablePath.new(load_path_entry, path)
|
77
82
|
end
|
78
83
|
end
|
@@ -144,7 +149,7 @@ module RubyIndexer
|
|
144
149
|
# just ignore if they're missing
|
145
150
|
end
|
146
151
|
|
147
|
-
indexables.uniq!
|
152
|
+
indexables.uniq!(&:full_path)
|
148
153
|
indexables
|
149
154
|
end
|
150
155
|
|
@@ -101,6 +101,9 @@ module RubyIndexer
|
|
101
101
|
sig { returns(T::Array[Parameter]) }
|
102
102
|
attr_reader :parameters
|
103
103
|
|
104
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
105
|
+
attr_reader :owner
|
106
|
+
|
104
107
|
sig do
|
105
108
|
params(
|
106
109
|
name: String,
|
@@ -108,11 +111,13 @@ module RubyIndexer
|
|
108
111
|
location: Prism::Location,
|
109
112
|
comments: T::Array[String],
|
110
113
|
parameters_node: T.nilable(Prism::ParametersNode),
|
114
|
+
owner: T.nilable(Entry::Namespace),
|
111
115
|
).void
|
112
116
|
end
|
113
|
-
def initialize(name, file_path, location, comments, parameters_node)
|
117
|
+
def initialize(name, file_path, location, comments, parameters_node, owner) # rubocop:disable Metrics/ParameterLists
|
114
118
|
super(name, file_path, location, comments)
|
115
119
|
@parameters = T.let(list_params(parameters_node), T::Array[Parameter])
|
120
|
+
@owner = owner
|
116
121
|
end
|
117
122
|
|
118
123
|
private
|
@@ -93,8 +93,14 @@ module RubyIndexer
|
|
93
93
|
# [#<Entry::Class name="Foo::Baz">],
|
94
94
|
# ]
|
95
95
|
# ```
|
96
|
-
sig { params(query: String, nesting: T::Array[String]).returns(T::Array[T::Array[Entry]]) }
|
97
|
-
def prefix_search(query, nesting)
|
96
|
+
sig { params(query: String, nesting: T.nilable(T::Array[String])).returns(T::Array[T::Array[Entry]]) }
|
97
|
+
def prefix_search(query, nesting = nil)
|
98
|
+
unless nesting
|
99
|
+
results = @entries_tree.search(query)
|
100
|
+
results.uniq!
|
101
|
+
return results
|
102
|
+
end
|
103
|
+
|
98
104
|
results = nesting.length.downto(0).flat_map do |i|
|
99
105
|
prefix = T.must(nesting[0...i]).join("::")
|
100
106
|
namespaced_query = prefix.empty? ? query : "#{prefix}::#{query}"
|
@@ -180,8 +186,8 @@ module RubyIndexer
|
|
180
186
|
def index_single(indexable_path, source = nil)
|
181
187
|
content = source || File.read(indexable_path.full_path)
|
182
188
|
result = Prism.parse(content)
|
183
|
-
|
184
|
-
result.value
|
189
|
+
collector = Collector.new(self, result, indexable_path.full_path)
|
190
|
+
collector.collect(result.value)
|
185
191
|
|
186
192
|
require_path = indexable_path.require_path
|
187
193
|
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
@@ -231,6 +237,18 @@ module RubyIndexer
|
|
231
237
|
real_parts.join("::")
|
232
238
|
end
|
233
239
|
|
240
|
+
# Attempts to find a given method for a resolved fully qualified receiver name. Returns `nil` if the method does not
|
241
|
+
# exist on that receiver
|
242
|
+
sig { params(method_name: String, receiver_name: String).returns(T.nilable(Entry::Method)) }
|
243
|
+
def resolve_method(method_name, receiver_name)
|
244
|
+
method_entries = T.cast(self[method_name], T.nilable(T::Array[Entry::Method]))
|
245
|
+
owner_entries = self[receiver_name]
|
246
|
+
return unless owner_entries && method_entries
|
247
|
+
|
248
|
+
owner_name = T.must(owner_entries.first).name
|
249
|
+
method_entries.find { |entry| entry.owner&.name == owner_name }
|
250
|
+
end
|
251
|
+
|
234
252
|
private
|
235
253
|
|
236
254
|
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
@@ -5,7 +5,7 @@ require "yaml"
|
|
5
5
|
require "did_you_mean"
|
6
6
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
|
-
require "ruby_indexer/lib/ruby_indexer/
|
8
|
+
require "ruby_indexer/lib/ruby_indexer/collector"
|
9
9
|
require "ruby_indexer/lib/ruby_indexer/index"
|
10
10
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
11
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
@@ -193,5 +193,58 @@ module RubyIndexer
|
|
193
193
|
|
194
194
|
assert_instance_of(Entry::UnresolvedAlias, entry)
|
195
195
|
end
|
196
|
+
|
197
|
+
def test_visitor_does_not_visit_unnecessary_nodes
|
198
|
+
concats = (0...10_000).map do |i|
|
199
|
+
<<~STRING
|
200
|
+
"string#{i}" \\
|
201
|
+
STRING
|
202
|
+
end.join
|
203
|
+
|
204
|
+
index(<<~RUBY)
|
205
|
+
module Foo
|
206
|
+
local_var = #{concats}
|
207
|
+
"final"
|
208
|
+
@class_instance_var = #{concats}
|
209
|
+
"final"
|
210
|
+
@@class_var = #{concats}
|
211
|
+
"final"
|
212
|
+
$global_var = #{concats}
|
213
|
+
"final"
|
214
|
+
CONST = #{concats}
|
215
|
+
"final"
|
216
|
+
end
|
217
|
+
RUBY
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_resolve_method_with_known_receiver
|
221
|
+
index(<<~RUBY)
|
222
|
+
module Foo
|
223
|
+
module Bar
|
224
|
+
def baz; end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
RUBY
|
228
|
+
|
229
|
+
entry = T.must(@index.resolve_method("baz", "Foo::Bar"))
|
230
|
+
assert_equal("baz", entry.name)
|
231
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_prefix_search_for_methods
|
235
|
+
index(<<~RUBY)
|
236
|
+
module Foo
|
237
|
+
module Bar
|
238
|
+
def baz; end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
RUBY
|
242
|
+
|
243
|
+
entries = @index.prefix_search("ba")
|
244
|
+
refute_empty(entries)
|
245
|
+
|
246
|
+
entry = T.must(entries.first).first
|
247
|
+
assert_equal("baz", entry.name)
|
248
|
+
end
|
196
249
|
end
|
197
250
|
end
|
@@ -69,5 +69,19 @@ module RubyIndexer
|
|
69
69
|
assert_equal(:"(a, (b, ))", parameter.name)
|
70
70
|
assert_instance_of(Entry::RequiredParameter, parameter)
|
71
71
|
end
|
72
|
+
|
73
|
+
def test_keeps_track_of_method_owner
|
74
|
+
index(<<~RUBY)
|
75
|
+
class Foo
|
76
|
+
def bar
|
77
|
+
end
|
78
|
+
end
|
79
|
+
RUBY
|
80
|
+
|
81
|
+
entry = T.must(@index["bar"].first)
|
82
|
+
owner_name = T.must(entry.owner).name
|
83
|
+
|
84
|
+
assert_equal("Foo", owner_name)
|
85
|
+
end
|
72
86
|
end
|
73
87
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
class Document
|
6
6
|
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
abstract!
|
7
10
|
|
8
11
|
PositionShape = T.type_alias { { line: Integer, character: Integer } }
|
9
12
|
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
@@ -28,8 +31,8 @@ module RubyLsp
|
|
28
31
|
@source = T.let(source, String)
|
29
32
|
@version = T.let(version, Integer)
|
30
33
|
@uri = T.let(uri, URI::Generic)
|
31
|
-
@needs_parsing = T.let(
|
32
|
-
@parse_result = T.let(
|
34
|
+
@needs_parsing = T.let(true, T::Boolean)
|
35
|
+
@parse_result = T.let(parse, Prism::ParseResult)
|
33
36
|
end
|
34
37
|
|
35
38
|
sig { returns(Prism::ProgramNode) }
|
@@ -91,13 +94,8 @@ module RubyLsp
|
|
91
94
|
@cache.clear
|
92
95
|
end
|
93
96
|
|
94
|
-
sig {
|
95
|
-
def parse
|
96
|
-
return unless @needs_parsing
|
97
|
-
|
98
|
-
@needs_parsing = false
|
99
|
-
@parse_result = Prism.parse(@source)
|
100
|
-
end
|
97
|
+
sig { abstract.returns(Prism::ParseResult) }
|
98
|
+
def parse; end
|
101
99
|
|
102
100
|
sig { returns(T::Boolean) }
|
103
101
|
def syntax_error?
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -57,6 +57,8 @@ module RubyLsp
|
|
57
57
|
warn(errored_addons.map(&:backtraces).join("\n\n"))
|
58
58
|
end
|
59
59
|
|
60
|
+
RubyVM::YJIT.enable if defined? RubyVM::YJIT.enable
|
61
|
+
|
60
62
|
perform_initial_indexing
|
61
63
|
check_formatter_is_available
|
62
64
|
|
@@ -474,34 +476,32 @@ module RubyLsp
|
|
474
476
|
def completion(uri, position)
|
475
477
|
document = @store.get(uri)
|
476
478
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
else
|
486
|
-
[Prism::CallNode]
|
487
|
-
end
|
488
|
-
|
489
|
-
matched, parent, nesting = document.locate(document.tree, char_position, node_types: target_node_types)
|
479
|
+
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
480
|
+
# back by 1, so that we find the right node
|
481
|
+
char_position = document.create_scanner.find_char_position(position) - 1
|
482
|
+
matched, parent, nesting = document.locate(
|
483
|
+
document.tree,
|
484
|
+
char_position,
|
485
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
486
|
+
)
|
490
487
|
return unless matched && parent
|
491
488
|
|
492
489
|
target = case matched
|
493
490
|
when Prism::CallNode
|
494
491
|
message = matched.message
|
495
|
-
return unless message == "require"
|
496
492
|
|
497
|
-
|
498
|
-
|
493
|
+
if message == "require"
|
494
|
+
args = matched.arguments&.arguments
|
495
|
+
return if args.nil? || args.is_a?(Prism::ForwardingArgumentsNode)
|
499
496
|
|
500
|
-
|
501
|
-
|
502
|
-
|
497
|
+
argument = args.first
|
498
|
+
return unless argument.is_a?(Prism::StringNode)
|
499
|
+
return unless (argument.location.start_offset..argument.location.end_offset).cover?(char_position)
|
503
500
|
|
504
|
-
|
501
|
+
argument
|
502
|
+
else
|
503
|
+
matched
|
504
|
+
end
|
505
505
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
506
506
|
if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
|
507
507
|
parent
|
@@ -579,7 +579,7 @@ module RubyLsp
|
|
579
579
|
# notification
|
580
580
|
end
|
581
581
|
|
582
|
-
sig { params(options: T::Hash[Symbol, T.untyped]).returns(
|
582
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
583
583
|
def initialize_request(options)
|
584
584
|
@store.clear
|
585
585
|
|
@@ -680,7 +680,7 @@ module RubyLsp
|
|
680
680
|
completion_provider = if enabled_features["completion"]
|
681
681
|
Interface::CompletionOptions.new(
|
682
682
|
resolve_provider: false,
|
683
|
-
trigger_characters: ["/"
|
683
|
+
trigger_characters: ["/"],
|
684
684
|
completion_item: {
|
685
685
|
labelDetailsSupport: true,
|
686
686
|
},
|
@@ -716,7 +716,7 @@ module RubyLsp
|
|
716
716
|
|
717
717
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
718
718
|
|
719
|
-
|
719
|
+
{
|
720
720
|
capabilities: Interface::ServerCapabilities.new(
|
721
721
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
722
722
|
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
@@ -740,7 +740,12 @@ module RubyLsp
|
|
740
740
|
definition_provider: enabled_features["definition"],
|
741
741
|
workspace_symbol_provider: enabled_features["workspaceSymbol"],
|
742
742
|
),
|
743
|
-
|
743
|
+
serverInfo: {
|
744
|
+
name: "Ruby LSP",
|
745
|
+
version: VERSION,
|
746
|
+
},
|
747
|
+
formatter: @store.formatter,
|
748
|
+
}
|
744
749
|
end
|
745
750
|
|
746
751
|
sig { void }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -24,6 +24,10 @@ require "ruby_lsp/server"
|
|
24
24
|
require "ruby_lsp/executor"
|
25
25
|
require "ruby_lsp/requests"
|
26
26
|
require "ruby_lsp/listener"
|
27
|
+
require "ruby_lsp/document"
|
28
|
+
require "ruby_lsp/ruby_document"
|
27
29
|
require "ruby_lsp/store"
|
28
30
|
require "ruby_lsp/addon"
|
29
31
|
require "ruby_lsp/requests/support/rubocop_runner"
|
32
|
+
|
33
|
+
Bundler.ui.level = :silent
|
@@ -6,9 +6,14 @@ module RubyLsp
|
|
6
6
|
# ![Completion demo](../../completion.gif)
|
7
7
|
#
|
8
8
|
# The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
|
9
|
-
# suggests possible completions according to what the developer is typing.
|
10
|
-
#
|
11
|
-
#
|
9
|
+
# suggests possible completions according to what the developer is typing.
|
10
|
+
#
|
11
|
+
# Currently supported targets:
|
12
|
+
# - Classes
|
13
|
+
# - Modules
|
14
|
+
# - Constants
|
15
|
+
# - Require paths
|
16
|
+
# - Methods invoked on self only
|
12
17
|
#
|
13
18
|
# # Example
|
14
19
|
#
|
@@ -45,6 +50,7 @@ module RubyLsp
|
|
45
50
|
:on_string_node_enter,
|
46
51
|
:on_constant_path_node_enter,
|
47
52
|
:on_constant_read_node_enter,
|
53
|
+
:on_call_node_enter,
|
48
54
|
)
|
49
55
|
end
|
50
56
|
|
@@ -118,17 +124,60 @@ module RubyLsp
|
|
118
124
|
end
|
119
125
|
end
|
120
126
|
|
127
|
+
sig { params(node: Prism::CallNode).void }
|
128
|
+
def on_call_node_enter(node)
|
129
|
+
return if DependencyDetector.instance.typechecker
|
130
|
+
return unless self_receiver?(node)
|
131
|
+
|
132
|
+
name = node.message
|
133
|
+
return unless name
|
134
|
+
|
135
|
+
receiver_entries = @index[@nesting.join("::")]
|
136
|
+
return unless receiver_entries
|
137
|
+
|
138
|
+
receiver = T.must(receiver_entries.first)
|
139
|
+
|
140
|
+
candidates = T.cast(@index.prefix_search(name), T::Array[T::Array[RubyIndexer::Entry::Method]])
|
141
|
+
candidates.each do |entries|
|
142
|
+
entry = entries.find { |e| e.owner&.name == receiver.name }
|
143
|
+
next unless entry
|
144
|
+
|
145
|
+
@_response << build_method_completion(entry, node)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
121
149
|
private
|
122
150
|
|
151
|
+
sig { params(entry: RubyIndexer::Entry::Method, node: Prism::CallNode).returns(Interface::CompletionItem) }
|
152
|
+
def build_method_completion(entry, node)
|
153
|
+
name = entry.name
|
154
|
+
parameters = entry.parameters
|
155
|
+
new_text = parameters.empty? ? name : "#{name}(#{parameters.map(&:name).join(", ")})"
|
156
|
+
|
157
|
+
Interface::CompletionItem.new(
|
158
|
+
label: name,
|
159
|
+
filter_text: name,
|
160
|
+
text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: new_text),
|
161
|
+
kind: Constant::CompletionItemKind::METHOD,
|
162
|
+
label_details: Interface::CompletionItemLabelDetails.new(
|
163
|
+
description: entry.file_name,
|
164
|
+
),
|
165
|
+
documentation: markdown_from_index_entries(name, entry),
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
123
169
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
124
170
|
def build_completion(label, node)
|
171
|
+
# We should use the content location as we only replace the content and not the delimiters of the string
|
172
|
+
loc = node.content_loc
|
173
|
+
|
125
174
|
Interface::CompletionItem.new(
|
126
175
|
label: label,
|
127
176
|
text_edit: Interface::TextEdit.new(
|
128
|
-
range:
|
177
|
+
range: range_from_location(loc),
|
129
178
|
new_text: label,
|
130
179
|
),
|
131
|
-
kind: Constant::CompletionItemKind::
|
180
|
+
kind: Constant::CompletionItemKind::FILE,
|
132
181
|
)
|
133
182
|
end
|
134
183
|
|
@@ -9,7 +9,12 @@ module RubyLsp
|
|
9
9
|
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
|
10
10
|
# definition of the symbol under the cursor.
|
11
11
|
#
|
12
|
-
# Currently
|
12
|
+
# Currently supported targets:
|
13
|
+
# - Classes
|
14
|
+
# - Modules
|
15
|
+
# - Constants
|
16
|
+
# - Require paths
|
17
|
+
# - Methods invoked on self only
|
13
18
|
#
|
14
19
|
# # Example
|
15
20
|
#
|
@@ -75,8 +80,52 @@ module RubyLsp
|
|
75
80
|
sig { params(node: Prism::CallNode).void }
|
76
81
|
def on_call_node_enter(node)
|
77
82
|
message = node.name
|
78
|
-
return unless message == :require || message == :require_relative
|
79
83
|
|
84
|
+
if message == :require || message == :require_relative
|
85
|
+
handle_require_definition(node)
|
86
|
+
else
|
87
|
+
handle_method_definition(node)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
92
|
+
def on_constant_path_node_enter(node)
|
93
|
+
find_in_index(node.slice)
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { params(node: Prism::ConstantReadNode).void }
|
97
|
+
def on_constant_read_node_enter(node)
|
98
|
+
find_in_index(node.slice)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
sig { params(node: Prism::CallNode).void }
|
104
|
+
def handle_method_definition(node)
|
105
|
+
return unless self_receiver?(node)
|
106
|
+
|
107
|
+
message = node.message
|
108
|
+
return unless message
|
109
|
+
|
110
|
+
target_method = @index.resolve_method(message, @nesting.join("::"))
|
111
|
+
return unless target_method
|
112
|
+
|
113
|
+
location = target_method.location
|
114
|
+
file_path = target_method.file_path
|
115
|
+
return if defined_in_gem?(file_path)
|
116
|
+
|
117
|
+
@_response = Interface::Location.new(
|
118
|
+
uri: URI::Generic.from_path(path: file_path).to_s,
|
119
|
+
range: Interface::Range.new(
|
120
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
121
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
122
|
+
),
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
sig { params(node: Prism::CallNode).void }
|
127
|
+
def handle_require_definition(node)
|
128
|
+
message = node.name
|
80
129
|
arguments = node.arguments
|
81
130
|
return unless arguments
|
82
131
|
|
@@ -116,18 +165,6 @@ module RubyLsp
|
|
116
165
|
end
|
117
166
|
end
|
118
167
|
|
119
|
-
sig { params(node: Prism::ConstantPathNode).void }
|
120
|
-
def on_constant_path_node_enter(node)
|
121
|
-
find_in_index(node.slice)
|
122
|
-
end
|
123
|
-
|
124
|
-
sig { params(node: Prism::ConstantReadNode).void }
|
125
|
-
def on_constant_read_node_enter(node)
|
126
|
-
find_in_index(node.slice)
|
127
|
-
end
|
128
|
-
|
129
|
-
private
|
130
|
-
|
131
168
|
sig { params(value: String).void }
|
132
169
|
def find_in_index(value)
|
133
170
|
entries = @index.resolve(value, @nesting)
|
@@ -138,23 +175,13 @@ module RubyLsp
|
|
138
175
|
first_entry = T.must(entries.first)
|
139
176
|
return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
|
140
177
|
|
141
|
-
bundle_path = begin
|
142
|
-
Bundler.bundle_path.to_s
|
143
|
-
rescue Bundler::GemfileNotFound
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
178
|
@_response = entries.filter_map do |entry|
|
148
179
|
location = entry.location
|
149
180
|
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
150
181
|
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
151
182
|
# in the project, even if the files are typed false
|
152
183
|
file_path = entry.file_path
|
153
|
-
if
|
154
|
-
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
155
|
-
|
156
|
-
next
|
157
|
-
end
|
184
|
+
next if defined_in_gem?(file_path)
|
158
185
|
|
159
186
|
Interface::Location.new(
|
160
187
|
uri: URI::Generic.from_path(path: file_path).to_s,
|
@@ -51,6 +51,7 @@ module RubyLsp
|
|
51
51
|
:on_constant_read_node_enter,
|
52
52
|
:on_constant_write_node_enter,
|
53
53
|
:on_constant_path_node_enter,
|
54
|
+
:on_call_node_enter,
|
54
55
|
)
|
55
56
|
end
|
56
57
|
|
@@ -95,6 +96,25 @@ module RubyLsp
|
|
95
96
|
generate_hover(node.slice, node.location)
|
96
97
|
end
|
97
98
|
|
99
|
+
sig { params(node: Prism::CallNode).void }
|
100
|
+
def on_call_node_enter(node)
|
101
|
+
return if DependencyDetector.instance.typechecker
|
102
|
+
return unless self_receiver?(node)
|
103
|
+
|
104
|
+
message = node.message
|
105
|
+
return unless message
|
106
|
+
|
107
|
+
target_method = @index.resolve_method(message, @nesting.join("::"))
|
108
|
+
return unless target_method
|
109
|
+
|
110
|
+
location = target_method.location
|
111
|
+
|
112
|
+
@_response = Interface::Hover.new(
|
113
|
+
range: range_from_location(location),
|
114
|
+
contents: markdown_from_index_entries(message, target_method),
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
98
118
|
private
|
99
119
|
|
100
120
|
sig { params(name: String, location: Prism::Location).void }
|
@@ -18,6 +18,16 @@ module RubyLsp
|
|
18
18
|
# puts "handle some rescue"
|
19
19
|
# end
|
20
20
|
# ```
|
21
|
+
#
|
22
|
+
# # Example
|
23
|
+
#
|
24
|
+
# ```ruby
|
25
|
+
# var = "foo"
|
26
|
+
# {
|
27
|
+
# var: var, # Label "var" goes here in cases where the value is omitted
|
28
|
+
# a: "hello",
|
29
|
+
# }
|
30
|
+
# ```
|
21
31
|
class InlayHints < Listener
|
22
32
|
extend T::Sig
|
23
33
|
extend T::Generic
|
@@ -36,7 +46,7 @@ module RubyLsp
|
|
36
46
|
@_response = T.let([], ResponseType)
|
37
47
|
@range = range
|
38
48
|
|
39
|
-
dispatcher.register(self, :on_rescue_node_enter)
|
49
|
+
dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
|
40
50
|
end
|
41
51
|
|
42
52
|
sig { params(node: Prism::RescueNode).void }
|
@@ -53,6 +63,34 @@ module RubyLsp
|
|
53
63
|
tooltip: "StandardError is implied in a bare rescue",
|
54
64
|
)
|
55
65
|
end
|
66
|
+
|
67
|
+
sig { params(node: Prism::ImplicitNode).void }
|
68
|
+
def on_implicit_node_enter(node)
|
69
|
+
return unless visible?(node, @range)
|
70
|
+
|
71
|
+
node_value = node.value
|
72
|
+
loc = node.location
|
73
|
+
tooltip = ""
|
74
|
+
node_name = ""
|
75
|
+
case node_value
|
76
|
+
when Prism::CallNode
|
77
|
+
node_name = node_value.name
|
78
|
+
tooltip = "This is a method call. Method name: #{node_name}"
|
79
|
+
when Prism::ConstantReadNode
|
80
|
+
node_name = node_value.name
|
81
|
+
tooltip = "This is a constant: #{node_name}"
|
82
|
+
when Prism::LocalVariableReadNode
|
83
|
+
node_name = node_value.name
|
84
|
+
tooltip = "This is a local variable: #{node_name}"
|
85
|
+
end
|
86
|
+
|
87
|
+
@_response << Interface::InlayHint.new(
|
88
|
+
position: { line: loc.start_line - 1, character: loc.start_column + node_name.length + 1 },
|
89
|
+
label: node_name,
|
90
|
+
padding_left: true,
|
91
|
+
tooltip: tooltip,
|
92
|
+
)
|
93
|
+
end
|
56
94
|
end
|
57
95
|
end
|
58
96
|
end
|
@@ -51,7 +51,14 @@ module RubyLsp
|
|
51
51
|
if (comment_match = @previous_line.match(/^#(\s*)/))
|
52
52
|
handle_comment_line(T.must(comment_match[1]))
|
53
53
|
elsif @document.syntax_error?
|
54
|
-
|
54
|
+
match = /(?<=<<(-|~))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(@previous_line)
|
55
|
+
heredoc_delimiter = match && match.named_captures["delimiter"]
|
56
|
+
|
57
|
+
if heredoc_delimiter
|
58
|
+
handle_heredoc_end(heredoc_delimiter)
|
59
|
+
else
|
60
|
+
handle_statement_end
|
61
|
+
end
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
@@ -121,6 +128,14 @@ module RubyLsp
|
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
131
|
+
sig { params(delimiter: String).void }
|
132
|
+
def handle_heredoc_end(delimiter)
|
133
|
+
indents = " " * @indentation
|
134
|
+
add_edit_with_text("\n")
|
135
|
+
add_edit_with_text("#{indents}#{delimiter}")
|
136
|
+
move_cursor_to(@position[:line], @indentation + 2)
|
137
|
+
end
|
138
|
+
|
124
139
|
sig { params(spaces: String).void }
|
125
140
|
def handle_comment_line(spaces)
|
126
141
|
add_edit_with_text("##{spaces}")
|
@@ -9,6 +9,9 @@ module RubyLsp
|
|
9
9
|
# https://github.com/Shopify/ruby-lsp-rails, or addons by created by developers outside of Shopify, so be
|
10
10
|
# cautious of changing anything.
|
11
11
|
extend T::Sig
|
12
|
+
extend T::Helpers
|
13
|
+
|
14
|
+
requires_ancestor { Kernel }
|
12
15
|
|
13
16
|
sig { params(node: Prism::Node).returns(Interface::Range) }
|
14
17
|
def range_from_node(node)
|
@@ -66,12 +69,29 @@ module RubyLsp
|
|
66
69
|
)
|
67
70
|
end
|
68
71
|
|
69
|
-
sig { params(
|
72
|
+
sig { params(file_path: String).returns(T.nilable(T::Boolean)) }
|
73
|
+
def defined_in_gem?(file_path)
|
74
|
+
DependencyDetector.instance.typechecker && BUNDLE_PATH && !file_path.start_with?(T.must(BUNDLE_PATH)) &&
|
75
|
+
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(node: Prism::CallNode).returns(T::Boolean) }
|
79
|
+
def self_receiver?(node)
|
80
|
+
receiver = node.receiver
|
81
|
+
receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig do
|
85
|
+
params(
|
86
|
+
title: String,
|
87
|
+
entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
|
88
|
+
).returns(Interface::MarkupContent)
|
89
|
+
end
|
70
90
|
def markdown_from_index_entries(title, entries)
|
71
91
|
markdown_title = "```ruby\n#{title}\n```"
|
72
92
|
definitions = []
|
73
93
|
content = +""
|
74
|
-
entries.each do |entry|
|
94
|
+
Array(entries).each do |entry|
|
75
95
|
loc = entry.location
|
76
96
|
|
77
97
|
# We always handle locations as zero based. However, for file links in Markdown we need them to be one
|
@@ -66,7 +66,6 @@ module RubyLsp
|
|
66
66
|
|
67
67
|
sig { returns(T::Array[String]) }
|
68
68
|
def dependencies
|
69
|
-
# NOTE: If changing this behaviour, it's likely that the VS Code extension will also need changed.
|
70
69
|
@dependencies ||= T.let(
|
71
70
|
begin
|
72
71
|
Bundler.with_original_env { Bundler.default_gemfile }
|
@@ -20,6 +20,7 @@ module RubyLsp
|
|
20
20
|
#
|
21
21
|
class WorkspaceSymbol
|
22
22
|
extend T::Sig
|
23
|
+
include Support::Common
|
23
24
|
|
24
25
|
sig { params(query: T.nilable(String), index: RubyIndexer::Index).void }
|
25
26
|
def initialize(query, index)
|
@@ -29,21 +30,11 @@ module RubyLsp
|
|
29
30
|
|
30
31
|
sig { returns(T::Array[Interface::WorkspaceSymbol]) }
|
31
32
|
def run
|
32
|
-
bundle_path = begin
|
33
|
-
Bundler.bundle_path.to_s
|
34
|
-
rescue Bundler::GemfileNotFound
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
33
|
@index.fuzzy_search(@query).filter_map do |entry|
|
39
34
|
# If the project is using Sorbet, we let Sorbet handle symbols defined inside the project itself and RBIs, but
|
40
35
|
# we still return entries defined in gems to allow developers to jump directly to the source
|
41
36
|
file_path = entry.file_path
|
42
|
-
if
|
43
|
-
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
44
|
-
|
45
|
-
next
|
46
|
-
end
|
37
|
+
next if defined_in_gem?(file_path)
|
47
38
|
|
48
39
|
# We should never show private symbols when searching the entire workspace
|
49
40
|
next if entry.visibility == :private
|
@@ -82,6 +73,8 @@ module RubyLsp
|
|
82
73
|
Constant::SymbolKind::NAMESPACE
|
83
74
|
when RubyIndexer::Entry::Constant
|
84
75
|
Constant::SymbolKind::CONSTANT
|
76
|
+
when RubyIndexer::Entry::Method
|
77
|
+
entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
|
85
78
|
end
|
86
79
|
end
|
87
80
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class RubyDocument < Document
|
6
|
+
sig { override.returns(Prism::ParseResult) }
|
7
|
+
def parse
|
8
|
+
return @parse_result unless @needs_parsing
|
9
|
+
|
10
|
+
@needs_parsing = false
|
11
|
+
@parse_result = Prism.parse(@source)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -12,6 +12,8 @@ require "time"
|
|
12
12
|
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
|
13
13
|
# exact locked versions of dependencies.
|
14
14
|
|
15
|
+
Bundler.ui.level = :silent
|
16
|
+
|
15
17
|
module RubyLsp
|
16
18
|
class SetupBundler
|
17
19
|
extend T::Sig
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "ruby_lsp/document"
|
5
|
-
|
6
4
|
module RubyLsp
|
7
5
|
class Store
|
8
6
|
extend T::Sig
|
@@ -40,7 +38,7 @@ module RubyLsp
|
|
40
38
|
|
41
39
|
sig { params(uri: URI::Generic, source: String, version: Integer).void }
|
42
40
|
def set(uri:, source:, version:)
|
43
|
-
document =
|
41
|
+
document = RubyDocument.new(source: source, version: version, uri: uri, encoding: @encoding)
|
44
42
|
@state[uri.to_s] = document
|
45
43
|
end
|
46
44
|
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -8,6 +8,15 @@ module RubyLsp
|
|
8
8
|
# This freeze is not redundant since the interpolated string is mutable
|
9
9
|
WORKSPACE_URI = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
10
10
|
|
11
|
+
BUNDLE_PATH = T.let(
|
12
|
+
begin
|
13
|
+
Bundler.bundle_path.to_s
|
14
|
+
rescue Bundler::GemfileNotFound
|
15
|
+
nil
|
16
|
+
end,
|
17
|
+
T.nilable(String),
|
18
|
+
)
|
19
|
+
|
11
20
|
# A notification to be sent to the client
|
12
21
|
class Message
|
13
22
|
extend T::Sig
|
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.12.
|
4
|
+
version: 0.12.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -78,12 +78,12 @@ files:
|
|
78
78
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
79
79
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
80
80
|
- lib/ruby-lsp.rb
|
81
|
+
- lib/ruby_indexer/lib/ruby_indexer/collector.rb
|
81
82
|
- lib/ruby_indexer/lib/ruby_indexer/configuration.rb
|
82
83
|
- lib/ruby_indexer/lib/ruby_indexer/entry.rb
|
83
84
|
- lib/ruby_indexer/lib/ruby_indexer/index.rb
|
84
85
|
- lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
|
85
86
|
- lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
|
86
|
-
- lib/ruby_indexer/lib/ruby_indexer/visitor.rb
|
87
87
|
- lib/ruby_indexer/ruby_indexer.rb
|
88
88
|
- lib/ruby_indexer/test/classes_and_modules_test.rb
|
89
89
|
- lib/ruby_indexer/test/configuration_test.rb
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- lib/ruby_lsp/requests/support/source_uri.rb
|
133
133
|
- lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb
|
134
134
|
- lib/ruby_lsp/requests/workspace_symbol.rb
|
135
|
+
- lib/ruby_lsp/ruby_document.rb
|
135
136
|
- lib/ruby_lsp/server.rb
|
136
137
|
- lib/ruby_lsp/setup_bundler.rb
|
137
138
|
- lib/ruby_lsp/store.rb
|