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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +3 -3
  5. data/lib/ruby_lsp/document.rb +12 -0
  6. data/lib/ruby_lsp/executor.rb +77 -266
  7. data/lib/ruby_lsp/listener.rb +1 -50
  8. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  9. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  10. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  11. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  12. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  13. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  14. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  15. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  16. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  17. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  18. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
  20. data/lib/ruby_lsp/requests/code_actions.rb +13 -4
  21. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  22. data/lib/ruby_lsp/requests/completion.rb +64 -244
  23. data/lib/ruby_lsp/requests/definition.rb +34 -147
  24. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  25. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  26. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  27. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  28. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  29. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  30. data/lib/ruby_lsp/requests/hover.rb +48 -92
  31. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  32. data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
  33. data/lib/ruby_lsp/requests/request.rb +17 -0
  34. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  36. data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
  37. data/lib/ruby_lsp/requests/signature_help.rb +43 -51
  38. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  39. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  40. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  42. data/lib/ruby_lsp/requests.rb +1 -1
  43. data/lib/ruby_lsp/utils.rb +8 -0
  44. metadata +17 -6
  45. 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 < BaseRequest
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(document)
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 run
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 < BaseRequest
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(document)
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 run
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 < ExtensibleListener
27
+ class CodeLens < Request
26
28
  extend T::Sig
27
29
  extend T::Generic
28
30
 
29
- ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
31
+ class << self
32
+ extend T::Sig
30
33
 
31
- BASE_COMMAND = T.let(
32
- begin
33
- Bundler.with_original_env { Bundler.default_lockfile }
34
- "bundle exec ruby"
35
- rescue Bundler::GemfileNotFound
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
- sig { override.returns(ResponseType) }
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
- @uri = T.let(uri, URI::Generic)
63
- @_response = T.let([], ResponseType)
64
- @path = T.let(uri.to_standardized_path, T.nilable(String))
65
- # visibility_stack is a stack of [current_visibility, previous_visibility]
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
- @_response << create_code_lens(
208
- node,
209
- title: "Run In Terminal",
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 { params(node: Prism::CallNode, remote: String).void }
257
- def add_open_gem_remote_code_lens(node, remote)
258
- @_response << create_code_lens(
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