ruby-lsp 0.3.2 → 0.3.4

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