ruby-lsp 0.12.2 → 0.13.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp-check +20 -4
  5. data/exe/ruby-lsp-doctor +2 -2
  6. data/lib/ruby_indexer/lib/ruby_indexer/{visitor.rb → collector.rb} +144 -61
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +9 -4
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +89 -12
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +22 -4
  10. data/lib/ruby_indexer/ruby_indexer.rb +1 -1
  11. data/lib/ruby_indexer/test/configuration_test.rb +10 -0
  12. data/lib/ruby_indexer/test/index_test.rb +64 -0
  13. data/lib/ruby_indexer/test/method_test.rb +80 -0
  14. data/lib/ruby_lsp/addon.rb +9 -13
  15. data/lib/ruby_lsp/document.rb +7 -9
  16. data/lib/ruby_lsp/executor.rb +54 -51
  17. data/lib/ruby_lsp/internal.rb +4 -0
  18. data/lib/ruby_lsp/listener.rb +4 -5
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +8 -4
  20. data/lib/ruby_lsp/requests/code_lens.rb +16 -7
  21. data/lib/ruby_lsp/requests/completion.rb +60 -8
  22. data/lib/ruby_lsp/requests/definition.rb +55 -29
  23. data/lib/ruby_lsp/requests/diagnostics.rb +0 -5
  24. data/lib/ruby_lsp/requests/document_highlight.rb +20 -11
  25. data/lib/ruby_lsp/requests/document_link.rb +2 -3
  26. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  27. data/lib/ruby_lsp/requests/folding_ranges.rb +12 -15
  28. data/lib/ruby_lsp/requests/formatting.rb +0 -5
  29. data/lib/ruby_lsp/requests/hover.rb +23 -4
  30. data/lib/ruby_lsp/requests/inlay_hints.rb +42 -4
  31. data/lib/ruby_lsp/requests/on_type_formatting.rb +18 -4
  32. data/lib/ruby_lsp/requests/semantic_highlighting.rb +41 -16
  33. data/lib/ruby_lsp/requests/support/common.rb +22 -2
  34. data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -1
  35. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -8
  36. data/lib/ruby_lsp/requests/workspace_symbol.rb +6 -11
  37. data/lib/ruby_lsp/ruby_document.rb +14 -0
  38. data/lib/ruby_lsp/setup_bundler.rb +2 -0
  39. data/lib/ruby_lsp/store.rb +5 -3
  40. data/lib/ruby_lsp/utils.rb +8 -3
  41. metadata +8 -7
@@ -6,9 +6,14 @@ module RubyLsp
6
6
  # ![Completion demo](../../completion.gif)
7
7
  #
8
8
  # The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
9
- # suggests possible completions according to what the developer is typing. Currently, completion is support for
10
- # - require paths
11
- # - classes, modules and constant names
9
+ # suggests possible completions according to what the developer is typing.
10
+ #
11
+ # Currently supported targets:
12
+ # - Classes
13
+ # - Modules
14
+ # - Constants
15
+ # - Require paths
16
+ # - Methods invoked on self only
12
17
  #
13
18
  # # Example
14
19
  #
@@ -31,11 +36,10 @@ module RubyLsp
31
36
  index: RubyIndexer::Index,
32
37
  nesting: T::Array[String],
33
38
  dispatcher: Prism::Dispatcher,
34
- message_queue: Thread::Queue,
35
39
  ).void
36
40
  end
37
- def initialize(index, nesting, dispatcher, message_queue)
38
- super(dispatcher, message_queue)
41
+ def initialize(index, nesting, dispatcher)
42
+ super(dispatcher)
39
43
  @_response = T.let([], ResponseType)
40
44
  @index = index
41
45
  @nesting = nesting
@@ -45,6 +49,7 @@ module RubyLsp
45
49
  :on_string_node_enter,
46
50
  :on_constant_path_node_enter,
47
51
  :on_constant_read_node_enter,
52
+ :on_call_node_enter,
48
53
  )
49
54
  end
50
55
 
@@ -118,17 +123,64 @@ module RubyLsp
118
123
  end
119
124
  end
120
125
 
126
+ sig { params(node: Prism::CallNode).void }
127
+ def on_call_node_enter(node)
128
+ return if DependencyDetector.instance.typechecker
129
+ return unless self_receiver?(node)
130
+
131
+ name = node.message
132
+ return unless name
133
+
134
+ receiver_entries = @index[@nesting.join("::")]
135
+ return unless receiver_entries
136
+
137
+ receiver = T.must(receiver_entries.first)
138
+
139
+ @index.prefix_search(name).each do |entries|
140
+ entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
141
+ next unless entry
142
+
143
+ @_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
144
+ end
145
+ end
146
+
121
147
  private
122
148
 
149
+ sig do
150
+ params(
151
+ entry: RubyIndexer::Entry::Member,
152
+ node: Prism::CallNode,
153
+ ).returns(Interface::CompletionItem)
154
+ end
155
+ def build_method_completion(entry, node)
156
+ name = entry.name
157
+ parameters = entry.parameters
158
+ new_text = parameters.empty? ? name : "#{name}(#{parameters.map(&:name).join(", ")})"
159
+
160
+ Interface::CompletionItem.new(
161
+ label: name,
162
+ filter_text: name,
163
+ text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: new_text),
164
+ kind: Constant::CompletionItemKind::METHOD,
165
+ label_details: Interface::CompletionItemLabelDetails.new(
166
+ description: entry.file_name,
167
+ ),
168
+ documentation: markdown_from_index_entries(name, entry),
169
+ )
170
+ end
171
+
123
172
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
124
173
  def build_completion(label, node)
174
+ # We should use the content location as we only replace the content and not the delimiters of the string
175
+ loc = node.content_loc
176
+
125
177
  Interface::CompletionItem.new(
126
178
  label: label,
127
179
  text_edit: Interface::TextEdit.new(
128
- range: range_from_node(node),
180
+ range: range_from_location(loc),
129
181
  new_text: label,
130
182
  ),
131
- kind: Constant::CompletionItemKind::REFERENCE,
183
+ kind: Constant::CompletionItemKind::FILE,
132
184
  )
133
185
  end
134
186
 
@@ -9,7 +9,12 @@ module RubyLsp
9
9
  # request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
10
10
  # definition of the symbol under the cursor.
11
11
  #
12
- # Currently, only jumping to classes, modules and required files is supported.
12
+ # Currently supported targets:
13
+ # - Classes
14
+ # - Modules
15
+ # - Constants
16
+ # - Require paths
17
+ # - Methods invoked on self only
13
18
  #
14
19
  # # Example
15
20
  #
@@ -32,16 +37,15 @@ module RubyLsp
32
37
  nesting: T::Array[String],
33
38
  index: RubyIndexer::Index,
34
39
  dispatcher: Prism::Dispatcher,
35
- message_queue: Thread::Queue,
36
40
  ).void
37
41
  end
38
- def initialize(uri, nesting, index, dispatcher, message_queue)
42
+ def initialize(uri, nesting, index, dispatcher)
39
43
  @uri = uri
40
44
  @nesting = nesting
41
45
  @index = index
42
46
  @_response = T.let(nil, ResponseType)
43
47
 
44
- super(dispatcher, message_queue)
48
+ super(dispatcher)
45
49
 
46
50
  dispatcher.register(
47
51
  self,
@@ -53,7 +57,7 @@ module RubyLsp
53
57
 
54
58
  sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
55
59
  def initialize_external_listener(addon)
56
- addon.create_definition_listener(@uri, @nesting, @index, @dispatcher, @message_queue)
60
+ addon.create_definition_listener(@uri, @nesting, @index, @dispatcher)
57
61
  end
58
62
 
59
63
  sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
@@ -75,8 +79,52 @@ module RubyLsp
75
79
  sig { params(node: Prism::CallNode).void }
76
80
  def on_call_node_enter(node)
77
81
  message = node.name
78
- return unless message == :require || message == :require_relative
79
82
 
83
+ if message == :require || message == :require_relative
84
+ handle_require_definition(node)
85
+ else
86
+ handle_method_definition(node)
87
+ end
88
+ end
89
+
90
+ sig { params(node: Prism::ConstantPathNode).void }
91
+ def on_constant_path_node_enter(node)
92
+ find_in_index(node.slice)
93
+ end
94
+
95
+ sig { params(node: Prism::ConstantReadNode).void }
96
+ def on_constant_read_node_enter(node)
97
+ find_in_index(node.slice)
98
+ end
99
+
100
+ private
101
+
102
+ sig { params(node: Prism::CallNode).void }
103
+ def handle_method_definition(node)
104
+ return unless self_receiver?(node)
105
+
106
+ message = node.message
107
+ return unless message
108
+
109
+ target_method = @index.resolve_method(message, @nesting.join("::"))
110
+ return unless target_method
111
+
112
+ location = target_method.location
113
+ file_path = target_method.file_path
114
+ return if defined_in_gem?(file_path)
115
+
116
+ @_response = Interface::Location.new(
117
+ uri: URI::Generic.from_path(path: file_path).to_s,
118
+ range: Interface::Range.new(
119
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
120
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
121
+ ),
122
+ )
123
+ end
124
+
125
+ sig { params(node: Prism::CallNode).void }
126
+ def handle_require_definition(node)
127
+ message = node.name
80
128
  arguments = node.arguments
81
129
  return unless arguments
82
130
 
@@ -116,18 +164,6 @@ module RubyLsp
116
164
  end
117
165
  end
118
166
 
119
- sig { params(node: Prism::ConstantPathNode).void }
120
- def on_constant_path_node_enter(node)
121
- find_in_index(node.slice)
122
- end
123
-
124
- sig { params(node: Prism::ConstantReadNode).void }
125
- def on_constant_read_node_enter(node)
126
- find_in_index(node.slice)
127
- end
128
-
129
- private
130
-
131
167
  sig { params(value: String).void }
132
168
  def find_in_index(value)
133
169
  entries = @index.resolve(value, @nesting)
@@ -138,23 +174,13 @@ module RubyLsp
138
174
  first_entry = T.must(entries.first)
139
175
  return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
140
176
 
141
- bundle_path = begin
142
- Bundler.bundle_path.to_s
143
- rescue Bundler::GemfileNotFound
144
- nil
145
- end
146
-
147
177
  @_response = entries.filter_map do |entry|
148
178
  location = entry.location
149
179
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
150
180
  # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
151
181
  # in the project, even if the files are typed false
152
182
  file_path = entry.file_path
153
- if DependencyDetector.instance.typechecker && bundle_path && !file_path.start_with?(bundle_path) &&
154
- !file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
155
-
156
- next
157
- end
183
+ next if defined_in_gem?(file_path)
158
184
 
159
185
  Interface::Location.new(
160
186
  uri: URI::Generic.from_path(path: file_path).to_s,
@@ -32,13 +32,8 @@ module RubyLsp
32
32
  def run
33
33
  # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
34
34
  return syntax_error_diagnostics if @document.syntax_error?
35
-
36
35
  return unless defined?(Support::RuboCopDiagnosticsRunner)
37
36
 
38
- # Don't try to run RuboCop diagnostics for files outside the current working directory
39
- path = @uri.to_standardized_path
40
- return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
41
-
42
37
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document).map!(&:to_lsp_diagnostic)
43
38
  end
44
39
 
@@ -97,7 +97,8 @@ module RubyLsp
97
97
  Prism::LocalVariableWriteNode,
98
98
  Prism::BlockParameterNode,
99
99
  Prism::RequiredParameterNode,
100
- Prism::KeywordParameterNode,
100
+ Prism::RequiredKeywordParameterNode,
101
+ Prism::OptionalKeywordParameterNode,
101
102
  Prism::RestParameterNode,
102
103
  Prism::OptionalParameterNode,
103
104
  Prism::KeywordRestParameterNode,
@@ -113,11 +114,10 @@ module RubyLsp
113
114
  target: T.nilable(Prism::Node),
114
115
  parent: T.nilable(Prism::Node),
115
116
  dispatcher: Prism::Dispatcher,
116
- message_queue: Thread::Queue,
117
117
  ).void
118
118
  end
119
- def initialize(target, parent, dispatcher, message_queue)
120
- super(dispatcher, message_queue)
119
+ def initialize(target, parent, dispatcher)
120
+ super(dispatcher)
121
121
 
122
122
  @_response = T.let([], T::Array[Interface::DocumentHighlight])
123
123
 
@@ -137,8 +137,9 @@ module RubyLsp
137
137
  Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode, Prism::ClassVariableWriteNode,
138
138
  Prism::LocalVariableAndWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
139
139
  Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode, Prism::LocalVariableWriteNode,
140
- Prism::CallNode, Prism::BlockParameterNode, Prism::KeywordParameterNode, Prism::KeywordRestParameterNode,
141
- Prism::OptionalParameterNode, Prism::RequiredParameterNode, Prism::RestParameterNode
140
+ Prism::CallNode, Prism::BlockParameterNode, Prism::RequiredKeywordParameterNode,
141
+ Prism::RequiredKeywordParameterNode, Prism::KeywordRestParameterNode, Prism::OptionalParameterNode,
142
+ Prism::RequiredParameterNode, Prism::RestParameterNode
142
143
  target
143
144
  end
144
145
 
@@ -171,7 +172,8 @@ module RubyLsp
171
172
  :on_constant_path_and_write_node_enter,
172
173
  :on_constant_path_operator_write_node_enter,
173
174
  :on_local_variable_write_node_enter,
174
- :on_keyword_parameter_node_enter,
175
+ :on_required_keyword_parameter_node_enter,
176
+ :on_optional_keyword_parameter_node_enter,
175
177
  :on_rest_parameter_node_enter,
176
178
  :on_optional_parameter_node_enter,
177
179
  :on_keyword_rest_parameter_node_enter,
@@ -359,8 +361,15 @@ module RubyLsp
359
361
  add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
360
362
  end
361
363
 
362
- sig { params(node: Prism::KeywordParameterNode).void }
363
- def on_keyword_parameter_node_enter(node)
364
+ sig { params(node: Prism::RequiredKeywordParameterNode).void }
365
+ def on_required_keyword_parameter_node_enter(node)
366
+ return unless matches?(node, LOCAL_NODES)
367
+
368
+ add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
369
+ end
370
+
371
+ sig { params(node: Prism::OptionalKeywordParameterNode).void }
372
+ def on_optional_keyword_parameter_node_enter(node)
364
373
  return unless matches?(node, LOCAL_NODES)
365
374
 
366
375
  add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
@@ -551,8 +560,8 @@ module RubyLsp
551
560
  Prism::ClassVariableTargetNode, Prism::ClassVariableWriteNode, Prism::LocalVariableAndWriteNode,
552
561
  Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableReadNode,
553
562
  Prism::LocalVariableTargetNode, Prism::LocalVariableWriteNode, Prism::DefNode, Prism::BlockParameterNode,
554
- Prism::KeywordParameterNode, Prism::KeywordRestParameterNode, Prism::OptionalParameterNode,
555
- Prism::RequiredParameterNode, Prism::RestParameterNode
563
+ Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode, Prism::KeywordRestParameterNode,
564
+ Prism::OptionalParameterNode, Prism::RequiredParameterNode, Prism::RestParameterNode
556
565
 
557
566
  node.name.to_s
558
567
  when Prism::CallNode
@@ -80,11 +80,10 @@ module RubyLsp
80
80
  uri: URI::Generic,
81
81
  comments: T::Array[Prism::Comment],
82
82
  dispatcher: Prism::Dispatcher,
83
- message_queue: Thread::Queue,
84
83
  ).void
85
84
  end
86
- def initialize(uri, comments, dispatcher, message_queue)
87
- super(dispatcher, message_queue)
85
+ def initialize(uri, comments, dispatcher)
86
+ super(dispatcher)
88
87
 
89
88
  # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
90
89
  # in the URI
@@ -49,8 +49,8 @@ module RubyLsp
49
49
  sig { override.returns(T::Array[Interface::DocumentSymbol]) }
50
50
  attr_reader :_response
51
51
 
52
- sig { params(dispatcher: Prism::Dispatcher, message_queue: Thread::Queue).void }
53
- def initialize(dispatcher, message_queue)
52
+ sig { params(dispatcher: Prism::Dispatcher).void }
53
+ def initialize(dispatcher)
54
54
  @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
55
55
  @_response = T.let(@root.children, T::Array[Interface::DocumentSymbol])
56
56
  @stack = T.let(
@@ -80,7 +80,7 @@ module RubyLsp
80
80
 
81
81
  sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) }
82
82
  def initialize_external_listener(addon)
83
- addon.create_document_symbol_listener(@dispatcher, @message_queue)
83
+ addon.create_document_symbol_listener(@dispatcher)
84
84
  end
85
85
 
86
86
  # Merges responses from other listeners
@@ -21,9 +21,9 @@ module RubyLsp
21
21
 
22
22
  ResponseType = type_member { { fixed: T::Array[Interface::FoldingRange] } }
23
23
 
24
- sig { params(comments: T::Array[Prism::Comment], dispatcher: Prism::Dispatcher, queue: Thread::Queue).void }
25
- def initialize(comments, dispatcher, queue)
26
- super(dispatcher, queue)
24
+ sig { params(comments: T::Array[Prism::Comment], dispatcher: Prism::Dispatcher).void }
25
+ def initialize(comments, dispatcher)
26
+ super(dispatcher)
27
27
 
28
28
  @_response = T.let([], ResponseType)
29
29
  @requires = T.let([], T::Array[Prism::CallNode])
@@ -40,6 +40,7 @@ module RubyLsp
40
40
  :on_array_node_enter,
41
41
  :on_block_node_enter,
42
42
  :on_case_node_enter,
43
+ :on_case_match_node_enter,
43
44
  :on_class_node_enter,
44
45
  :on_module_node_enter,
45
46
  :on_for_node_enter,
@@ -51,7 +52,6 @@ module RubyLsp
51
52
  :on_else_node_enter,
52
53
  :on_ensure_node_enter,
53
54
  :on_begin_node_enter,
54
- :on_string_concat_node_enter,
55
55
  :on_def_node_enter,
56
56
  :on_call_node_enter,
57
57
  :on_lambda_node_enter,
@@ -91,10 +91,10 @@ module RubyLsp
91
91
 
92
92
  sig { params(node: Prism::InterpolatedStringNode).void }
93
93
  def on_interpolated_string_node_enter(node)
94
- opening_loc = node.opening_loc
95
- closing_loc = node.closing_loc
94
+ opening_loc = node.opening_loc || node.location
95
+ closing_loc = node.closing_loc || node.parts.last&.location || node.location
96
96
 
97
- add_lines_range(opening_loc.start_line, closing_loc.end_line - 1) if opening_loc && closing_loc
97
+ add_lines_range(opening_loc.start_line, closing_loc.start_line - 1)
98
98
  end
99
99
 
100
100
  sig { params(node: Prism::ArrayNode).void }
@@ -112,6 +112,11 @@ module RubyLsp
112
112
  add_simple_range(node)
113
113
  end
114
114
 
115
+ sig { params(node: Prism::CaseMatchNode).void }
116
+ def on_case_match_node_enter(node)
117
+ add_simple_range(node)
118
+ end
119
+
115
120
  sig { params(node: Prism::ClassNode).void }
116
121
  def on_class_node_enter(node)
117
122
  add_simple_range(node)
@@ -167,14 +172,6 @@ module RubyLsp
167
172
  add_simple_range(node)
168
173
  end
169
174
 
170
- sig { params(node: Prism::StringConcatNode).void }
171
- def on_string_concat_node_enter(node)
172
- left = T.let(node.left, Prism::Node)
173
- left = left.left while left.is_a?(Prism::StringConcatNode)
174
-
175
- add_lines_range(left.location.start_line, node.right.location.end_line - 1)
176
- end
177
-
178
175
  sig { params(node: Prism::DefNode).void }
179
176
  def on_def_node_enter(node)
180
177
  params = node.parameters
@@ -64,11 +64,6 @@ module RubyLsp
64
64
  sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
65
65
  def run
66
66
  return if @formatter == "none"
67
-
68
- # Don't try to format files outside the current working directory
69
- path = @uri.to_standardized_path
70
- return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
71
-
72
67
  return if @document.syntax_error?
73
68
 
74
69
  formatted_text = formatted_file
@@ -37,26 +37,26 @@ module RubyLsp
37
37
  index: RubyIndexer::Index,
38
38
  nesting: T::Array[String],
39
39
  dispatcher: Prism::Dispatcher,
40
- message_queue: Thread::Queue,
41
40
  ).void
42
41
  end
43
- def initialize(index, nesting, dispatcher, message_queue)
42
+ def initialize(index, nesting, dispatcher)
44
43
  @index = index
45
44
  @nesting = nesting
46
45
  @_response = T.let(nil, ResponseType)
47
46
 
48
- super(dispatcher, message_queue)
47
+ super(dispatcher)
49
48
  dispatcher.register(
50
49
  self,
51
50
  :on_constant_read_node_enter,
52
51
  :on_constant_write_node_enter,
53
52
  :on_constant_path_node_enter,
53
+ :on_call_node_enter,
54
54
  )
55
55
  end
56
56
 
57
57
  sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) }
58
58
  def initialize_external_listener(addon)
59
- addon.create_hover_listener(@nesting, @index, @dispatcher, @message_queue)
59
+ addon.create_hover_listener(@nesting, @index, @dispatcher)
60
60
  end
61
61
 
62
62
  # Merges responses from other hover listeners
@@ -95,6 +95,25 @@ module RubyLsp
95
95
  generate_hover(node.slice, node.location)
96
96
  end
97
97
 
98
+ sig { params(node: Prism::CallNode).void }
99
+ def on_call_node_enter(node)
100
+ return if DependencyDetector.instance.typechecker
101
+ return unless self_receiver?(node)
102
+
103
+ message = node.message
104
+ return unless message
105
+
106
+ target_method = @index.resolve_method(message, @nesting.join("::"))
107
+ return unless target_method
108
+
109
+ location = target_method.location
110
+
111
+ @_response = Interface::Hover.new(
112
+ range: range_from_location(location),
113
+ contents: markdown_from_index_entries(message, target_method),
114
+ )
115
+ end
116
+
98
117
  private
99
118
 
100
119
  sig { params(name: String, location: Prism::Location).void }
@@ -18,6 +18,16 @@ module RubyLsp
18
18
  # puts "handle some rescue"
19
19
  # end
20
20
  # ```
21
+ #
22
+ # # Example
23
+ #
24
+ # ```ruby
25
+ # var = "foo"
26
+ # {
27
+ # var: var, # Label "var" goes here in cases where the value is omitted
28
+ # a: "hello",
29
+ # }
30
+ # ```
21
31
  class InlayHints < Listener
22
32
  extend T::Sig
23
33
  extend T::Generic
@@ -29,14 +39,14 @@ module RubyLsp
29
39
  sig { override.returns(ResponseType) }
30
40
  attr_reader :_response
31
41
 
32
- sig { params(range: T::Range[Integer], dispatcher: Prism::Dispatcher, message_queue: Thread::Queue).void }
33
- def initialize(range, dispatcher, message_queue)
34
- super(dispatcher, message_queue)
42
+ sig { params(range: T::Range[Integer], dispatcher: Prism::Dispatcher).void }
43
+ def initialize(range, dispatcher)
44
+ super(dispatcher)
35
45
 
36
46
  @_response = T.let([], ResponseType)
37
47
  @range = range
38
48
 
39
- dispatcher.register(self, :on_rescue_node_enter)
49
+ dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
40
50
  end
41
51
 
42
52
  sig { params(node: Prism::RescueNode).void }
@@ -53,6 +63,34 @@ module RubyLsp
53
63
  tooltip: "StandardError is implied in a bare rescue",
54
64
  )
55
65
  end
66
+
67
+ sig { params(node: Prism::ImplicitNode).void }
68
+ def on_implicit_node_enter(node)
69
+ return unless visible?(node, @range)
70
+
71
+ node_value = node.value
72
+ loc = node.location
73
+ tooltip = ""
74
+ node_name = ""
75
+ case node_value
76
+ when Prism::CallNode
77
+ node_name = node_value.name
78
+ tooltip = "This is a method call. Method name: #{node_name}"
79
+ when Prism::ConstantReadNode
80
+ node_name = node_value.name
81
+ tooltip = "This is a constant: #{node_name}"
82
+ when Prism::LocalVariableReadNode
83
+ node_name = node_value.name
84
+ tooltip = "This is a local variable: #{node_name}"
85
+ end
86
+
87
+ @_response << Interface::InlayHint.new(
88
+ position: { line: loc.start_line - 1, character: loc.start_column + node_name.length + 1 },
89
+ label: node_name,
90
+ padding_left: true,
91
+ tooltip: tooltip,
92
+ )
93
+ end
56
94
  end
57
95
  end
58
96
  end
@@ -20,8 +20,8 @@ module RubyLsp
20
20
 
21
21
  END_REGEXES = T.let(
22
22
  [
23
- /(if|unless|for|while|class|module|until|def|case).*/,
24
- /.*\sdo/,
23
+ /\b(if|unless|for|while|class|module|until|def|case)\b.*/,
24
+ /.*\s\bdo\b/,
25
25
  ],
26
26
  T::Array[Regexp],
27
27
  )
@@ -51,7 +51,14 @@ module RubyLsp
51
51
  if (comment_match = @previous_line.match(/^#(\s*)/))
52
52
  handle_comment_line(T.must(comment_match[1]))
53
53
  elsif @document.syntax_error?
54
- handle_statement_end
54
+ match = /(?<=<<(-|~))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(@previous_line)
55
+ heredoc_delimiter = match && match.named_captures["delimiter"]
56
+
57
+ if heredoc_delimiter
58
+ handle_heredoc_end(heredoc_delimiter)
59
+ else
60
+ handle_statement_end
61
+ end
55
62
  end
56
63
  end
57
64
 
@@ -121,10 +128,17 @@ module RubyLsp
121
128
  end
122
129
  end
123
130
 
131
+ sig { params(delimiter: String).void }
132
+ def handle_heredoc_end(delimiter)
133
+ indents = " " * @indentation
134
+ add_edit_with_text("\n")
135
+ add_edit_with_text("#{indents}#{delimiter}")
136
+ move_cursor_to(@position[:line], @indentation + 2)
137
+ end
138
+
124
139
  sig { params(spaces: String).void }
125
140
  def handle_comment_line(spaces)
126
141
  add_edit_with_text("##{spaces}")
127
- move_cursor_to(@position[:line], @indentation + spaces.size + 1)
128
142
  end
129
143
 
130
144
  sig { params(text: String, position: Document::PositionShape).void }