ruby-lsp 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +80 -3
- data/lib/ruby_indexer/test/method_test.rb +136 -0
- data/lib/ruby_lsp/document.rb +6 -7
- data/lib/ruby_lsp/executor.rb +69 -14
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/code_actions.rb +4 -18
- data/lib/ruby_lsp/requests/code_lens.rb +15 -2
- data/lib/ruby_lsp/requests/completion.rb +1 -3
- data/lib/ruby_lsp/requests/inlay_hints.rb +19 -2
- data/lib/ruby_lsp/requests/on_type_formatting.rb +31 -3
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +95 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +92 -21
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/store.rb +18 -1
- data/lib/ruby_lsp/utils.rb +18 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2329129b74c75a0bb4113d1f1489463f666b74ef495d0ac65bad675fbd83d1d3
|
4
|
+
data.tar.gz: 2f31f495b9ca0b1a9f909758dabce816411d0b64a006577c93c7a80a253791e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c3ab34cb3e7877105ca21df3f521589d9838bd15e77a3d17cc4df91ca1f93221ef845e514a902a03a63a1f82ad6b5cf90fa4f9a93037fbfb57af5f7a48b0ebe
|
7
|
+
data.tar.gz: d4867c86eb238abf039f40592d7ff48acd85749fcf9349ab72fdea218ff5201156fc0d511987ed768b1fb2dcc9fd55a76bfac611f3fc565b7b2ba8c8d1054a72
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.13.
|
1
|
+
0.13.2
|
@@ -193,7 +193,7 @@ module RubyIndexer
|
|
193
193
|
# When working on a gem, we need to make sure that its gemspec dependencies can't be excluded. This is necessary
|
194
194
|
# because Bundler doesn't assign groups to gemspec dependencies
|
195
195
|
this_gem = Bundler.definition.dependencies.find do |d|
|
196
|
-
d.to_spec
|
196
|
+
d.to_spec&.full_gem_path == Dir.pwd
|
197
197
|
rescue Gem::MissingSpecError
|
198
198
|
false
|
199
199
|
end
|
@@ -81,9 +81,13 @@ module RubyIndexer
|
|
81
81
|
|
82
82
|
abstract!
|
83
83
|
|
84
|
+
# Name includes just the name of the parameter, excluding symbols like splats
|
84
85
|
sig { returns(Symbol) }
|
85
86
|
attr_reader :name
|
86
87
|
|
88
|
+
# Decorated name is the parameter name including the splat or block prefix, e.g.: `*foo`, `**foo` or `&block`
|
89
|
+
alias_method :decorated_name, :name
|
90
|
+
|
87
91
|
sig { params(name: Symbol).void }
|
88
92
|
def initialize(name:)
|
89
93
|
@name = name
|
@@ -100,10 +104,48 @@ module RubyIndexer
|
|
100
104
|
|
101
105
|
# An required keyword method parameter, e.g. `def foo(a:)`
|
102
106
|
class KeywordParameter < Parameter
|
107
|
+
sig { override.returns(Symbol) }
|
108
|
+
def decorated_name
|
109
|
+
:"#{@name}:"
|
110
|
+
end
|
103
111
|
end
|
104
112
|
|
105
113
|
# An optional keyword method parameter, e.g. `def foo(a: 123)`
|
106
114
|
class OptionalKeywordParameter < Parameter
|
115
|
+
sig { override.returns(Symbol) }
|
116
|
+
def decorated_name
|
117
|
+
:"#{@name}:"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# A rest method parameter, e.g. `def foo(*a)`
|
122
|
+
class RestParameter < Parameter
|
123
|
+
DEFAULT_NAME = T.let(:"<anonymous splat>", Symbol)
|
124
|
+
|
125
|
+
sig { override.returns(Symbol) }
|
126
|
+
def decorated_name
|
127
|
+
:"*#{@name}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# A keyword rest method parameter, e.g. `def foo(**a)`
|
132
|
+
class KeywordRestParameter < Parameter
|
133
|
+
DEFAULT_NAME = T.let(:"<anonymous keyword splat>", Symbol)
|
134
|
+
|
135
|
+
sig { override.returns(Symbol) }
|
136
|
+
def decorated_name
|
137
|
+
:"**#{@name}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# A block method parameter, e.g. `def foo(&block)`
|
142
|
+
class BlockParameter < Parameter
|
143
|
+
DEFAULT_NAME = T.let(:"<anonymous block>", Symbol)
|
144
|
+
|
145
|
+
sig { override.returns(Symbol) }
|
146
|
+
def decorated_name
|
147
|
+
:"&#{@name}"
|
148
|
+
end
|
107
149
|
end
|
108
150
|
|
109
151
|
class Member < Entry
|
@@ -203,17 +245,52 @@ module RubyIndexer
|
|
203
245
|
end
|
204
246
|
end
|
205
247
|
|
248
|
+
rest = parameters_node.rest
|
249
|
+
|
250
|
+
if rest.is_a?(Prism::RestParameterNode)
|
251
|
+
rest_name = rest.name || RestParameter::DEFAULT_NAME
|
252
|
+
parameters << RestParameter.new(name: rest_name)
|
253
|
+
end
|
254
|
+
|
255
|
+
keyword_rest = parameters_node.keyword_rest
|
256
|
+
|
257
|
+
if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
|
258
|
+
keyword_rest_name = parameter_name(keyword_rest) || KeywordRestParameter::DEFAULT_NAME
|
259
|
+
parameters << KeywordRestParameter.new(name: keyword_rest_name)
|
260
|
+
end
|
261
|
+
|
262
|
+
parameters_node.posts.each do |post|
|
263
|
+
name = parameter_name(post)
|
264
|
+
next unless name
|
265
|
+
|
266
|
+
parameters << RequiredParameter.new(name: name)
|
267
|
+
end
|
268
|
+
|
269
|
+
block = parameters_node.block
|
270
|
+
parameters << BlockParameter.new(name: block.name || BlockParameter::DEFAULT_NAME) if block
|
271
|
+
|
206
272
|
parameters
|
207
273
|
end
|
208
274
|
|
209
|
-
sig { params(node: Prism::Node).returns(T.nilable(Symbol)) }
|
275
|
+
sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
|
210
276
|
def parameter_name(node)
|
211
277
|
case node
|
212
278
|
when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
|
213
|
-
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode
|
279
|
+
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
|
280
|
+
Prism::RestParameterNode, Prism::KeywordRestParameterNode
|
214
281
|
node.name
|
215
282
|
when Prism::MultiTargetNode
|
216
|
-
names =
|
283
|
+
names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
|
284
|
+
|
285
|
+
rest = node.rest
|
286
|
+
if rest.is_a?(Prism::SplatNode)
|
287
|
+
name = rest.expression&.slice
|
288
|
+
names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
|
289
|
+
end
|
290
|
+
|
291
|
+
names << nil if rest.is_a?(Prism::ImplicitRestNode)
|
292
|
+
|
293
|
+
names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
|
217
294
|
|
218
295
|
names_with_commas = names.join(", ")
|
219
296
|
:"(#{names_with_commas})"
|
@@ -106,6 +106,142 @@ module RubyIndexer
|
|
106
106
|
assert_instance_of(Entry::OptionalKeywordParameter, b)
|
107
107
|
end
|
108
108
|
|
109
|
+
def test_method_with_rest_and_keyword_rest_parameters
|
110
|
+
index(<<~RUBY)
|
111
|
+
class Foo
|
112
|
+
def bar(*a, **b)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
RUBY
|
116
|
+
|
117
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
118
|
+
entry = T.must(@index["bar"].first)
|
119
|
+
assert_equal(2, entry.parameters.length)
|
120
|
+
a, b = entry.parameters
|
121
|
+
|
122
|
+
assert_equal(:a, a.name)
|
123
|
+
assert_instance_of(Entry::RestParameter, a)
|
124
|
+
|
125
|
+
assert_equal(:b, b.name)
|
126
|
+
assert_instance_of(Entry::KeywordRestParameter, b)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_method_with_post_parameters
|
130
|
+
index(<<~RUBY)
|
131
|
+
class Foo
|
132
|
+
def bar(*a, b)
|
133
|
+
end
|
134
|
+
|
135
|
+
def baz(**a, b)
|
136
|
+
end
|
137
|
+
|
138
|
+
def qux(*a, (b, c))
|
139
|
+
end
|
140
|
+
RUBY
|
141
|
+
|
142
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
143
|
+
entry = T.must(@index["bar"].first)
|
144
|
+
assert_equal(2, entry.parameters.length)
|
145
|
+
a, b = entry.parameters
|
146
|
+
|
147
|
+
assert_equal(:a, a.name)
|
148
|
+
assert_instance_of(Entry::RestParameter, a)
|
149
|
+
|
150
|
+
assert_equal(:b, b.name)
|
151
|
+
assert_instance_of(Entry::RequiredParameter, b)
|
152
|
+
|
153
|
+
entry = T.must(@index["baz"].first)
|
154
|
+
assert_equal(2, entry.parameters.length)
|
155
|
+
a, b = entry.parameters
|
156
|
+
|
157
|
+
assert_equal(:a, a.name)
|
158
|
+
assert_instance_of(Entry::KeywordRestParameter, a)
|
159
|
+
|
160
|
+
assert_equal(:b, b.name)
|
161
|
+
assert_instance_of(Entry::RequiredParameter, b)
|
162
|
+
|
163
|
+
entry = T.must(@index["qux"].first)
|
164
|
+
assert_equal(2, entry.parameters.length)
|
165
|
+
_a, second = entry.parameters
|
166
|
+
|
167
|
+
assert_equal(:"(b, c)", second.name)
|
168
|
+
assert_instance_of(Entry::RequiredParameter, second)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_method_with_destructured_rest_parameters
|
172
|
+
index(<<~RUBY)
|
173
|
+
class Foo
|
174
|
+
def bar((a, *b))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
RUBY
|
178
|
+
|
179
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
180
|
+
entry = T.must(@index["bar"].first)
|
181
|
+
assert_equal(1, entry.parameters.length)
|
182
|
+
param = entry.parameters.first
|
183
|
+
|
184
|
+
assert_equal(:"(a, *b)", param.name)
|
185
|
+
assert_instance_of(Entry::RequiredParameter, param)
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_method_with_block_parameters
|
189
|
+
index(<<~RUBY)
|
190
|
+
class Foo
|
191
|
+
def bar(&block)
|
192
|
+
end
|
193
|
+
|
194
|
+
def baz(&)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
RUBY
|
198
|
+
|
199
|
+
entry = T.must(@index["bar"].first)
|
200
|
+
param = entry.parameters.first
|
201
|
+
assert_equal(:block, param.name)
|
202
|
+
assert_instance_of(Entry::BlockParameter, param)
|
203
|
+
|
204
|
+
entry = T.must(@index["baz"].first)
|
205
|
+
assert_equal(1, entry.parameters.length)
|
206
|
+
|
207
|
+
param = entry.parameters.first
|
208
|
+
assert_equal(Entry::BlockParameter::DEFAULT_NAME, param.name)
|
209
|
+
assert_instance_of(Entry::BlockParameter, param)
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_method_with_anonymous_rest_parameters
|
213
|
+
index(<<~RUBY)
|
214
|
+
class Foo
|
215
|
+
def bar(*, **)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
RUBY
|
219
|
+
|
220
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
221
|
+
entry = T.must(@index["bar"].first)
|
222
|
+
assert_equal(2, entry.parameters.length)
|
223
|
+
first, second = entry.parameters
|
224
|
+
|
225
|
+
assert_equal(Entry::RestParameter::DEFAULT_NAME, first.name)
|
226
|
+
assert_instance_of(Entry::RestParameter, first)
|
227
|
+
|
228
|
+
assert_equal(Entry::KeywordRestParameter::DEFAULT_NAME, second.name)
|
229
|
+
assert_instance_of(Entry::KeywordRestParameter, second)
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_method_with_forbidden_keyword_splat_parameter
|
233
|
+
index(<<~RUBY)
|
234
|
+
class Foo
|
235
|
+
def bar(**nil)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
RUBY
|
239
|
+
|
240
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
241
|
+
entry = T.must(@index["bar"].first)
|
242
|
+
assert_empty(entry.parameters)
|
243
|
+
end
|
244
|
+
|
109
245
|
def test_keeps_track_of_method_owner
|
110
246
|
index(<<~RUBY)
|
111
247
|
class Foo
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -8,10 +8,6 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
abstract!
|
10
10
|
|
11
|
-
PositionShape = T.type_alias { { line: Integer, character: Integer } }
|
12
|
-
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
13
|
-
EditShape = T.type_alias { { range: RangeShape, text: String } }
|
14
|
-
|
15
11
|
sig { returns(Prism::ParseResult) }
|
16
12
|
attr_reader :parse_result
|
17
13
|
|
@@ -24,6 +20,9 @@ module RubyLsp
|
|
24
20
|
sig { returns(URI::Generic) }
|
25
21
|
attr_reader :uri
|
26
22
|
|
23
|
+
sig { returns(String) }
|
24
|
+
attr_reader :encoding
|
25
|
+
|
27
26
|
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
|
28
27
|
def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
|
29
28
|
@cache = T.let({}, T::Hash[String, T.untyped])
|
@@ -77,7 +76,7 @@ module RubyLsp
|
|
77
76
|
@cache[request_name]
|
78
77
|
end
|
79
78
|
|
80
|
-
sig { params(edits: T::Array[
|
79
|
+
sig { params(edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
|
81
80
|
def push_edits(edits, version:)
|
82
81
|
edits.each do |edit|
|
83
82
|
range = edit[:range]
|
@@ -109,7 +108,7 @@ module RubyLsp
|
|
109
108
|
|
110
109
|
sig do
|
111
110
|
params(
|
112
|
-
position:
|
111
|
+
position: T::Hash[Symbol, T.untyped],
|
113
112
|
node_types: T::Array[T.class_of(Prism::Node)],
|
114
113
|
).returns([T.nilable(Prism::Node), T.nilable(Prism::Node), T::Array[String]])
|
115
114
|
end
|
@@ -190,7 +189,7 @@ module RubyLsp
|
|
190
189
|
end
|
191
190
|
|
192
191
|
# Finds the character index inside the source string for a given line and column
|
193
|
-
sig { params(position:
|
192
|
+
sig { params(position: T::Hash[Symbol, T.untyped]).returns(Integer) }
|
194
193
|
def find_char_position(position)
|
195
194
|
# Find the character index for the beginning of the requested line
|
196
195
|
until @current_line == position[:line]
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -25,6 +25,7 @@ module RubyLsp
|
|
25
25
|
begin
|
26
26
|
response = run(request)
|
27
27
|
rescue StandardError, LoadError => e
|
28
|
+
warn(e.message)
|
28
29
|
error = e
|
29
30
|
end
|
30
31
|
|
@@ -98,7 +99,8 @@ module RubyLsp
|
|
98
99
|
folding_range = Requests::FoldingRanges.new(document.parse_result.comments, dispatcher)
|
99
100
|
document_symbol = Requests::DocumentSymbol.new(dispatcher)
|
100
101
|
document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
|
101
|
-
|
102
|
+
lenses_configuration = T.must(@store.features_configuration.dig(:codeLens))
|
103
|
+
code_lens = Requests::CodeLens.new(uri, lenses_configuration, dispatcher)
|
102
104
|
|
103
105
|
semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
|
104
106
|
dispatcher.dispatch(document.tree)
|
@@ -170,6 +172,8 @@ module RubyLsp
|
|
170
172
|
completion(uri, request.dig(:params, :position))
|
171
173
|
when "textDocument/definition"
|
172
174
|
definition(uri, request.dig(:params, :position))
|
175
|
+
when "textDocument/signatureHelp"
|
176
|
+
signature_help(uri, request.dig(:params, :position), request.dig(:params, :context))
|
173
177
|
when "workspace/didChangeWatchedFiles"
|
174
178
|
did_change_watched_files(request.dig(:params, :changes))
|
175
179
|
when "workspace/symbol"
|
@@ -239,12 +243,44 @@ module RubyLsp
|
|
239
243
|
end
|
240
244
|
end
|
241
245
|
|
246
|
+
sig do
|
247
|
+
params(
|
248
|
+
uri: URI::Generic,
|
249
|
+
position: T::Hash[Symbol, T.untyped],
|
250
|
+
context: T::Hash[Symbol, T.untyped],
|
251
|
+
).returns(T.any(T.nilable(Interface::SignatureHelp), T::Hash[Symbol, T.untyped]))
|
252
|
+
end
|
253
|
+
def signature_help(uri, position, context)
|
254
|
+
current_signature = context[:activeSignatureHelp]
|
255
|
+
document = @store.get(uri)
|
256
|
+
target, parent, nesting = document.locate_node(
|
257
|
+
{ line: position[:line], character: position[:character] - 2 },
|
258
|
+
node_types: [Prism::CallNode],
|
259
|
+
)
|
260
|
+
|
261
|
+
# If we're typing a nested method call (e.g.: `foo(bar)`), then we may end up locating `bar` as the target method
|
262
|
+
# call incorrectly. To correct that, we check if there's an active signature with the same name as the parent node
|
263
|
+
# and then replace the target
|
264
|
+
if current_signature && parent.is_a?(Prism::CallNode)
|
265
|
+
active_signature = current_signature[:activeSignature] || 0
|
266
|
+
|
267
|
+
if current_signature.dig(:signatures, active_signature, :label)&.start_with?(parent.message)
|
268
|
+
target = parent
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
dispatcher = Prism::Dispatcher.new
|
273
|
+
listener = Requests::SignatureHelp.new(context, nesting, @index, dispatcher)
|
274
|
+
dispatcher.dispatch_once(target)
|
275
|
+
listener.response
|
276
|
+
end
|
277
|
+
|
242
278
|
sig { params(query: T.nilable(String)).returns(T::Array[Interface::WorkspaceSymbol]) }
|
243
279
|
def workspace_symbol(query)
|
244
280
|
Requests::WorkspaceSymbol.new(query, @index).run
|
245
281
|
end
|
246
282
|
|
247
|
-
sig { params(uri: URI::Generic, range: T.nilable(
|
283
|
+
sig { params(uri: URI::Generic, range: T.nilable(T::Hash[Symbol, T.untyped])).returns({ ast: String }) }
|
248
284
|
def show_syntax_tree(uri, range)
|
249
285
|
{ ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
|
250
286
|
end
|
@@ -252,7 +288,7 @@ module RubyLsp
|
|
252
288
|
sig do
|
253
289
|
params(
|
254
290
|
uri: URI::Generic,
|
255
|
-
position:
|
291
|
+
position: T::Hash[Symbol, T.untyped],
|
256
292
|
).returns(T.nilable(T.any(T::Array[Interface::Location], Interface::Location)))
|
257
293
|
end
|
258
294
|
def definition(uri, position)
|
@@ -273,7 +309,7 @@ module RubyLsp
|
|
273
309
|
sig do
|
274
310
|
params(
|
275
311
|
uri: URI::Generic,
|
276
|
-
position:
|
312
|
+
position: T::Hash[Symbol, T.untyped],
|
277
313
|
).returns(T.nilable(Interface::Hover))
|
278
314
|
end
|
279
315
|
def hover(uri, position)
|
@@ -300,7 +336,7 @@ module RubyLsp
|
|
300
336
|
end
|
301
337
|
|
302
338
|
sig do
|
303
|
-
params(uri: URI::Generic, content_changes: T::Array[
|
339
|
+
params(uri: URI::Generic, content_changes: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).returns(Object)
|
304
340
|
end
|
305
341
|
def text_document_did_change(uri, content_changes, version)
|
306
342
|
@store.push_edits(uri: uri, edits: content_changes, version: version)
|
@@ -322,7 +358,7 @@ module RubyLsp
|
|
322
358
|
sig do
|
323
359
|
params(
|
324
360
|
uri: URI::Generic,
|
325
|
-
positions: T::Array[
|
361
|
+
positions: T::Array[T::Hash[Symbol, T.untyped]],
|
326
362
|
).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
|
327
363
|
end
|
328
364
|
def selection_range(uri, positions)
|
@@ -360,7 +396,7 @@ module RubyLsp
|
|
360
396
|
sig do
|
361
397
|
params(
|
362
398
|
uri: URI::Generic,
|
363
|
-
position:
|
399
|
+
position: T::Hash[Symbol, T.untyped],
|
364
400
|
character: String,
|
365
401
|
).returns(T::Array[Interface::TextEdit])
|
366
402
|
end
|
@@ -371,7 +407,7 @@ module RubyLsp
|
|
371
407
|
sig do
|
372
408
|
params(
|
373
409
|
uri: URI::Generic,
|
374
|
-
position:
|
410
|
+
position: T::Hash[Symbol, T.untyped],
|
375
411
|
).returns(T.nilable(T::Array[Interface::DocumentHighlight]))
|
376
412
|
end
|
377
413
|
def document_highlight(uri, position)
|
@@ -384,7 +420,12 @@ module RubyLsp
|
|
384
420
|
listener.response
|
385
421
|
end
|
386
422
|
|
387
|
-
sig
|
423
|
+
sig do
|
424
|
+
params(
|
425
|
+
uri: URI::Generic,
|
426
|
+
range: T::Hash[Symbol, T.untyped],
|
427
|
+
).returns(T.nilable(T::Array[Interface::InlayHint]))
|
428
|
+
end
|
388
429
|
def inlay_hint(uri, range)
|
389
430
|
document = @store.get(uri)
|
390
431
|
|
@@ -392,7 +433,8 @@ module RubyLsp
|
|
392
433
|
end_line = range.dig(:end, :line)
|
393
434
|
|
394
435
|
dispatcher = Prism::Dispatcher.new
|
395
|
-
|
436
|
+
hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
|
437
|
+
listener = Requests::InlayHints.new(start_line..end_line, hints_configurations, dispatcher)
|
396
438
|
dispatcher.visit(document.tree)
|
397
439
|
listener.response
|
398
440
|
end
|
@@ -400,7 +442,7 @@ module RubyLsp
|
|
400
442
|
sig do
|
401
443
|
params(
|
402
444
|
uri: URI::Generic,
|
403
|
-
range:
|
445
|
+
range: T::Hash[Symbol, T.untyped],
|
404
446
|
context: T::Hash[Symbol, T.untyped],
|
405
447
|
).returns(T.nilable(T::Array[Interface::CodeAction]))
|
406
448
|
end
|
@@ -454,7 +496,7 @@ module RubyLsp
|
|
454
496
|
Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response) if response
|
455
497
|
end
|
456
498
|
|
457
|
-
sig { params(uri: URI::Generic, range:
|
499
|
+
sig { params(uri: URI::Generic, range: T::Hash[Symbol, T.untyped]).returns(Interface::SemanticTokens) }
|
458
500
|
def semantic_tokens_range(uri, range)
|
459
501
|
document = @store.get(uri)
|
460
502
|
start_line = range.dig(:start, :line)
|
@@ -470,7 +512,7 @@ module RubyLsp
|
|
470
512
|
sig do
|
471
513
|
params(
|
472
514
|
uri: URI::Generic,
|
473
|
-
position:
|
515
|
+
position: T::Hash[Symbol, T.untyped],
|
474
516
|
).returns(T.nilable(T::Array[Interface::CompletionItem]))
|
475
517
|
end
|
476
518
|
def completion(uri, position)
|
@@ -602,6 +644,11 @@ module RubyLsp
|
|
602
644
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
603
645
|
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
604
646
|
|
647
|
+
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
648
|
+
configured_lenses = options.dig(:initializationOptions, :featuresConfiguration, :codeLens)
|
649
|
+
T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
|
650
|
+
T.must(@store.features_configuration.dig(:codeLens)).configuration.merge!(configured_lenses) if configured_lenses
|
651
|
+
|
605
652
|
enabled_features = case configured_features
|
606
653
|
when Array
|
607
654
|
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
@@ -663,7 +710,7 @@ module RubyLsp
|
|
663
710
|
on_type_formatting_provider = if enabled_features["onTypeFormatting"]
|
664
711
|
Interface::DocumentOnTypeFormattingOptions.new(
|
665
712
|
first_trigger_character: "{",
|
666
|
-
more_trigger_character: ["\n", "|"],
|
713
|
+
more_trigger_character: ["\n", "|", "d"],
|
667
714
|
)
|
668
715
|
end
|
669
716
|
|
@@ -685,6 +732,13 @@ module RubyLsp
|
|
685
732
|
)
|
686
733
|
end
|
687
734
|
|
735
|
+
signature_help_provider = if enabled_features["signatureHelp"]
|
736
|
+
# Identifier characters are automatically included, such as A-Z, a-z, 0-9, _, * or :
|
737
|
+
Interface::SignatureHelpOptions.new(
|
738
|
+
trigger_characters: ["(", " ", ","],
|
739
|
+
)
|
740
|
+
end
|
741
|
+
|
688
742
|
# Dynamically registered capabilities
|
689
743
|
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
690
744
|
|
@@ -737,6 +791,7 @@ module RubyLsp
|
|
737
791
|
code_lens_provider: code_lens_provider,
|
738
792
|
definition_provider: enabled_features["definition"],
|
739
793
|
workspace_symbol_provider: enabled_features["workspaceSymbol"],
|
794
|
+
signature_help_provider: signature_help_provider,
|
740
795
|
),
|
741
796
|
serverInfo: {
|
742
797
|
name: "Ruby LSP",
|
@@ -137,7 +137,7 @@ module RubyLsp
|
|
137
137
|
|
138
138
|
private
|
139
139
|
|
140
|
-
sig { params(range:
|
140
|
+
sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
|
141
141
|
def create_text_edit(range, new_text)
|
142
142
|
Interface::TextEdit.new(
|
143
143
|
range: Interface::Range.new(
|
@@ -22,7 +22,7 @@ module RubyLsp
|
|
22
22
|
sig do
|
23
23
|
params(
|
24
24
|
document: Document,
|
25
|
-
range:
|
25
|
+
range: T::Hash[Symbol, T.untyped],
|
26
26
|
context: T::Hash[Symbol, T.untyped],
|
27
27
|
).void
|
28
28
|
end
|
@@ -38,14 +38,8 @@ module RubyLsp
|
|
38
38
|
def run
|
39
39
|
diagnostics = @context[:diagnostics]
|
40
40
|
|
41
|
-
code_actions = diagnostics.
|
42
|
-
|
43
|
-
next if code_action.nil?
|
44
|
-
|
45
|
-
# We want to return only code actions that are within range or that do not have any edits, such as refactor
|
46
|
-
# code actions
|
47
|
-
range = code_action.dig(:edit, :documentChanges, 0, :edits, 0, :range)
|
48
|
-
code_action if diagnostic.dig(:data, :correctable) && cover?(range)
|
41
|
+
code_actions = diagnostics.flat_map do |diagnostic|
|
42
|
+
diagnostic.dig(:data, :code_actions) || []
|
49
43
|
end
|
50
44
|
|
51
45
|
# Only add refactor actions if there's a non empty selection in the editor
|
@@ -55,15 +49,7 @@ module RubyLsp
|
|
55
49
|
|
56
50
|
private
|
57
51
|
|
58
|
-
sig { params(range: T.
|
59
|
-
def cover?(range)
|
60
|
-
range.nil? ||
|
61
|
-
((@range.dig(:start, :line))..(@range.dig(:end, :line))).cover?(
|
62
|
-
(range.dig(:start, :line))..(range.dig(:end, :line)),
|
63
|
-
)
|
64
|
-
end
|
65
|
-
|
66
|
-
sig { params(range: Document::RangeShape, uri: URI::Generic).returns(Interface::CodeAction) }
|
52
|
+
sig { params(range: T::Hash[Symbol, T.untyped], uri: URI::Generic).returns(Interface::CodeAction) }
|
67
53
|
def refactor_code_action(range, uri)
|
68
54
|
Interface::CodeAction.new(
|
69
55
|
title: "Refactor: Extract Variable",
|
@@ -11,6 +11,10 @@ module RubyLsp
|
|
11
11
|
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
|
12
12
|
# request informs the editor of runnable commands such as tests
|
13
13
|
#
|
14
|
+
# # Configuration
|
15
|
+
#
|
16
|
+
# To disable gem code lenses, set `rubyLsp.featuresConfiguration.codeLens.gemfileLinks` to `false`.
|
17
|
+
#
|
14
18
|
# # Example
|
15
19
|
#
|
16
20
|
# ```ruby
|
@@ -47,8 +51,14 @@ module RubyLsp
|
|
47
51
|
sig { override.returns(ResponseType) }
|
48
52
|
attr_reader :_response
|
49
53
|
|
50
|
-
sig
|
51
|
-
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
uri: URI::Generic,
|
57
|
+
lenses_configuration: RequestConfig,
|
58
|
+
dispatcher: Prism::Dispatcher,
|
59
|
+
).void
|
60
|
+
end
|
61
|
+
def initialize(uri, lenses_configuration, dispatcher)
|
52
62
|
@uri = T.let(uri, URI::Generic)
|
53
63
|
@_response = T.let([], ResponseType)
|
54
64
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
@@ -57,6 +67,7 @@ module RubyLsp
|
|
57
67
|
@class_stack = T.let([], T::Array[String])
|
58
68
|
@group_id = T.let(1, Integer)
|
59
69
|
@group_id_stack = T.let([], T::Array[Integer])
|
70
|
+
@lenses_configuration = lenses_configuration
|
60
71
|
|
61
72
|
super(dispatcher)
|
62
73
|
|
@@ -134,6 +145,8 @@ module RubyLsp
|
|
134
145
|
end
|
135
146
|
|
136
147
|
if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
|
148
|
+
return unless @lenses_configuration.enabled?(:gemfileLinks)
|
149
|
+
|
137
150
|
first_argument = arguments.arguments.first
|
138
151
|
return unless first_argument.is_a?(Prism::StringNode)
|
139
152
|
|
@@ -154,13 +154,11 @@ module RubyLsp
|
|
154
154
|
end
|
155
155
|
def build_method_completion(entry, node)
|
156
156
|
name = entry.name
|
157
|
-
parameters = entry.parameters
|
158
|
-
new_text = parameters.empty? ? name : "#{name}(#{parameters.map(&:name).join(", ")})"
|
159
157
|
|
160
158
|
Interface::CompletionItem.new(
|
161
159
|
label: name,
|
162
160
|
filter_text: name,
|
163
|
-
text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text:
|
161
|
+
text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: name),
|
164
162
|
kind: Constant::CompletionItemKind::METHOD,
|
165
163
|
label_details: Interface::CompletionItemLabelDetails.new(
|
166
164
|
description: entry.file_name,
|
@@ -9,6 +9,14 @@ module RubyLsp
|
|
9
9
|
# are labels added directly in the code that explicitly show the user something that might
|
10
10
|
# otherwise just be implied.
|
11
11
|
#
|
12
|
+
# # Configuration
|
13
|
+
#
|
14
|
+
# To enable rescue hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitRescue` to `true`.
|
15
|
+
#
|
16
|
+
# To enable hash value hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitHashValue` to `true`.
|
17
|
+
#
|
18
|
+
# To enable all hints, set `rubyLsp.featuresConfiguration.inlayHint.enableAll` to `true`.
|
19
|
+
#
|
12
20
|
# # Example
|
13
21
|
#
|
14
22
|
# ```ruby
|
@@ -39,18 +47,26 @@ module RubyLsp
|
|
39
47
|
sig { override.returns(ResponseType) }
|
40
48
|
attr_reader :_response
|
41
49
|
|
42
|
-
sig
|
43
|
-
|
50
|
+
sig do
|
51
|
+
params(
|
52
|
+
range: T::Range[Integer],
|
53
|
+
hints_configuration: RequestConfig,
|
54
|
+
dispatcher: Prism::Dispatcher,
|
55
|
+
).void
|
56
|
+
end
|
57
|
+
def initialize(range, hints_configuration, dispatcher)
|
44
58
|
super(dispatcher)
|
45
59
|
|
46
60
|
@_response = T.let([], ResponseType)
|
47
61
|
@range = range
|
62
|
+
@hints_configuration = hints_configuration
|
48
63
|
|
49
64
|
dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
|
50
65
|
end
|
51
66
|
|
52
67
|
sig { params(node: Prism::RescueNode).void }
|
53
68
|
def on_rescue_node_enter(node)
|
69
|
+
return unless @hints_configuration.enabled?(:implicitRescue)
|
54
70
|
return unless node.exceptions.empty?
|
55
71
|
|
56
72
|
loc = node.location
|
@@ -66,6 +82,7 @@ module RubyLsp
|
|
66
82
|
|
67
83
|
sig { params(node: Prism::ImplicitNode).void }
|
68
84
|
def on_implicit_node_enter(node)
|
85
|
+
return unless @hints_configuration.enabled?(:implicitHashValue)
|
69
86
|
return unless visible?(node, @range)
|
70
87
|
|
71
88
|
node_value = node.value
|
@@ -26,7 +26,7 @@ module RubyLsp
|
|
26
26
|
T::Array[Regexp],
|
27
27
|
)
|
28
28
|
|
29
|
-
sig { params(document: Document, position:
|
29
|
+
sig { params(document: Document, position: T::Hash[Symbol, T.untyped], trigger_character: String).void }
|
30
30
|
def initialize(document, position, trigger_character)
|
31
31
|
super(document)
|
32
32
|
|
@@ -60,6 +60,8 @@ module RubyLsp
|
|
60
60
|
handle_statement_end
|
61
61
|
end
|
62
62
|
end
|
63
|
+
when "d"
|
64
|
+
auto_indent_after_end_keyword
|
63
65
|
end
|
64
66
|
|
65
67
|
@edits
|
@@ -118,7 +120,7 @@ module RubyLsp
|
|
118
120
|
current_line = @lines[@position[:line]]
|
119
121
|
next_line = @lines[@position[:line] + 1]
|
120
122
|
|
121
|
-
if current_line.nil? || current_line.strip.empty?
|
123
|
+
if current_line.nil? || current_line.strip.empty? || current_line.include?(")") || current_line.include?("]")
|
122
124
|
add_edit_with_text("\n")
|
123
125
|
add_edit_with_text("#{indents}end")
|
124
126
|
move_cursor_to(@position[:line], @indentation + 2)
|
@@ -141,7 +143,7 @@ module RubyLsp
|
|
141
143
|
add_edit_with_text("##{spaces}")
|
142
144
|
end
|
143
145
|
|
144
|
-
sig { params(text: String, position:
|
146
|
+
sig { params(text: String, position: T::Hash[Symbol, T.untyped]).void }
|
145
147
|
def add_edit_with_text(text, position = @position)
|
146
148
|
pos = Interface::Position.new(
|
147
149
|
line: position[:line],
|
@@ -185,6 +187,32 @@ module RubyLsp
|
|
185
187
|
|
186
188
|
count
|
187
189
|
end
|
190
|
+
|
191
|
+
sig { void }
|
192
|
+
def auto_indent_after_end_keyword
|
193
|
+
current_line = @lines[@position[:line]]
|
194
|
+
return unless current_line&.strip == "end"
|
195
|
+
|
196
|
+
target, _parent, _nesting = @document.locate_node({
|
197
|
+
line: @position[:line],
|
198
|
+
character: @position[:character] - 1,
|
199
|
+
})
|
200
|
+
|
201
|
+
statements = case target
|
202
|
+
when Prism::IfNode, Prism::UnlessNode, Prism::ForNode, Prism::WhileNode, Prism::UntilNode
|
203
|
+
target.statements
|
204
|
+
end
|
205
|
+
return unless statements
|
206
|
+
|
207
|
+
statements.body.each do |node|
|
208
|
+
loc = node.location
|
209
|
+
next unless loc.start_column == @indentation
|
210
|
+
|
211
|
+
add_edit_with_text(" ", { line: loc.start_line - 1, character: 0 })
|
212
|
+
end
|
213
|
+
|
214
|
+
move_cursor_to(@position[:line], @position[:character])
|
215
|
+
end
|
188
216
|
end
|
189
217
|
end
|
190
218
|
end
|
@@ -20,7 +20,7 @@ module RubyLsp
|
|
20
20
|
class ShowSyntaxTree < BaseRequest
|
21
21
|
extend T::Sig
|
22
22
|
|
23
|
-
sig { params(document: Document, range: T.nilable(
|
23
|
+
sig { params(document: Document, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
24
24
|
def initialize(document, range)
|
25
25
|
super(document)
|
26
26
|
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# The [signature help
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp) displays
|
10
|
+
# information about the parameters of a method as you type an invocation.
|
11
|
+
#
|
12
|
+
# Currently only supports methods invoked directly on `self` without taking inheritance into account.
|
13
|
+
#
|
14
|
+
# # Example
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# class Foo
|
18
|
+
# def bar(a, b, c)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def baz
|
22
|
+
# bar( # -> Signature help will show the parameters of `bar`
|
23
|
+
# end
|
24
|
+
# ```
|
25
|
+
class SignatureHelp < Listener
|
26
|
+
extend T::Sig
|
27
|
+
extend T::Generic
|
28
|
+
|
29
|
+
ResponseType = type_member { { fixed: T.nilable(T.any(Interface::SignatureHelp, T::Hash[Symbol, T.untyped])) } }
|
30
|
+
|
31
|
+
sig { override.returns(ResponseType) }
|
32
|
+
attr_reader :_response
|
33
|
+
|
34
|
+
sig do
|
35
|
+
params(
|
36
|
+
context: T::Hash[Symbol, T.untyped],
|
37
|
+
nesting: T::Array[String],
|
38
|
+
index: RubyIndexer::Index,
|
39
|
+
dispatcher: Prism::Dispatcher,
|
40
|
+
).void
|
41
|
+
end
|
42
|
+
def initialize(context, nesting, index, dispatcher)
|
43
|
+
@context = context
|
44
|
+
@nesting = nesting
|
45
|
+
@index = index
|
46
|
+
@_response = T.let(nil, ResponseType)
|
47
|
+
|
48
|
+
super(dispatcher)
|
49
|
+
dispatcher.register(self, :on_call_node_enter)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(node: Prism::CallNode).void }
|
53
|
+
def on_call_node_enter(node)
|
54
|
+
return if DependencyDetector.instance.typechecker
|
55
|
+
return unless self_receiver?(node)
|
56
|
+
|
57
|
+
message = node.message
|
58
|
+
return unless message
|
59
|
+
|
60
|
+
target_method = @index.resolve_method(message, @nesting.join("::"))
|
61
|
+
return unless target_method
|
62
|
+
|
63
|
+
parameters = target_method.parameters
|
64
|
+
name = target_method.name
|
65
|
+
|
66
|
+
# If the method doesn't have any parameters, there's no need to show signature help
|
67
|
+
return if parameters.empty?
|
68
|
+
|
69
|
+
label = "#{name}(#{parameters.map(&:decorated_name).join(", ")})"
|
70
|
+
|
71
|
+
arguments_node = node.arguments
|
72
|
+
arguments = arguments_node&.arguments || []
|
73
|
+
active_parameter = (arguments.length - 1).clamp(0, parameters.length - 1)
|
74
|
+
|
75
|
+
# If there are arguments, then we need to check if there's a trailing comma after the end of the last argument
|
76
|
+
# to advance the active parameter to the next one
|
77
|
+
if arguments_node &&
|
78
|
+
node.slice.byteslice(arguments_node.location.end_offset - node.location.start_offset) == ","
|
79
|
+
active_parameter += 1
|
80
|
+
end
|
81
|
+
|
82
|
+
@_response = Interface::SignatureHelp.new(
|
83
|
+
signatures: [
|
84
|
+
Interface::SignatureInformation.new(
|
85
|
+
label: label,
|
86
|
+
parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
|
87
|
+
documentation: markdown_from_index_entries("", target_method),
|
88
|
+
),
|
89
|
+
],
|
90
|
+
active_parameter: active_parameter,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -19,30 +19,23 @@ module RubyLsp
|
|
19
19
|
T::Hash[Symbol, Integer],
|
20
20
|
)
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
# TODO: avoid passing document once we have alternative ways to get at
|
23
|
+
# encoding and file source
|
24
|
+
sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
|
25
|
+
def initialize(document, offense, uri)
|
26
|
+
@document = document
|
24
27
|
@offense = offense
|
25
28
|
@uri = uri
|
26
29
|
end
|
27
30
|
|
28
|
-
sig { returns(Interface::CodeAction) }
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
37
|
-
uri: @uri.to_s,
|
38
|
-
version: nil,
|
39
|
-
),
|
40
|
-
edits: @offense.correctable? ? offense_replacements : [],
|
41
|
-
),
|
42
|
-
],
|
43
|
-
),
|
44
|
-
is_preferred: true,
|
45
|
-
)
|
31
|
+
sig { returns(T::Array[Interface::CodeAction]) }
|
32
|
+
def to_lsp_code_actions
|
33
|
+
code_actions = []
|
34
|
+
|
35
|
+
code_actions << autocorrect_action if @offense.correctable?
|
36
|
+
code_actions << disable_line_action
|
37
|
+
|
38
|
+
code_actions
|
46
39
|
end
|
47
40
|
|
48
41
|
sig { returns(Interface::Diagnostic) }
|
@@ -65,7 +58,7 @@ module RubyLsp
|
|
65
58
|
),
|
66
59
|
data: {
|
67
60
|
correctable: @offense.correctable?,
|
68
|
-
|
61
|
+
code_actions: to_lsp_code_actions,
|
69
62
|
},
|
70
63
|
)
|
71
64
|
end
|
@@ -90,6 +83,26 @@ module RubyLsp
|
|
90
83
|
Interface::CodeDescription.new(href: doc_url) if doc_url
|
91
84
|
end
|
92
85
|
|
86
|
+
sig { returns(Interface::CodeAction) }
|
87
|
+
def autocorrect_action
|
88
|
+
Interface::CodeAction.new(
|
89
|
+
title: "Autocorrect #{@offense.cop_name}",
|
90
|
+
kind: Constant::CodeActionKind::QUICK_FIX,
|
91
|
+
edit: Interface::WorkspaceEdit.new(
|
92
|
+
document_changes: [
|
93
|
+
Interface::TextDocumentEdit.new(
|
94
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
95
|
+
uri: @uri.to_s,
|
96
|
+
version: nil,
|
97
|
+
),
|
98
|
+
edits: @offense.correctable? ? offense_replacements : [],
|
99
|
+
),
|
100
|
+
],
|
101
|
+
),
|
102
|
+
is_preferred: true,
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
93
106
|
sig { returns(T::Array[Interface::TextEdit]) }
|
94
107
|
def offense_replacements
|
95
108
|
@offense.corrector.as_replacements.map do |range, replacement|
|
@@ -102,6 +115,64 @@ module RubyLsp
|
|
102
115
|
)
|
103
116
|
end
|
104
117
|
end
|
118
|
+
|
119
|
+
sig { returns(Interface::CodeAction) }
|
120
|
+
def disable_line_action
|
121
|
+
Interface::CodeAction.new(
|
122
|
+
title: "Disable #{@offense.cop_name} for this line",
|
123
|
+
kind: Constant::CodeActionKind::QUICK_FIX,
|
124
|
+
edit: Interface::WorkspaceEdit.new(
|
125
|
+
document_changes: [
|
126
|
+
Interface::TextDocumentEdit.new(
|
127
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
128
|
+
uri: @uri.to_s,
|
129
|
+
version: nil,
|
130
|
+
),
|
131
|
+
edits: line_disable_comment,
|
132
|
+
),
|
133
|
+
],
|
134
|
+
),
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { returns(T::Array[Interface::TextEdit]) }
|
139
|
+
def line_disable_comment
|
140
|
+
new_text = if @offense.source_line.include?(" # rubocop:disable ")
|
141
|
+
",#{@offense.cop_name}"
|
142
|
+
else
|
143
|
+
" # rubocop:disable #{@offense.cop_name}"
|
144
|
+
end
|
145
|
+
|
146
|
+
eol = Interface::Position.new(
|
147
|
+
line: @offense.line - 1,
|
148
|
+
character: length_of_line(@offense.source_line),
|
149
|
+
)
|
150
|
+
|
151
|
+
# TODO: fails for multiline strings - may be preferable to use block
|
152
|
+
# comments to disable some offenses
|
153
|
+
inline_comment = Interface::TextEdit.new(
|
154
|
+
range: Interface::Range.new(start: eol, end: eol),
|
155
|
+
new_text: new_text,
|
156
|
+
)
|
157
|
+
|
158
|
+
[inline_comment]
|
159
|
+
end
|
160
|
+
|
161
|
+
sig { params(line: String).returns(Integer) }
|
162
|
+
def length_of_line(line)
|
163
|
+
if @document.encoding == Constant::PositionEncodingKind::UTF16
|
164
|
+
line_length = 0
|
165
|
+
line.codepoints.each do |codepoint|
|
166
|
+
line_length += 1
|
167
|
+
if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
|
168
|
+
line_length += 1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
line_length
|
172
|
+
else
|
173
|
+
line.length
|
174
|
+
end
|
175
|
+
end
|
105
176
|
end
|
106
177
|
end
|
107
178
|
end
|
@@ -7,7 +7,7 @@ module RubyLsp
|
|
7
7
|
class SelectionRange < Interface::SelectionRange
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
sig { params(position:
|
10
|
+
sig { params(position: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
|
11
11
|
def cover?(position)
|
12
12
|
start_covered = range.start.line < position[:line] ||
|
13
13
|
(range.start.line == position[:line] && range.start.character <= position[:character])
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -22,6 +22,7 @@ module RubyLsp
|
|
22
22
|
# - [Definition](rdoc-ref:RubyLsp::Requests::Definition)
|
23
23
|
# - [ShowSyntaxTree](rdoc-ref:RubyLsp::Requests::ShowSyntaxTree)
|
24
24
|
# - [WorkspaceSymbol](rdoc-ref:RubyLsp::Requests::WorkspaceSymbol)
|
25
|
+
# - [SignatureHelp](rdoc-ref:RubyLsp::Requests::SignatureHelp)
|
25
26
|
|
26
27
|
module Requests
|
27
28
|
autoload :BaseRequest, "ruby_lsp/requests/base_request"
|
@@ -43,6 +44,7 @@ module RubyLsp
|
|
43
44
|
autoload :Definition, "ruby_lsp/requests/definition"
|
44
45
|
autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
|
45
46
|
autoload :WorkspaceSymbol, "ruby_lsp/requests/workspace_symbol"
|
47
|
+
autoload :SignatureHelp, "ruby_lsp/requests/signature_help"
|
46
48
|
|
47
49
|
# :nodoc:
|
48
50
|
module Support
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -20,6 +20,9 @@ module RubyLsp
|
|
20
20
|
sig { returns(URI::Generic) }
|
21
21
|
attr_accessor :workspace_uri
|
22
22
|
|
23
|
+
sig { returns(T::Hash[Symbol, RequestConfig]) }
|
24
|
+
attr_accessor :features_configuration
|
25
|
+
|
23
26
|
sig { void }
|
24
27
|
def initialize
|
25
28
|
@state = T.let({}, T::Hash[String, Document])
|
@@ -28,6 +31,20 @@ module RubyLsp
|
|
28
31
|
@supports_progress = T.let(true, T::Boolean)
|
29
32
|
@experimental_features = T.let(false, T::Boolean)
|
30
33
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
34
|
+
@features_configuration = T.let(
|
35
|
+
{
|
36
|
+
codeLens: RequestConfig.new({
|
37
|
+
enableAll: false,
|
38
|
+
gemfileLinks: true,
|
39
|
+
}),
|
40
|
+
inlayHint: RequestConfig.new({
|
41
|
+
enableAll: false,
|
42
|
+
implicitRescue: false,
|
43
|
+
implicitHashValue: false,
|
44
|
+
}),
|
45
|
+
},
|
46
|
+
T::Hash[Symbol, RequestConfig],
|
47
|
+
)
|
31
48
|
end
|
32
49
|
|
33
50
|
sig { params(uri: URI::Generic).returns(Document) }
|
@@ -46,7 +63,7 @@ module RubyLsp
|
|
46
63
|
@state[uri.to_s] = document
|
47
64
|
end
|
48
65
|
|
49
|
-
sig { params(uri: URI::Generic, edits: T::Array[
|
66
|
+
sig { params(uri: URI::Generic, edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
|
50
67
|
def push_edits(uri:, edits:, version:)
|
51
68
|
T.must(@state[uri.to_s]).push_edits(edits, version: version)
|
52
69
|
end
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -74,4 +74,22 @@ module RubyLsp
|
|
74
74
|
@cancelled = true
|
75
75
|
end
|
76
76
|
end
|
77
|
+
|
78
|
+
# A request configuration, to turn on/off features
|
79
|
+
class RequestConfig
|
80
|
+
extend T::Sig
|
81
|
+
|
82
|
+
sig { returns(T::Hash[Symbol, T::Boolean]) }
|
83
|
+
attr_accessor :configuration
|
84
|
+
|
85
|
+
sig { params(configuration: T::Hash[Symbol, T::Boolean]).void }
|
86
|
+
def initialize(configuration)
|
87
|
+
@configuration = configuration
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { params(feature: Symbol).returns(T.nilable(T::Boolean)) }
|
91
|
+
def enabled?(feature)
|
92
|
+
@configuration[:enableAll] || @configuration[feature]
|
93
|
+
end
|
94
|
+
end
|
77
95
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.2
|
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
|
+
date: 2023-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -30,20 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.19.0
|
34
34
|
- - "<"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: '0.
|
36
|
+
version: '0.20'
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 0.19.0
|
44
44
|
- - "<"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.20'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sorbet-runtime
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/ruby_lsp/requests/selection_ranges.rb
|
119
119
|
- lib/ruby_lsp/requests/semantic_highlighting.rb
|
120
120
|
- lib/ruby_lsp/requests/show_syntax_tree.rb
|
121
|
+
- lib/ruby_lsp/requests/signature_help.rb
|
121
122
|
- lib/ruby_lsp/requests/support/annotation.rb
|
122
123
|
- lib/ruby_lsp/requests/support/common.rb
|
123
124
|
- lib/ruby_lsp/requests/support/dependency_detector.rb
|
@@ -157,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
158
|
- !ruby/object:Gem::Version
|
158
159
|
version: '0'
|
159
160
|
requirements: []
|
160
|
-
rubygems_version: 3.4.
|
161
|
+
rubygems_version: 3.4.22
|
161
162
|
signing_key:
|
162
163
|
specification_version: 4
|
163
164
|
summary: An opinionated language server for Ruby
|