ruby-lsp 0.12.2 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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 }