ruby-lsp 0.3.2 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e09a85000549c0b7b7dd2886b49d44ca2e32b830616a4af7181a2d7952f948f
4
- data.tar.gz: f19b017ebffce3a090d9fe9c39c94cb0dad75a7a74856c099443839524d513f3
3
+ metadata.gz: 9a8db1decbd8a8d3a05226ececbd13ba54aa2a0b5e727fd9a752ccda5b52322d
4
+ data.tar.gz: 4f49ca2d80313dcd41e0410a1583ddc3f0c913305b4c3347611181a752561265
5
5
  SHA512:
6
- metadata.gz: e0b75fa95c43a4a15a46e3cd663b5dd637c9288497116b356eb69dc3f831cfdbb3e167c1c578b68feee715e85c72d26c7d63236e3250552ddbf0f2db334e8f46
7
- data.tar.gz: c80518dba77375f07f9944f0401f6836ca69bba60ba6723677e33e366104dd69b70832e8ce76c05312b47c3a032a5308711505e88be2ce69f3f837bec2db3c21
6
+ metadata.gz: 510bd7ef4da254c79da9af3437403ffe70a054718e9b0a33efc52b1bb49f93cf9c951729e6528ad4cbcf7d383fa7904835f8e0496482611c3891c3ed12ba784c
7
+ data.tar.gz: 2df5df95106e85a37c834c4ff77a5a19609e1946326ca86828292abb4440787b0eb8f38c9fd8f6f3cd2eb5460e8c27d0c41bb1c025d317f01502ab21afad4161
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.3.4
@@ -24,6 +24,7 @@ module RubyLsp
24
24
  @syntax_error_edits = T.let([], T::Array[EditShape])
25
25
  @source = T.let(source, String)
26
26
  @parsable_source = T.let(source.dup, String)
27
+ @unparsed_edits = T.let([], T::Array[EditShape])
27
28
  @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
28
29
  rescue SyntaxTree::Parser::ParseError
29
30
  # Do not raise if we failed to parse
@@ -38,7 +39,7 @@ module RubyLsp
38
39
  type_parameters(:T)
39
40
  .params(
40
41
  request_name: Symbol,
41
- block: T.proc.params(document: Document).returns(T.type_parameter(:T))
42
+ block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
42
43
  ).returns(T.type_parameter(:T))
43
44
  end
44
45
  def cache_fetch(request_name, &block)
@@ -55,13 +56,21 @@ module RubyLsp
55
56
  # Apply the edits on the real source
56
57
  edits.each { |edit| apply_edit(@source, edit[:range], edit[:text]) }
57
58
 
59
+ @unparsed_edits.concat(edits)
58
60
  @cache.clear
61
+ end
62
+
63
+ sig { void }
64
+ def parse
65
+ return if @unparsed_edits.empty?
66
+
59
67
  @tree = SyntaxTree.parse(@source)
60
68
  @syntax_error_edits.clear
69
+ @unparsed_edits.clear
61
70
  @parsable_source = @source.dup
62
- nil
63
71
  rescue SyntaxTree::Parser::ParseError
64
- update_parsable_source(edits)
72
+ @syntax_error_edits = @unparsed_edits
73
+ update_parsable_source(@unparsed_edits)
65
74
  end
66
75
 
67
76
  sig { returns(T::Boolean) }
@@ -81,7 +90,6 @@ module RubyLsp
81
90
  # If the new edits caused a syntax error, make all edits blank spaces and line breaks to adjust the line and
82
91
  # column numbers. This is attempt to make the document parsable while partial edits are being applied
83
92
  edits.each do |edit|
84
- @syntax_error_edits << edit
85
93
  next if edit[:text].empty? # skip deletions, since they may have caused the syntax error
86
94
 
87
95
  apply_edit(@parsable_source, edit[:range], edit[:text].gsub(/[^\r\n]/, " "))
@@ -26,7 +26,7 @@ module RubyLsp
26
26
  # for displaying window messages on errors
27
27
  sig do
28
28
  params(
29
- block: T.proc.bind(Handler).params(error: Exception, request: T::Hash[Symbol, T.untyped]).void
29
+ block: T.proc.bind(Handler).params(error: Exception, request: T::Hash[Symbol, T.untyped]).void,
30
30
  ).void
31
31
  end
32
32
  def on_error(&block)
@@ -85,7 +85,7 @@ module RubyLsp
85
85
  params(
86
86
  msg: String,
87
87
  parallel: T::Boolean,
88
- blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
88
+ blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped),
89
89
  ).returns(RequestHandler)
90
90
  end
91
91
  def on(msg, parallel: false, &blk)
@@ -103,7 +103,7 @@ module RubyLsp
103
103
  def clear_diagnostics(uri)
104
104
  @writer.write(
105
105
  method: "textDocument/publishDiagnostics",
106
- params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: [])
106
+ params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
107
107
  )
108
108
  end
109
109
 
@@ -111,7 +111,7 @@ module RubyLsp
111
111
  def show_message(type, message)
112
112
  @writer.write(
113
113
  method: "window/showMessage",
114
- params: Interface::ShowMessageParams.new(type: type, message: message)
114
+ params: Interface::ShowMessageParams.new(type: type, message: message),
115
115
  )
116
116
  end
117
117
  end
@@ -28,7 +28,7 @@ module RubyLsp
28
28
  sig do
29
29
  params(
30
30
  writer: LanguageServer::Protocol::Transport::Stdio::Writer,
31
- handlers: T::Hash[String, Handler::RequestHandler]
31
+ handlers: T::Hash[String, Handler::RequestHandler],
32
32
  ).void
33
33
  end
34
34
  def initialize(writer, handlers)
@@ -94,7 +94,7 @@ module RubyLsp
94
94
  sig do
95
95
  params(
96
96
  result: Result,
97
- request: T::Hash[Symbol, T.untyped]
97
+ request: T::Hash[Symbol, T.untyped],
98
98
  ).void
99
99
  end
100
100
  def finalize_request(result, request)
@@ -107,7 +107,7 @@ module RubyLsp
107
107
  id: request[:id],
108
108
  error: {
109
109
  code: LanguageServer::Protocol::Constant::ErrorCodes::INTERNAL_ERROR,
110
- message: result.error.inspect,
110
+ message: error.inspect,
111
111
  data: request.to_json,
112
112
  },
113
113
  )
@@ -117,7 +117,7 @@ module RubyLsp
117
117
 
118
118
  request_time = result.request_time
119
119
  if request_time
120
- @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, result.error))
120
+ @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
121
121
  end
122
122
  end
123
123
  end
@@ -152,7 +152,7 @@ module RubyLsp
152
152
  params(
153
153
  request: T::Hash[Symbol, T.untyped],
154
154
  request_time: Float,
155
- error: T.nilable(Exception)
155
+ error: T.nilable(Exception),
156
156
  ).returns(T::Hash[Symbol, T.any(String, Float)])
157
157
  end
158
158
  def telemetry_params(request, request_time, error)
@@ -167,6 +167,12 @@ module RubyLsp
167
167
  if error
168
168
  params[:errorClass] = error.class.name
169
169
  params[:errorMessage] = error.message
170
+
171
+ log_params = request[:params]&.reject { |k, _| k == :textDocument }
172
+ params[:params] = log_params.to_json if log_params&.any?
173
+
174
+ backtrace = error.backtrace
175
+ params[:backtrace] = backtrace.map { |bt| bt.sub(/^#{Dir.home}/, "~") }.join("\n") if backtrace
170
176
  end
171
177
 
172
178
  params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
@@ -14,6 +14,10 @@ module RubyLsp
14
14
  def initialize(document)
15
15
  @document = document
16
16
 
17
+ # Parsing the document here means we're taking a lazy approach by only doing it when the first feature request
18
+ # is received by the server. This happens because {Document#parse} remembers if there are new edits to be parsed
19
+ @document.parse
20
+
17
21
  super()
18
22
  end
19
23
 
@@ -30,6 +34,51 @@ module RubyLsp
30
34
  end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
31
35
  )
32
36
  end
37
+
38
+ sig { params(node: SyntaxTree::ConstPathRef).returns(String) }
39
+ def full_constant_name(node)
40
+ name = +node.constant.value
41
+ constant = T.let(node, SyntaxTree::Node)
42
+
43
+ while constant.is_a?(SyntaxTree::ConstPathRef)
44
+ constant = constant.parent
45
+
46
+ case constant
47
+ when SyntaxTree::ConstPathRef
48
+ name.prepend("#{constant.constant.value}::")
49
+ when SyntaxTree::VarRef
50
+ name.prepend("#{constant.value.value}::")
51
+ end
52
+ end
53
+
54
+ name
55
+ end
56
+
57
+ sig do
58
+ params(
59
+ parent: SyntaxTree::Node,
60
+ target_nodes: T::Array[T.class_of(SyntaxTree::Node)],
61
+ position: Integer,
62
+ ).returns(T::Array[SyntaxTree::Node])
63
+ end
64
+ def locate_node_and_parent(parent, target_nodes, position)
65
+ matched = parent.child_nodes.compact.bsearch do |child|
66
+ if (child.location.start_char...child.location.end_char).cover?(position)
67
+ 0
68
+ else
69
+ position <=> child.location.start_char
70
+ end
71
+ end
72
+
73
+ case matched
74
+ when *target_nodes
75
+ [matched, parent]
76
+ when SyntaxTree::Node
77
+ locate_node_and_parent(matched, target_nodes, position)
78
+ else
79
+ []
80
+ end
81
+ end
33
82
  end
34
83
  end
35
84
  end
@@ -23,7 +23,7 @@ module RubyLsp
23
23
  params(
24
24
  uri: String,
25
25
  document: Document,
26
- range: T::Range[Integer]
26
+ range: T::Range[Integer],
27
27
  ).void
28
28
  end
29
29
  def initialize(uri, document, range)
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  T.any(
34
34
  T.all(T::Array[Support::RuboCopDiagnostic], Object),
35
35
  T.all(T::Array[Support::SyntaxErrorDiagnostic], Object),
36
- )
36
+ ),
37
37
  )
38
38
  end
39
39
  def run
@@ -37,13 +37,11 @@ module RubyLsp
37
37
  sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
38
38
  def run
39
39
  # no @target means the target is not highlightable
40
- return [] unless @target
41
-
42
- visit(@document.tree)
40
+ visit(@document.tree) if @document.parsed? && @target
43
41
  @highlights
44
42
  end
45
43
 
46
- sig { params(node: T.nilable(SyntaxTree::Node)).void }
44
+ sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
47
45
  def visit(node)
48
46
  return if node.nil?
49
47
 
@@ -55,6 +53,14 @@ module RubyLsp
55
53
 
56
54
  private
57
55
 
56
+ DIRECT_HIGHLIGHTS = T.let([
57
+ SyntaxTree::GVar,
58
+ SyntaxTree::IVar,
59
+ SyntaxTree::Const,
60
+ SyntaxTree::CVar,
61
+ SyntaxTree::VarField,
62
+ ], T::Array[T.class_of(SyntaxTree::Node)])
63
+
58
64
  sig do
59
65
  params(
60
66
  node: SyntaxTree::Node,
@@ -62,27 +68,16 @@ module RubyLsp
62
68
  ).returns(T.nilable(Support::HighlightTarget))
63
69
  end
64
70
  def find(node, position)
65
- matched =
66
- node.child_nodes.compact.bsearch do |child|
67
- if (child.location.start_char...child.location.end_char).cover?(position)
68
- 0
69
- else
70
- position <=> child.location.start_char
71
- end
72
- end
71
+ matched, parent = locate_node_and_parent(node, DIRECT_HIGHLIGHTS + [SyntaxTree::Ident], position)
72
+
73
+ return unless matched && parent
73
74
 
74
75
  case matched
75
- when SyntaxTree::GVar,
76
- SyntaxTree::IVar,
77
- SyntaxTree::Const,
78
- SyntaxTree::CVar,
79
- SyntaxTree::VarField
76
+ when *DIRECT_HIGHLIGHTS
80
77
  Support::HighlightTarget.new(matched)
81
78
  when SyntaxTree::Ident
82
- relevant_node = node.is_a?(SyntaxTree::Params) ? matched : node
79
+ relevant_node = parent.is_a?(SyntaxTree::Params) ? matched : parent
83
80
  Support::HighlightTarget.new(relevant_node)
84
- when SyntaxTree::Node
85
- find(matched, position)
86
81
  end
87
82
  end
88
83
 
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].map! do |s|
26
26
  [s.name, s.version.to_s]
27
27
  end.to_h.freeze,
28
- T::Hash[String, String]
28
+ T::Hash[String, String],
29
29
  )
30
30
 
31
31
  class << self
@@ -78,11 +78,11 @@ module RubyLsp
78
78
 
79
79
  sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentLink], Object)) }
80
80
  def run
81
- visit(@document.tree)
81
+ visit(@document.tree) if @document.parsed?
82
82
  @links
83
83
  end
84
84
 
85
- sig { params(node: SyntaxTree::Comment).void }
85
+ sig { override.params(node: SyntaxTree::Comment).void }
86
86
  def visit_comment(node)
87
87
  match = node.value.match(%r{source://.*#\d+$})
88
88
  return unless match
@@ -95,7 +95,7 @@ module RubyLsp
95
95
  @links << LanguageServer::Protocol::Interface::DocumentLink.new(
96
96
  range: range_from_syntax_tree_node(node),
97
97
  target: "file://#{file_path}##{uri.line_number}",
98
- tooltip: "Jump to #{file_path}##{uri.line_number}"
98
+ tooltip: "Jump to #{file_path}##{uri.line_number}",
99
99
  )
100
100
  end
101
101
 
@@ -79,23 +79,23 @@ module RubyLsp
79
79
  @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
80
80
  @stack = T.let(
81
81
  [@root],
82
- T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)]
82
+ T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)],
83
83
  )
84
84
  end
85
85
 
86
86
  sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol], Object)) }
87
87
  def run
88
- visit(@document.tree)
88
+ visit(@document.tree) if @document.parsed?
89
89
  @root.children
90
90
  end
91
91
 
92
- sig { params(node: SyntaxTree::ClassDeclaration).void }
92
+ sig { override.params(node: SyntaxTree::ClassDeclaration).void }
93
93
  def visit_class(node)
94
94
  symbol = create_document_symbol(
95
95
  name: fully_qualified_name(node),
96
96
  kind: :class,
97
97
  range_node: node,
98
- selection_range_node: node.constant
98
+ selection_range_node: node.constant,
99
99
  )
100
100
 
101
101
  @stack << symbol
@@ -103,7 +103,7 @@ module RubyLsp
103
103
  @stack.pop
104
104
  end
105
105
 
106
- sig { params(node: SyntaxTree::Command).void }
106
+ sig { override.params(node: SyntaxTree::Command).void }
107
107
  def visit_command(node)
108
108
  return unless ATTR_ACCESSORS.include?(node.message.value)
109
109
 
@@ -114,22 +114,22 @@ module RubyLsp
114
114
  name: argument.value.value,
115
115
  kind: :field,
116
116
  range_node: argument,
117
- selection_range_node: argument.value
117
+ selection_range_node: argument.value,
118
118
  )
119
119
  end
120
120
  end
121
121
 
122
- sig { params(node: SyntaxTree::ConstPathField).void }
122
+ sig { override.params(node: SyntaxTree::ConstPathField).void }
123
123
  def visit_const_path_field(node)
124
124
  create_document_symbol(
125
125
  name: node.constant.value,
126
126
  kind: :constant,
127
127
  range_node: node,
128
- selection_range_node: node.constant
128
+ selection_range_node: node.constant,
129
129
  )
130
130
  end
131
131
 
132
- sig { params(node: SyntaxTree::Def).void }
132
+ sig { override.params(node: SyntaxTree::Def).void }
133
133
  def visit_def(node)
134
134
  name = node.name.value
135
135
 
@@ -137,7 +137,7 @@ module RubyLsp
137
137
  name: name,
138
138
  kind: name == "initialize" ? :constructor : :method,
139
139
  range_node: node,
140
- selection_range_node: node.name
140
+ selection_range_node: node.name,
141
141
  )
142
142
 
143
143
  @stack << symbol
@@ -145,7 +145,7 @@ module RubyLsp
145
145
  @stack.pop
146
146
  end
147
147
 
148
- sig { params(node: SyntaxTree::DefEndless).void }
148
+ sig { override.params(node: SyntaxTree::DefEndless).void }
149
149
  def visit_def_endless(node)
150
150
  name = node.name.value
151
151
 
@@ -153,7 +153,7 @@ module RubyLsp
153
153
  name: name,
154
154
  kind: name == "initialize" ? :constructor : :method,
155
155
  range_node: node,
156
- selection_range_node: node.name
156
+ selection_range_node: node.name,
157
157
  )
158
158
 
159
159
  @stack << symbol
@@ -161,13 +161,13 @@ module RubyLsp
161
161
  @stack.pop
162
162
  end
163
163
 
164
- sig { params(node: SyntaxTree::Defs).void }
164
+ sig { override.params(node: SyntaxTree::Defs).void }
165
165
  def visit_defs(node)
166
166
  symbol = create_document_symbol(
167
167
  name: "self.#{node.name.value}",
168
168
  kind: :method,
169
169
  range_node: node,
170
- selection_range_node: node.name
170
+ selection_range_node: node.name,
171
171
  )
172
172
 
173
173
  @stack << symbol
@@ -175,13 +175,13 @@ module RubyLsp
175
175
  @stack.pop
176
176
  end
177
177
 
178
- sig { params(node: SyntaxTree::ModuleDeclaration).void }
178
+ sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
179
179
  def visit_module(node)
180
180
  symbol = create_document_symbol(
181
181
  name: fully_qualified_name(node),
182
182
  kind: :module,
183
183
  range_node: node,
184
- selection_range_node: node.constant
184
+ selection_range_node: node.constant,
185
185
  )
186
186
 
187
187
  @stack << symbol
@@ -189,17 +189,17 @@ module RubyLsp
189
189
  @stack.pop
190
190
  end
191
191
 
192
- sig { params(node: SyntaxTree::TopConstField).void }
192
+ sig { override.params(node: SyntaxTree::TopConstField).void }
193
193
  def visit_top_const_field(node)
194
194
  create_document_symbol(
195
195
  name: node.constant.value,
196
196
  kind: :constant,
197
197
  range_node: node,
198
- selection_range_node: node.constant
198
+ selection_range_node: node.constant,
199
199
  )
200
200
  end
201
201
 
202
- sig { params(node: SyntaxTree::VarField).void }
202
+ sig { override.params(node: SyntaxTree::VarField).void }
203
203
  def visit_var_field(node)
204
204
  kind = case node.value
205
205
  when SyntaxTree::Const
@@ -214,7 +214,7 @@ module RubyLsp
214
214
  name: node.value.value,
215
215
  kind: kind,
216
216
  range_node: node,
217
- selection_range_node: node.value
217
+ selection_range_node: node.value,
218
218
  )
219
219
  end
220
220
 
@@ -225,7 +225,7 @@ module RubyLsp
225
225
  name: String,
226
226
  kind: Symbol,
227
227
  range_node: SyntaxTree::Node,
228
- selection_range_node: SyntaxTree::Node
228
+ selection_range_node: SyntaxTree::Node,
229
229
  ).returns(LanguageServer::Protocol::Interface::DocumentSymbol)
230
230
  end
231
231
  def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
@@ -66,14 +66,17 @@ module RubyLsp
66
66
 
67
67
  sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
68
68
  def run
69
- visit(@document.tree)
70
- emit_partial_range
69
+ if @document.parsed?
70
+ visit(@document.tree)
71
+ emit_partial_range
72
+ end
73
+
71
74
  @ranges
72
75
  end
73
76
 
74
77
  private
75
78
 
76
- sig { params(node: T.nilable(SyntaxTree::Node)).void }
79
+ sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
77
80
  def visit(node)
78
81
  return unless handle_partial_range(node)
79
82
 
@@ -137,7 +140,7 @@ module RubyLsp
137
140
  LanguageServer::Protocol::Interface::FoldingRange.new(
138
141
  start_line: @start_line,
139
142
  end_line: @end_line,
140
- kind: @kind
143
+ kind: @kind,
141
144
  )
142
145
  end
143
146
 
@@ -248,7 +251,7 @@ module RubyLsp
248
251
  @ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
249
252
  start_line: start_line - 1,
250
253
  end_line: end_line - 1,
251
- kind: "region"
254
+ kind: "region",
252
255
  )
253
256
  end
254
257
  end
@@ -42,9 +42,9 @@ module RubyLsp
42
42
  LanguageServer::Protocol::Interface::TextEdit.new(
43
43
  range: LanguageServer::Protocol::Interface::Range.new(
44
44
  start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
45
- end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size)
45
+ end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size),
46
46
  ),
47
- new_text: formatted_text
47
+ new_text: formatted_text,
48
48
  ),
49
49
  ]
50
50
  end
@@ -0,0 +1,74 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "ruby_lsp/requests/support/rails_document_client"
5
+
6
+ module RubyLsp
7
+ module Requests
8
+ # ![Hover demo](../../misc/rails_document_link_hover.gif)
9
+ #
10
+ # The [hover request](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover)
11
+ # renders a clickable link to the code's official documentation.
12
+ # It currently only supports Rails' documentation: when hovering over Rails DSLs/constants under certain paths,
13
+ # like `before_save :callback` in `models/post.rb`, it generates a link to `before_save`'s API documentation.
14
+ #
15
+ # # Example
16
+ #
17
+ # ```ruby
18
+ # class Post < ApplicationRecord
19
+ # before_save :do_something # when hovering on before_save, the link will be rendered
20
+ # end
21
+ # ```
22
+ class Hover < BaseRequest
23
+ extend T::Sig
24
+
25
+ sig { params(document: Document, position: Document::PositionShape).void }
26
+ def initialize(document, position)
27
+ super(document)
28
+
29
+ @position = T.let(Document::Scanner.new(document.source).find_position(position), Integer)
30
+ end
31
+
32
+ sig { override.returns(T.nilable(LanguageServer::Protocol::Interface::Hover)) }
33
+ def run
34
+ return unless @document.parsed?
35
+
36
+ target, _ = locate_node_and_parent(
37
+ T.must(@document.tree), [SyntaxTree::Command, SyntaxTree::FCall, SyntaxTree::ConstPathRef], @position
38
+ )
39
+
40
+ case target
41
+ when SyntaxTree::Command
42
+ message = target.message
43
+ generate_rails_document_link_hover(message.value, message)
44
+ when SyntaxTree::FCall
45
+ message = target.value
46
+ generate_rails_document_link_hover(message.value, message)
47
+ when SyntaxTree::ConstPathRef
48
+ constant_name = full_constant_name(target)
49
+ generate_rails_document_link_hover(constant_name, target)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ sig do
56
+ params(name: String, node: SyntaxTree::Node).returns(T.nilable(LanguageServer::Protocol::Interface::Hover))
57
+ end
58
+ def generate_rails_document_link_hover(name, node)
59
+ urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
60
+
61
+ return if urls.empty?
62
+
63
+ contents = LanguageServer::Protocol::Interface::MarkupContent.new(
64
+ kind: "markdown",
65
+ value: urls.join("\n\n"),
66
+ )
67
+ LanguageServer::Protocol::Interface::Hover.new(
68
+ range: range_from_syntax_tree_node(node),
69
+ contents: contents,
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Inlay hint demo](../../misc/inlay_hint.gif)
7
+ #
8
+ # [Inlay hints](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
9
+ # are labels added directly in the code that explicitly show the user something that might
10
+ # otherwise just be implied.
11
+ #
12
+ # # Example
13
+ #
14
+ # ```ruby
15
+ # begin
16
+ # puts "do something that might raise"
17
+ # rescue # Label "StandardError" goes here as a bare rescue implies rescuing StandardError
18
+ # puts "handle some rescue"
19
+ # end
20
+ # ```
21
+ class InlayHints < BaseRequest
22
+ RESCUE_STRING_LENGTH = T.let("rescue".length, Integer)
23
+
24
+ sig { params(document: Document, range: T::Range[Integer]).void }
25
+ def initialize(document, range)
26
+ super(document)
27
+
28
+ @hints = T.let([], T::Array[LanguageServer::Protocol::Interface::InlayHint])
29
+ @range = range
30
+ end
31
+
32
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::InlayHint], Object)) }
33
+ def run
34
+ visit(@document.tree) if @document.parsed?
35
+ @hints
36
+ end
37
+
38
+ sig { override.params(node: SyntaxTree::Rescue).void }
39
+ def visit_rescue(node)
40
+ return unless node.exception.nil?
41
+
42
+ loc = node.location
43
+ return unless @range.cover?(loc.start_line - 1) && @range.cover?(loc.end_line - 1)
44
+
45
+ @hints << LanguageServer::Protocol::Interface::InlayHint.new(
46
+ position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
47
+ label: "StandardError",
48
+ padding_left: true,
49
+ tooltip: "StandardError is implied in a bare rescue",
50
+ )
51
+
52
+ super
53
+ end
54
+ end
55
+ end
56
+ end