ruby-lsp 0.17.4 → 0.17.6
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/README.md +4 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +40 -39
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +120 -25
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +266 -68
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +115 -34
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +33 -3
- data/lib/ruby_indexer/test/index_test.rb +248 -7
- data/lib/ruby_indexer/test/method_test.rb +58 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +259 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +13 -1
- data/lib/ruby_lsp/document.rb +13 -15
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +4 -1
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +26 -30
- data/lib/ruby_lsp/listeners/definition.rb +24 -17
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -7
- data/lib/ruby_lsp/requests/definition.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +10 -0
- data/lib/ruby_lsp/server.rb +41 -11
- data/lib/ruby_lsp/store.rb +23 -8
- data/lib/ruby_lsp/test_helper.rb +2 -0
- metadata +7 -6
@@ -63,5 +63,264 @@ module RubyIndexer
|
|
63
63
|
assert_instance_of(Entry::SingletonClass, owner)
|
64
64
|
assert_equal("File::<Class:File>", owner.name)
|
65
65
|
end
|
66
|
+
|
67
|
+
def test_location_and_name_location_are_the_same
|
68
|
+
# NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
|
69
|
+
# we would like, but for now we assign the same location to both
|
70
|
+
|
71
|
+
entries = @index["Array"]
|
72
|
+
refute_nil(entries)
|
73
|
+
entry = entries.find { |entry| entry.is_a?(Entry::Class) }
|
74
|
+
|
75
|
+
assert_same(entry.location, entry.name_location)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_rbs_method_with_required_positionals
|
79
|
+
entries = @index["crypt"]
|
80
|
+
assert_equal(1, entries.length)
|
81
|
+
|
82
|
+
entry = entries.first
|
83
|
+
signatures = entry.signatures
|
84
|
+
assert_equal(1, signatures.length)
|
85
|
+
|
86
|
+
first_signature = signatures.first
|
87
|
+
|
88
|
+
# (::string salt_str) -> ::String
|
89
|
+
|
90
|
+
assert_equal(1, first_signature.parameters.length)
|
91
|
+
assert_kind_of(Entry::RequiredParameter, first_signature.parameters[0])
|
92
|
+
assert_equal(:salt_str, first_signature.parameters[0].name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_rbs_method_with_unnamed_required_positionals
|
96
|
+
entries = @index["try_convert"]
|
97
|
+
entry = entries.find { |entry| entry.owner.name == "Array::<Class:Array>" }
|
98
|
+
|
99
|
+
parameters = entry.signatures[0].parameters
|
100
|
+
|
101
|
+
assert_equal([:arg0], parameters.map(&:name))
|
102
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_rbs_method_with_optional_positionals
|
106
|
+
entries = @index["polar"]
|
107
|
+
entry = entries.find { |entry| entry.owner.name == "Complex::<Class:Complex>" }
|
108
|
+
|
109
|
+
# def self.polar: (Numeric, ?Numeric) -> Complex
|
110
|
+
|
111
|
+
parameters = entry.signatures[0].parameters
|
112
|
+
|
113
|
+
assert_equal([:arg0, :arg1], parameters.map(&:name))
|
114
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
115
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_rbs_method_with_optional_parameter
|
119
|
+
entries = @index["chomp"]
|
120
|
+
assert_equal(1, entries.length)
|
121
|
+
|
122
|
+
entry = entries.first
|
123
|
+
signatures = entry.signatures
|
124
|
+
assert_equal(1, signatures.length)
|
125
|
+
|
126
|
+
first_signature = signatures.first
|
127
|
+
|
128
|
+
# (?::string? separator) -> ::String
|
129
|
+
|
130
|
+
assert_equal(1, first_signature.parameters.length)
|
131
|
+
assert_kind_of(Entry::OptionalParameter, first_signature.parameters[0])
|
132
|
+
assert_equal(:separator, first_signature.parameters[0].name)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_rbs_method_with_required_and_optional_parameters
|
136
|
+
entries = @index["gsub"]
|
137
|
+
assert_equal(1, entries.length)
|
138
|
+
|
139
|
+
entry = entries.first
|
140
|
+
|
141
|
+
signatures = entry.signatures
|
142
|
+
assert_equal(3, signatures.length)
|
143
|
+
|
144
|
+
# (::Regexp | ::string pattern, ::string | ::hash[::String, ::_ToS] replacement) -> ::String
|
145
|
+
# | (::Regexp | ::string pattern) -> ::Enumerator[::String, ::String]
|
146
|
+
# | (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String
|
147
|
+
|
148
|
+
parameters = signatures[0].parameters
|
149
|
+
assert_equal([:pattern, :replacement], parameters.map(&:name))
|
150
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
151
|
+
assert_kind_of(Entry::RequiredParameter, parameters[1])
|
152
|
+
|
153
|
+
parameters = signatures[1].parameters
|
154
|
+
assert_equal([:pattern], parameters.map(&:name))
|
155
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
156
|
+
|
157
|
+
parameters = signatures[2].parameters
|
158
|
+
assert_equal([:pattern, :"<anonymous block>"], parameters.map(&:name))
|
159
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
160
|
+
assert_kind_of(Entry::BlockParameter, parameters[1])
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_rbs_anonymous_block_parameter
|
164
|
+
entries = @index["open"]
|
165
|
+
entry = entries.find { |entry| entry.owner.name == "File::<Class:File>" }
|
166
|
+
|
167
|
+
assert_equal(2, entry.signatures.length)
|
168
|
+
|
169
|
+
# (::String name, ?::String mode, ?::Integer perm) -> ::IO?
|
170
|
+
# | [T] (::String name, ?::String mode, ?::Integer perm) { (::IO) -> T } -> T
|
171
|
+
|
172
|
+
parameters = entry.signatures[0].parameters
|
173
|
+
assert_equal([:file_name, :mode, :perm], parameters.map(&:name))
|
174
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
175
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
176
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
177
|
+
|
178
|
+
parameters = entry.signatures[1].parameters
|
179
|
+
assert_equal([:file_name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
|
180
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
181
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
182
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
183
|
+
assert_kind_of(Entry::BlockParameter, parameters[3])
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_rbs_method_with_rest_positionals
|
187
|
+
entries = @index["count"]
|
188
|
+
entry = entries.find { |entry| entry.owner.name == "String" }
|
189
|
+
|
190
|
+
parameters = entry.signatures.first.parameters
|
191
|
+
assert_equal(1, entry.signatures.length)
|
192
|
+
|
193
|
+
# (::String::selector selector_0, *::String::selector more_selectors) -> ::Integer
|
194
|
+
|
195
|
+
assert_equal([:selector_0, :more_selectors], parameters.map(&:name))
|
196
|
+
assert_kind_of(RubyIndexer::Entry::RequiredParameter, parameters[0])
|
197
|
+
assert_kind_of(RubyIndexer::Entry::RestParameter, parameters[1])
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_rbs_method_with_trailing_positionals
|
201
|
+
entries = @index["select"] # https://ruby-doc.org/3.3.3/IO.html#method-c-select
|
202
|
+
entry = entries.find { |entry| entry.owner.name == "IO::<Class:IO>" }
|
203
|
+
|
204
|
+
signatures = entry.signatures
|
205
|
+
assert_equal(2, signatures.length)
|
206
|
+
|
207
|
+
# def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] # rubocop:disable Layout/LineLength
|
208
|
+
# | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? # rubocop:disable Layout/LineLength
|
209
|
+
|
210
|
+
parameters = signatures[0].parameters
|
211
|
+
assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
|
212
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
213
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
214
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
215
|
+
|
216
|
+
parameters = signatures[1].parameters
|
217
|
+
assert_equal([:read_array, :write_array, :error_array, :timeout], parameters.map(&:name))
|
218
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
219
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
220
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
221
|
+
assert_kind_of(Entry::OptionalParameter, parameters[3])
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_rbs_method_with_optional_keywords
|
225
|
+
entries = @index["step"]
|
226
|
+
entry = entries.find { |entry| entry.owner.name == "Numeric" }
|
227
|
+
|
228
|
+
signatures = entry.signatures
|
229
|
+
assert_equal(4, signatures.length)
|
230
|
+
|
231
|
+
# (?::Numeric limit, ?::Numeric step) { (::Numeric) -> void } -> self
|
232
|
+
# | (?::Numeric limit, ?::Numeric step) -> ::Enumerator[::Numeric, self]
|
233
|
+
# | (?by: ::Numeric, ?to: ::Numeric) { (::Numeric) -> void } -> self
|
234
|
+
# | (?by: ::Numeric, ?to: ::Numeric) -> ::Enumerator[::Numeric, self]
|
235
|
+
|
236
|
+
parameters = signatures[0].parameters
|
237
|
+
assert_equal([:limit, :step, :"<anonymous block>"], parameters.map(&:name))
|
238
|
+
assert_kind_of(Entry::OptionalParameter, parameters[0])
|
239
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
240
|
+
assert_kind_of(Entry::BlockParameter, parameters[2])
|
241
|
+
|
242
|
+
parameters = signatures[1].parameters
|
243
|
+
assert_equal([:limit, :step], parameters.map(&:name))
|
244
|
+
assert_kind_of(Entry::OptionalParameter, parameters[0])
|
245
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
246
|
+
|
247
|
+
parameters = signatures[2].parameters
|
248
|
+
assert_equal([:by, :to, :"<anonymous block>"], parameters.map(&:name))
|
249
|
+
assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
|
250
|
+
assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
|
251
|
+
assert_kind_of(Entry::BlockParameter, parameters[2])
|
252
|
+
|
253
|
+
parameters = signatures[3].parameters
|
254
|
+
assert_equal([:by, :to], parameters.map(&:name))
|
255
|
+
assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
|
256
|
+
assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_rbs_method_with_required_keywords
|
260
|
+
# There are no methods in Core that have required keyword arguments,
|
261
|
+
# so we test against RBS directly
|
262
|
+
|
263
|
+
rbs = <<~RBS
|
264
|
+
class File
|
265
|
+
def foo: (a: ::Numeric sz, b: ::Numeric) -> void
|
266
|
+
end
|
267
|
+
RBS
|
268
|
+
signatures = parse_rbs_methods(rbs, "foo")
|
269
|
+
parameters = signatures[0].parameters
|
270
|
+
assert_equal([:a, :b], parameters.map(&:name))
|
271
|
+
assert_kind_of(Entry::KeywordParameter, parameters[0])
|
272
|
+
assert_kind_of(Entry::KeywordParameter, parameters[1])
|
273
|
+
end
|
274
|
+
|
275
|
+
def test_rbs_method_with_rest_keywords
|
276
|
+
entries = @index["method_missing"]
|
277
|
+
entry = entries.find { |entry| entry.owner.name == "BasicObject" }
|
278
|
+
signatures = entry.signatures
|
279
|
+
assert_equal(1, signatures.length)
|
280
|
+
|
281
|
+
# (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
|
282
|
+
|
283
|
+
parameters = signatures[0].parameters
|
284
|
+
assert_equal([:arg0, :"<anonymous splat>", :"<anonymous keyword splat>"], parameters.map(&:name))
|
285
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
286
|
+
assert_kind_of(Entry::RestParameter, parameters[1])
|
287
|
+
assert_kind_of(Entry::KeywordRestParameter, parameters[2])
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_parse_simple_rbs
|
291
|
+
rbs = <<~RBS
|
292
|
+
class File
|
293
|
+
def self?.open: (String name, ?String mode, ?Integer perm) -> IO?
|
294
|
+
| [T] (String name, ?String mode, ?Integer perm) { (IO) -> T } -> T
|
295
|
+
end
|
296
|
+
RBS
|
297
|
+
signatures = parse_rbs_methods(rbs, "open")
|
298
|
+
assert_equal(2, signatures.length)
|
299
|
+
parameters = signatures[0].parameters
|
300
|
+
assert_equal([:name, :mode, :perm], parameters.map(&:name))
|
301
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
302
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
303
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
304
|
+
|
305
|
+
parameters = signatures[1].parameters
|
306
|
+
assert_equal([:name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
|
307
|
+
assert_kind_of(Entry::RequiredParameter, parameters[0])
|
308
|
+
assert_kind_of(Entry::OptionalParameter, parameters[1])
|
309
|
+
assert_kind_of(Entry::OptionalParameter, parameters[2])
|
310
|
+
assert_kind_of(Entry::BlockParameter, parameters[3])
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
def parse_rbs_methods(rbs, method_name)
|
316
|
+
buffer = RBS::Buffer.new(content: rbs, name: "")
|
317
|
+
_, _, declarations = RBS::Parser.parse_signature(buffer)
|
318
|
+
index = RubyIndexer::Index.new
|
319
|
+
indexer = RubyIndexer::RBSIndexer.new(index)
|
320
|
+
pathname = Pathname.new("file.rbs")
|
321
|
+
indexer.process_signature(pathname, declarations)
|
322
|
+
entry = T.must(index[method_name]).first
|
323
|
+
T.cast(entry, Entry::Method).signatures
|
324
|
+
end
|
66
325
|
end
|
67
326
|
end
|
@@ -40,16 +40,12 @@ module RubyIndexer
|
|
40
40
|
assert_nil(entries, "Expected #{expected_name} to not be indexed")
|
41
41
|
end
|
42
42
|
|
43
|
-
def assert_no_entries
|
44
|
-
assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
|
45
|
-
end
|
46
|
-
|
47
43
|
def assert_no_indexed_entries
|
48
44
|
assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
|
49
45
|
end
|
50
46
|
|
51
47
|
def assert_no_entry(entry)
|
52
|
-
refute(@index.
|
48
|
+
refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -72,6 +72,15 @@ module RubyLsp
|
|
72
72
|
addon.add_error(e)
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
# Intended for use by tests for addons
|
77
|
+
sig { params(addon_name: String).returns(Addon) }
|
78
|
+
def get(addon_name)
|
79
|
+
addon = addons.find { |addon| addon.name == addon_name }
|
80
|
+
raise "Could not find addon '#{addon_name}'" unless addon
|
81
|
+
|
82
|
+
addon
|
83
|
+
end
|
75
84
|
end
|
76
85
|
|
77
86
|
sig { void }
|
@@ -157,7 +166,10 @@ module RubyLsp
|
|
157
166
|
# Creates a new Definition listener. This method is invoked on every Definition request
|
158
167
|
sig do
|
159
168
|
overridable.params(
|
160
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[
|
169
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
|
170
|
+
Interface::Location,
|
171
|
+
Interface::LocationLink,
|
172
|
+
)],
|
161
173
|
uri: URI::Generic,
|
162
174
|
node_context: NodeContext,
|
163
175
|
dispatcher: Prism::Dispatcher,
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
class Document
|
6
|
+
class LanguageId < T::Enum
|
7
|
+
enums do
|
8
|
+
Ruby = new("ruby")
|
9
|
+
ERB = new("erb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
6
13
|
extend T::Sig
|
7
14
|
extend T::Helpers
|
8
15
|
|
@@ -34,21 +41,14 @@ module RubyLsp
|
|
34
41
|
@parse_result = T.let(parse, Prism::ParseResult)
|
35
42
|
end
|
36
43
|
|
37
|
-
sig { returns(Prism::ProgramNode) }
|
38
|
-
def tree
|
39
|
-
@parse_result.value
|
40
|
-
end
|
41
|
-
|
42
|
-
sig { returns(T::Array[Prism::Comment]) }
|
43
|
-
def comments
|
44
|
-
@parse_result.comments
|
45
|
-
end
|
46
|
-
|
47
44
|
sig { params(other: Document).returns(T::Boolean) }
|
48
45
|
def ==(other)
|
49
|
-
@source == other.source
|
46
|
+
self.class == other.class && uri == other.uri && @source == other.source
|
50
47
|
end
|
51
48
|
|
49
|
+
sig { abstract.returns(LanguageId) }
|
50
|
+
def language_id; end
|
51
|
+
|
52
52
|
# TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
|
53
53
|
sig do
|
54
54
|
type_parameters(:T)
|
@@ -96,10 +96,8 @@ module RubyLsp
|
|
96
96
|
sig { abstract.returns(Prism::ParseResult) }
|
97
97
|
def parse; end
|
98
98
|
|
99
|
-
sig { returns(T::Boolean) }
|
100
|
-
def syntax_error
|
101
|
-
@parse_result.failure?
|
102
|
-
end
|
99
|
+
sig { abstract.returns(T::Boolean) }
|
100
|
+
def syntax_error?; end
|
103
101
|
|
104
102
|
sig { returns(Scanner) }
|
105
103
|
def create_scanner
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class ERBDocument < Document
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { override.returns(Prism::ParseResult) }
|
9
|
+
def parse
|
10
|
+
return @parse_result unless @needs_parsing
|
11
|
+
|
12
|
+
@needs_parsing = false
|
13
|
+
scanner = ERBScanner.new(@source)
|
14
|
+
scanner.scan
|
15
|
+
@parse_result = Prism.parse(scanner.ruby)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.returns(T::Boolean) }
|
19
|
+
def syntax_error?
|
20
|
+
@parse_result.failure?
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.returns(LanguageId) }
|
24
|
+
def language_id
|
25
|
+
LanguageId::ERB
|
26
|
+
end
|
27
|
+
|
28
|
+
class ERBScanner
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
attr_reader :ruby, :html
|
33
|
+
|
34
|
+
sig { params(source: String).void }
|
35
|
+
def initialize(source)
|
36
|
+
@source = source
|
37
|
+
@html = T.let(+"", String)
|
38
|
+
@ruby = T.let(+"", String)
|
39
|
+
@current_pos = T.let(0, Integer)
|
40
|
+
@inside_ruby = T.let(false, T::Boolean)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { void }
|
44
|
+
def scan
|
45
|
+
while @current_pos < @source.length
|
46
|
+
scan_char
|
47
|
+
@current_pos += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
sig { void }
|
54
|
+
def scan_char
|
55
|
+
char = @source[@current_pos]
|
56
|
+
|
57
|
+
case char
|
58
|
+
when "<"
|
59
|
+
if next_char == "%"
|
60
|
+
@inside_ruby = true
|
61
|
+
@current_pos += 1
|
62
|
+
push_char(" ")
|
63
|
+
|
64
|
+
if next_char == "=" && @source[@current_pos + 2] == "="
|
65
|
+
@current_pos += 2
|
66
|
+
push_char(" ")
|
67
|
+
elsif next_char == "=" || next_char == "-"
|
68
|
+
@current_pos += 1
|
69
|
+
push_char(" ")
|
70
|
+
end
|
71
|
+
else
|
72
|
+
push_char(T.must(char))
|
73
|
+
end
|
74
|
+
when "-"
|
75
|
+
if @inside_ruby && next_char == "%" &&
|
76
|
+
@source[@current_pos + 2] == ">"
|
77
|
+
@current_pos += 2
|
78
|
+
push_char(" ")
|
79
|
+
@inside_ruby = false
|
80
|
+
else
|
81
|
+
push_char(T.must(char))
|
82
|
+
end
|
83
|
+
when "%"
|
84
|
+
if @inside_ruby && next_char == ">"
|
85
|
+
@inside_ruby = false
|
86
|
+
@current_pos += 1
|
87
|
+
push_char(" ")
|
88
|
+
else
|
89
|
+
push_char(T.must(char))
|
90
|
+
end
|
91
|
+
when "\r"
|
92
|
+
@ruby << char
|
93
|
+
@html << char
|
94
|
+
|
95
|
+
if next_char == "\n"
|
96
|
+
@ruby << next_char
|
97
|
+
@html << next_char
|
98
|
+
@current_pos += 1
|
99
|
+
end
|
100
|
+
when "\n"
|
101
|
+
@ruby << char
|
102
|
+
@html << char
|
103
|
+
else
|
104
|
+
push_char(T.must(char))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(char: String).void }
|
109
|
+
def push_char(char)
|
110
|
+
if @inside_ruby
|
111
|
+
@ruby << char
|
112
|
+
@html << " " * char.length
|
113
|
+
else
|
114
|
+
@ruby << " " * char.length
|
115
|
+
@html << char
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { returns(String) }
|
120
|
+
def next_char
|
121
|
+
@source[@current_pos + 1] || ""
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
attr_reader :encoding
|
22
22
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
|
-
attr_reader :supports_watching_files
|
24
|
+
attr_reader :supports_watching_files, :experimental_features
|
25
25
|
|
26
26
|
sig { returns(TypeInferrer) }
|
27
27
|
attr_reader :type_inferrer
|
@@ -39,6 +39,7 @@ module RubyLsp
|
|
39
39
|
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
40
40
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
41
41
|
@supports_watching_files = T.let(false, T::Boolean)
|
42
|
+
@experimental_features = T.let(false, T::Boolean)
|
42
43
|
end
|
43
44
|
|
44
45
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -87,6 +88,8 @@ module RubyLsp
|
|
87
88
|
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
88
89
|
@supports_watching_files = true
|
89
90
|
end
|
91
|
+
|
92
|
+
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
90
93
|
end
|
91
94
|
|
92
95
|
sig { returns(String) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -15,6 +15,7 @@ Bundler.ui.level = :silent
|
|
15
15
|
require "uri"
|
16
16
|
require "cgi"
|
17
17
|
require "set"
|
18
|
+
require "strscan"
|
18
19
|
require "prism"
|
19
20
|
require "prism/visitor"
|
20
21
|
require "language_server-protocol"
|
@@ -34,6 +35,7 @@ require "ruby_lsp/response_builders"
|
|
34
35
|
require "ruby_lsp/node_context"
|
35
36
|
require "ruby_lsp/document"
|
36
37
|
require "ruby_lsp/ruby_document"
|
38
|
+
require "ruby_lsp/erb_document"
|
37
39
|
require "ruby_lsp/store"
|
38
40
|
require "ruby_lsp/addon"
|
39
41
|
require "ruby_lsp/requests/support/rubocop_runner"
|
@@ -209,8 +209,13 @@ module RubyLsp
|
|
209
209
|
@index.instance_variable_completion_candidates(name, type).each do |entry|
|
210
210
|
variable_name = entry.name
|
211
211
|
|
212
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
213
|
+
description: entry.file_name,
|
214
|
+
)
|
215
|
+
|
212
216
|
@response_builder << Interface::CompletionItem.new(
|
213
217
|
label: variable_name,
|
218
|
+
label_details: label_details,
|
214
219
|
text_edit: Interface::TextEdit.new(
|
215
220
|
range: range_from_location(location),
|
216
221
|
new_text: variable_name,
|
@@ -283,19 +288,29 @@ module RubyLsp
|
|
283
288
|
range = if method_name
|
284
289
|
range_from_location(T.must(node.message_loc))
|
285
290
|
else
|
286
|
-
loc =
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
+
loc = node.call_operator_loc
|
292
|
+
|
293
|
+
if loc
|
294
|
+
Interface::Range.new(
|
295
|
+
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
296
|
+
end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
297
|
+
)
|
298
|
+
end
|
291
299
|
end
|
292
300
|
|
301
|
+
return unless range
|
302
|
+
|
293
303
|
@index.method_completion_candidates(method_name, type).each do |entry|
|
294
304
|
entry_name = entry.name
|
295
305
|
|
306
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
307
|
+
description: entry.file_name,
|
308
|
+
detail: entry.decorated_parameters,
|
309
|
+
)
|
296
310
|
@response_builder << Interface::CompletionItem.new(
|
297
311
|
label: entry_name,
|
298
312
|
filter_text: entry_name,
|
313
|
+
label_details: label_details,
|
299
314
|
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
|
300
315
|
kind: Constant::CompletionItemKind::METHOD,
|
301
316
|
data: {
|
@@ -307,31 +322,6 @@ module RubyLsp
|
|
307
322
|
# We have not indexed this namespace, so we can't provide any completions
|
308
323
|
end
|
309
324
|
|
310
|
-
sig do
|
311
|
-
params(
|
312
|
-
entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
|
313
|
-
node: Prism::CallNode,
|
314
|
-
).returns(Interface::CompletionItem)
|
315
|
-
end
|
316
|
-
def build_method_completion(entry, node)
|
317
|
-
name = entry.name
|
318
|
-
|
319
|
-
Interface::CompletionItem.new(
|
320
|
-
label: name,
|
321
|
-
filter_text: name,
|
322
|
-
text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
|
323
|
-
kind: Constant::CompletionItemKind::METHOD,
|
324
|
-
label_details: Interface::CompletionItemLabelDetails.new(
|
325
|
-
detail: entry.decorated_parameters,
|
326
|
-
description: entry.file_name,
|
327
|
-
),
|
328
|
-
documentation: Interface::MarkupContent.new(
|
329
|
-
kind: "markdown",
|
330
|
-
value: markdown_from_index_entries(name, entry),
|
331
|
-
),
|
332
|
-
)
|
333
|
-
end
|
334
|
-
|
335
325
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
336
326
|
def build_completion(label, node)
|
337
327
|
# We should use the content location as we only replace the content and not the delimiters of the string
|
@@ -413,8 +403,14 @@ module RubyLsp
|
|
413
403
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
414
404
|
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
415
405
|
# the right entries in the index
|
406
|
+
|
407
|
+
label_details = Interface::CompletionItemLabelDetails.new(
|
408
|
+
description: entries.map(&:file_name).join(","),
|
409
|
+
)
|
410
|
+
|
416
411
|
Interface::CompletionItem.new(
|
417
412
|
label: real_name,
|
413
|
+
label_details: label_details,
|
418
414
|
filter_text: filter_text,
|
419
415
|
text_edit: Interface::TextEdit.new(
|
420
416
|
range: range,
|