ruby-lsp 0.13.2 → 0.13.4

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.
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