ruby-lsp 0.13.2 → 0.13.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -0
- data/VERSION +1 -1
- data/lib/ruby_lsp/check_docs.rb +3 -3
- data/lib/ruby_lsp/document.rb +12 -0
- data/lib/ruby_lsp/executor.rb +77 -266
- data/lib/ruby_lsp/listener.rb +1 -50
- data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
- data/lib/ruby_lsp/listeners/completion.rb +275 -0
- data/lib/ruby_lsp/listeners/definition.rb +158 -0
- data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
- data/lib/ruby_lsp/listeners/document_link.rb +162 -0
- data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
- data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
- data/lib/ruby_lsp/listeners/hover.rb +152 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
- data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
- data/lib/ruby_lsp/requests/code_actions.rb +13 -4
- data/lib/ruby_lsp/requests/code_lens.rb +21 -221
- data/lib/ruby_lsp/requests/completion.rb +64 -244
- data/lib/ruby_lsp/requests/definition.rb +34 -147
- data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
- data/lib/ruby_lsp/requests/document_link.rb +11 -132
- data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
- data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
- data/lib/ruby_lsp/requests/formatting.rb +4 -4
- data/lib/ruby_lsp/requests/hover.rb +48 -92
- data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
- data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
- data/lib/ruby_lsp/requests/request.rb +17 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
- data/lib/ruby_lsp/requests/signature_help.rb +43 -51
- data/lib/ruby_lsp/requests/support/common.rb +3 -2
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
- data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
- data/lib/ruby_lsp/requests.rb +1 -1
- data/lib/ruby_lsp/utils.rb +8 -0
- metadata +17 -6
- data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -0,0 +1,74 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Listeners
|
6
|
+
class SignatureHelp < Listener
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
|
10
|
+
ResponseType = type_member { { fixed: T.nilable(T.any(Interface::SignatureHelp, T::Hash[Symbol, T.untyped])) } }
|
11
|
+
|
12
|
+
sig { override.returns(ResponseType) }
|
13
|
+
attr_reader :_response
|
14
|
+
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
nesting: T::Array[String],
|
18
|
+
index: RubyIndexer::Index,
|
19
|
+
dispatcher: Prism::Dispatcher,
|
20
|
+
).void
|
21
|
+
end
|
22
|
+
def initialize(nesting, index, dispatcher)
|
23
|
+
@nesting = nesting
|
24
|
+
@index = index
|
25
|
+
@_response = T.let(nil, ResponseType)
|
26
|
+
|
27
|
+
super(dispatcher)
|
28
|
+
dispatcher.register(self, :on_call_node_enter)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(node: Prism::CallNode).void }
|
32
|
+
def on_call_node_enter(node)
|
33
|
+
return if DependencyDetector.instance.typechecker
|
34
|
+
return unless self_receiver?(node)
|
35
|
+
|
36
|
+
message = node.message
|
37
|
+
return unless message
|
38
|
+
|
39
|
+
target_method = @index.resolve_method(message, @nesting.join("::"))
|
40
|
+
return unless target_method
|
41
|
+
|
42
|
+
parameters = target_method.parameters
|
43
|
+
name = target_method.name
|
44
|
+
|
45
|
+
# If the method doesn't have any parameters, there's no need to show signature help
|
46
|
+
return if parameters.empty?
|
47
|
+
|
48
|
+
label = "#{name}(#{parameters.map(&:decorated_name).join(", ")})"
|
49
|
+
|
50
|
+
arguments_node = node.arguments
|
51
|
+
arguments = arguments_node&.arguments || []
|
52
|
+
active_parameter = (arguments.length - 1).clamp(0, parameters.length - 1)
|
53
|
+
|
54
|
+
# If there are arguments, then we need to check if there's a trailing comma after the end of the last argument
|
55
|
+
# to advance the active parameter to the next one
|
56
|
+
if arguments_node &&
|
57
|
+
node.slice.byteslice(arguments_node.location.end_offset - node.location.start_offset) == ","
|
58
|
+
active_parameter += 1
|
59
|
+
end
|
60
|
+
|
61
|
+
@_response = Interface::SignatureHelp.new(
|
62
|
+
signatures: [
|
63
|
+
Interface::SignatureInformation.new(
|
64
|
+
label: label,
|
65
|
+
parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
|
66
|
+
documentation: markdown_from_index_entries("", target_method),
|
67
|
+
),
|
68
|
+
],
|
69
|
+
active_parameter: active_parameter,
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
#
|
22
22
|
# ```
|
23
23
|
#
|
24
|
-
class CodeActionResolve <
|
24
|
+
class CodeActionResolve < Request
|
25
25
|
extend T::Sig
|
26
26
|
NEW_VARIABLE_NAME = "new_variable"
|
27
27
|
|
@@ -36,13 +36,13 @@ module RubyLsp
|
|
36
36
|
|
37
37
|
sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
|
38
38
|
def initialize(document, code_action)
|
39
|
-
super(
|
40
|
-
|
39
|
+
super()
|
40
|
+
@document = document
|
41
41
|
@code_action = code_action
|
42
42
|
end
|
43
43
|
|
44
44
|
sig { override.returns(T.any(Interface::CodeAction, Error)) }
|
45
|
-
def
|
45
|
+
def perform
|
46
46
|
return Error::EmptySelection if @document.source.empty?
|
47
47
|
|
48
48
|
source_range = @code_action.dig(:data, :range)
|
@@ -16,9 +16,18 @@ module RubyLsp
|
|
16
16
|
# puts "Hello" # --> code action: quick fix indentation
|
17
17
|
# end
|
18
18
|
# ```
|
19
|
-
class CodeActions <
|
19
|
+
class CodeActions < Request
|
20
20
|
extend T::Sig
|
21
21
|
|
22
|
+
class << self
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
sig { returns(Interface::CodeActionOptions) }
|
26
|
+
def provider
|
27
|
+
Interface::CodeActionOptions.new(resolve_provider: true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
22
31
|
sig do
|
23
32
|
params(
|
24
33
|
document: Document,
|
@@ -27,15 +36,15 @@ module RubyLsp
|
|
27
36
|
).void
|
28
37
|
end
|
29
38
|
def initialize(document, range, context)
|
30
|
-
super(
|
31
|
-
|
39
|
+
super()
|
40
|
+
@document = document
|
32
41
|
@uri = T.let(document.uri, URI::Generic)
|
33
42
|
@range = range
|
34
43
|
@context = context
|
35
44
|
end
|
36
45
|
|
37
46
|
sig { override.returns(T.nilable(T.all(T::Array[Interface::CodeAction], Object))) }
|
38
|
-
def
|
47
|
+
def perform
|
39
48
|
diagnostics = @context[:diagnostics]
|
40
49
|
|
41
50
|
code_actions = diagnostics.flat_map do |diagnostic|
|
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
require "shellwords"
|
5
5
|
|
6
|
+
require "ruby_lsp/listeners/code_lens"
|
7
|
+
|
6
8
|
module RubyLsp
|
7
9
|
module Requests
|
8
10
|
# ![Code lens demo](../../code_lens.gif)
|
@@ -22,34 +24,20 @@ module RubyLsp
|
|
22
24
|
# class Test < Minitest::Test
|
23
25
|
# end
|
24
26
|
# ```
|
25
|
-
class CodeLens <
|
27
|
+
class CodeLens < Request
|
26
28
|
extend T::Sig
|
27
29
|
extend T::Generic
|
28
30
|
|
29
|
-
|
31
|
+
class << self
|
32
|
+
extend T::Sig
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
"ruby"
|
37
|
-
end + " -Itest ",
|
38
|
-
String,
|
39
|
-
)
|
40
|
-
GEMFILE_NAME = T.let(
|
41
|
-
begin
|
42
|
-
Bundler.with_original_env { Bundler.default_gemfile.basename.to_s }
|
43
|
-
rescue Bundler::GemfileNotFound
|
44
|
-
"Gemfile"
|
45
|
-
end,
|
46
|
-
String,
|
47
|
-
)
|
48
|
-
ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
|
49
|
-
SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
|
34
|
+
sig { returns(Interface::CodeLensOptions) }
|
35
|
+
def provider
|
36
|
+
Interface::CodeLensOptions.new(resolve_provider: false)
|
37
|
+
end
|
38
|
+
end
|
50
39
|
|
51
|
-
|
52
|
-
attr_reader :_response
|
40
|
+
ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
|
53
41
|
|
54
42
|
sig do
|
55
43
|
params(
|
@@ -59,209 +47,21 @@ module RubyLsp
|
|
59
47
|
).void
|
60
48
|
end
|
61
49
|
def initialize(uri, lenses_configuration, dispatcher)
|
62
|
-
|
63
|
-
@
|
64
|
-
|
65
|
-
|
66
|
-
@visibility_stack = T.let([[:public, :public]], T::Array[T::Array[T.nilable(Symbol)]])
|
67
|
-
@class_stack = T.let([], T::Array[String])
|
68
|
-
@group_id = T.let(1, Integer)
|
69
|
-
@group_id_stack = T.let([], T::Array[Integer])
|
70
|
-
@lenses_configuration = lenses_configuration
|
71
|
-
|
72
|
-
super(dispatcher)
|
73
|
-
|
74
|
-
dispatcher.register(
|
75
|
-
self,
|
76
|
-
:on_class_node_enter,
|
77
|
-
:on_class_node_leave,
|
78
|
-
:on_def_node_enter,
|
79
|
-
:on_call_node_enter,
|
80
|
-
:on_call_node_leave,
|
81
|
-
)
|
82
|
-
end
|
83
|
-
|
84
|
-
sig { params(node: Prism::ClassNode).void }
|
85
|
-
def on_class_node_enter(node)
|
86
|
-
@visibility_stack.push([:public, :public])
|
87
|
-
class_name = node.constant_path.slice
|
88
|
-
@class_stack.push(class_name)
|
89
|
-
|
90
|
-
if @path && class_name.end_with?("Test")
|
91
|
-
add_test_code_lens(
|
92
|
-
node,
|
93
|
-
name: class_name,
|
94
|
-
command: generate_test_command(class_name: class_name),
|
95
|
-
kind: :group,
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
|
-
@group_id_stack.push(@group_id)
|
100
|
-
@group_id += 1
|
101
|
-
end
|
102
|
-
|
103
|
-
sig { params(node: Prism::ClassNode).void }
|
104
|
-
def on_class_node_leave(node)
|
105
|
-
@visibility_stack.pop
|
106
|
-
@class_stack.pop
|
107
|
-
@group_id_stack.pop
|
108
|
-
end
|
109
|
-
|
110
|
-
sig { params(node: Prism::DefNode).void }
|
111
|
-
def on_def_node_enter(node)
|
112
|
-
class_name = @class_stack.last
|
113
|
-
return unless class_name&.end_with?("Test")
|
114
|
-
|
115
|
-
visibility, _ = @visibility_stack.last
|
116
|
-
if visibility == :public
|
117
|
-
method_name = node.name.to_s
|
118
|
-
if @path && method_name.start_with?("test_")
|
119
|
-
add_test_code_lens(
|
120
|
-
node,
|
121
|
-
name: method_name,
|
122
|
-
command: generate_test_command(method_name: method_name, class_name: class_name),
|
123
|
-
kind: :example,
|
124
|
-
)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
sig { params(node: Prism::CallNode).void }
|
130
|
-
def on_call_node_enter(node)
|
131
|
-
name = node.name
|
132
|
-
arguments = node.arguments
|
133
|
-
|
134
|
-
# If we found `private` by itself or `private def foo`
|
135
|
-
if ACCESS_MODIFIERS.include?(name)
|
136
|
-
if arguments.nil?
|
137
|
-
@visibility_stack.pop
|
138
|
-
@visibility_stack.push([name, name])
|
139
|
-
elsif arguments.arguments.first.is_a?(Prism::DefNode)
|
140
|
-
visibility, _ = @visibility_stack.pop
|
141
|
-
@visibility_stack.push([name, visibility])
|
142
|
-
end
|
143
|
-
|
144
|
-
return
|
145
|
-
end
|
146
|
-
|
147
|
-
if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
|
148
|
-
return unless @lenses_configuration.enabled?(:gemfileLinks)
|
149
|
-
|
150
|
-
first_argument = arguments.arguments.first
|
151
|
-
return unless first_argument.is_a?(Prism::StringNode)
|
152
|
-
|
153
|
-
remote = resolve_gem_remote(first_argument)
|
154
|
-
return unless remote
|
155
|
-
|
156
|
-
add_open_gem_remote_code_lens(node, remote)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
sig { params(node: Prism::CallNode).void }
|
161
|
-
def on_call_node_leave(node)
|
162
|
-
_, prev_visibility = @visibility_stack.pop
|
163
|
-
@visibility_stack.push([prev_visibility, prev_visibility])
|
164
|
-
end
|
165
|
-
|
166
|
-
sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) }
|
167
|
-
def initialize_external_listener(addon)
|
168
|
-
addon.create_code_lens_listener(@uri, @dispatcher)
|
169
|
-
end
|
170
|
-
|
171
|
-
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
172
|
-
def merge_response!(other)
|
173
|
-
@_response.concat(other.response)
|
174
|
-
self
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
|
180
|
-
def add_test_code_lens(node, name:, command:, kind:)
|
181
|
-
# don't add code lenses if the test library is not supported or unknown
|
182
|
-
return unless SUPPORTED_TEST_LIBRARIES.include?(DependencyDetector.instance.detected_test_library) && @path
|
183
|
-
|
184
|
-
arguments = [
|
185
|
-
@path,
|
186
|
-
name,
|
187
|
-
command,
|
188
|
-
{
|
189
|
-
start_line: node.location.start_line - 1,
|
190
|
-
start_column: node.location.start_column,
|
191
|
-
end_line: node.location.end_line - 1,
|
192
|
-
end_column: node.location.end_column,
|
193
|
-
},
|
194
|
-
]
|
195
|
-
|
196
|
-
grouping_data = { group_id: @group_id_stack.last, kind: kind }
|
197
|
-
grouping_data[:id] = @group_id if kind == :group
|
198
|
-
|
199
|
-
@_response << create_code_lens(
|
200
|
-
node,
|
201
|
-
title: "Run",
|
202
|
-
command_name: "rubyLsp.runTest",
|
203
|
-
arguments: arguments,
|
204
|
-
data: { type: "test", **grouping_data },
|
50
|
+
super()
|
51
|
+
@listeners = T.let(
|
52
|
+
[Listeners::CodeLens.new(uri, lenses_configuration, dispatcher)],
|
53
|
+
T::Array[Listener[ResponseType]],
|
205
54
|
)
|
206
55
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
command_name: "rubyLsp.runTestInTerminal",
|
211
|
-
arguments: arguments,
|
212
|
-
data: { type: "test_in_terminal", **grouping_data },
|
213
|
-
)
|
214
|
-
|
215
|
-
@_response << create_code_lens(
|
216
|
-
node,
|
217
|
-
title: "Debug",
|
218
|
-
command_name: "rubyLsp.debugTest",
|
219
|
-
arguments: arguments,
|
220
|
-
data: { type: "debug", **grouping_data },
|
221
|
-
)
|
222
|
-
end
|
223
|
-
|
224
|
-
sig { params(gem_name: Prism::StringNode).returns(T.nilable(String)) }
|
225
|
-
def resolve_gem_remote(gem_name)
|
226
|
-
spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.content }&.to_spec
|
227
|
-
return if spec.nil?
|
228
|
-
|
229
|
-
[spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
|
230
|
-
page.start_with?("https://github.com", "https://gitlab.com")
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
|
235
|
-
def generate_test_command(class_name:, method_name: nil)
|
236
|
-
command = BASE_COMMAND + T.must(@path)
|
237
|
-
|
238
|
-
case DependencyDetector.instance.detected_test_library
|
239
|
-
when "minitest"
|
240
|
-
command += if method_name
|
241
|
-
" --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
|
242
|
-
else
|
243
|
-
" --name " + "/#{Shellwords.escape(class_name)}/"
|
244
|
-
end
|
245
|
-
when "test-unit"
|
246
|
-
command += " --testcase " + "/#{Shellwords.escape(class_name)}/"
|
247
|
-
|
248
|
-
if method_name
|
249
|
-
command += " --name " + Shellwords.escape(method_name)
|
250
|
-
end
|
56
|
+
Addon.addons.each do |addon|
|
57
|
+
addon_listener = addon.create_code_lens_listener(uri, dispatcher)
|
58
|
+
@listeners << addon_listener if addon_listener
|
251
59
|
end
|
252
|
-
|
253
|
-
command
|
254
60
|
end
|
255
61
|
|
256
|
-
sig {
|
257
|
-
def
|
258
|
-
@
|
259
|
-
node,
|
260
|
-
title: "Open remote",
|
261
|
-
command_name: "rubyLsp.openLink",
|
262
|
-
arguments: [remote],
|
263
|
-
data: { type: "link" },
|
264
|
-
)
|
62
|
+
sig { override.returns(ResponseType) }
|
63
|
+
def perform
|
64
|
+
@listeners.flat_map(&:response)
|
265
65
|
end
|
266
66
|
end
|
267
67
|
end
|