ruby-lsp 0.13.1 → 0.13.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -1
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +30 -1
  6. data/lib/ruby_lsp/check_docs.rb +3 -3
  7. data/lib/ruby_lsp/document.rb +15 -7
  8. data/lib/ruby_lsp/executor.rb +85 -226
  9. data/lib/ruby_lsp/listener.rb +1 -50
  10. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  11. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  12. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  13. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  14. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  15. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  16. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  17. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  18. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  19. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  20. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  21. data/lib/ruby_lsp/requests/code_action_resolve.rb +5 -5
  22. data/lib/ruby_lsp/requests/code_actions.rb +15 -6
  23. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  24. data/lib/ruby_lsp/requests/completion.rb +64 -246
  25. data/lib/ruby_lsp/requests/definition.rb +34 -147
  26. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  27. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  28. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  29. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  30. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  31. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  32. data/lib/ruby_lsp/requests/hover.rb +48 -92
  33. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +18 -6
  35. data/lib/ruby_lsp/requests/request.rb +17 -0
  36. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  37. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  38. data/lib/ruby_lsp/requests/show_syntax_tree.rb +5 -5
  39. data/lib/ruby_lsp/requests/signature_help.rb +87 -0
  40. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  41. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  42. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
  43. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  44. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  45. data/lib/ruby_lsp/requests.rb +3 -1
  46. data/lib/ruby_lsp/store.rb +1 -1
  47. data/lib/ruby_lsp/utils.rb +8 -0
  48. metadata +20 -8
  49. data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -0,0 +1,162 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "ruby_lsp/requests/support/source_uri"
5
+
6
+ module RubyLsp
7
+ module Listeners
8
+ class DocumentLink < Listener
9
+ extend T::Sig
10
+ extend T::Generic
11
+
12
+ ResponseType = type_member { { fixed: T::Array[Interface::DocumentLink] } }
13
+
14
+ GEM_TO_VERSION_MAP = T.let(
15
+ [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].map! do |s|
16
+ [s.name, s.version.to_s]
17
+ end.to_h.freeze,
18
+ T::Hash[String, String],
19
+ )
20
+
21
+ class << self
22
+ extend T::Sig
23
+
24
+ sig { returns(T::Hash[String, T::Hash[String, T::Hash[String, String]]]) }
25
+ def gem_paths
26
+ @gem_paths ||= T.let(
27
+ begin
28
+ lookup = {}
29
+
30
+ Gem::Specification.stubs.each do |stub|
31
+ spec = stub.to_spec
32
+ lookup[spec.name] = {}
33
+ lookup[spec.name][spec.version.to_s] = {}
34
+
35
+ Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
36
+ lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
37
+ end
38
+ end
39
+
40
+ Gem::Specification.default_stubs.each do |stub|
41
+ spec = stub.to_spec
42
+ lookup[spec.name] = {}
43
+ lookup[spec.name][spec.version.to_s] = {}
44
+ prefix_matchers = Regexp.union(spec.require_paths.map do |rp|
45
+ Regexp.new("^#{rp}/")
46
+ end)
47
+ prefix_matcher = Regexp.union(prefix_matchers, //)
48
+
49
+ spec.files.each do |file|
50
+ path = file.sub(prefix_matcher, "")
51
+ lookup[spec.name][spec.version.to_s][path] = "#{RbConfig::CONFIG["rubylibdir"]}/#{path}"
52
+ end
53
+ end
54
+
55
+ lookup
56
+ end,
57
+ T.nilable(T::Hash[String, T::Hash[String, T::Hash[String, String]]]),
58
+ )
59
+ end
60
+ end
61
+
62
+ sig { override.returns(ResponseType) }
63
+ attr_reader :_response
64
+
65
+ sig do
66
+ params(
67
+ uri: URI::Generic,
68
+ comments: T::Array[Prism::Comment],
69
+ dispatcher: Prism::Dispatcher,
70
+ ).void
71
+ end
72
+ def initialize(uri, comments, dispatcher)
73
+ super(dispatcher)
74
+
75
+ # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
76
+ # in the URI
77
+ path = uri.to_standardized_path
78
+ version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
79
+ @gem_version = T.let(version_match && version_match[0], T.nilable(String))
80
+ @_response = T.let([], T::Array[Interface::DocumentLink])
81
+ @lines_to_comments = T.let(
82
+ comments.to_h do |comment|
83
+ [comment.location.end_line, comment]
84
+ end,
85
+ T::Hash[Integer, Prism::Comment],
86
+ )
87
+
88
+ dispatcher.register(
89
+ self,
90
+ :on_def_node_enter,
91
+ :on_class_node_enter,
92
+ :on_module_node_enter,
93
+ :on_constant_write_node_enter,
94
+ :on_constant_path_write_node_enter,
95
+ )
96
+ end
97
+
98
+ sig { params(node: Prism::DefNode).void }
99
+ def on_def_node_enter(node)
100
+ extract_document_link(node)
101
+ end
102
+
103
+ sig { params(node: Prism::ClassNode).void }
104
+ def on_class_node_enter(node)
105
+ extract_document_link(node)
106
+ end
107
+
108
+ sig { params(node: Prism::ModuleNode).void }
109
+ def on_module_node_enter(node)
110
+ extract_document_link(node)
111
+ end
112
+
113
+ sig { params(node: Prism::ConstantWriteNode).void }
114
+ def on_constant_write_node_enter(node)
115
+ extract_document_link(node)
116
+ end
117
+
118
+ sig { params(node: Prism::ConstantPathWriteNode).void }
119
+ def on_constant_path_write_node_enter(node)
120
+ extract_document_link(node)
121
+ end
122
+
123
+ private
124
+
125
+ sig { params(node: Prism::Node).void }
126
+ def extract_document_link(node)
127
+ comment = @lines_to_comments[node.location.start_line - 1]
128
+ return unless comment
129
+
130
+ match = comment.location.slice.match(%r{source://.*#\d+$})
131
+ return unless match
132
+
133
+ uri = T.cast(URI(T.must(match[0])), URI::Source)
134
+ gem_version = resolve_version(uri)
135
+ return if gem_version.nil?
136
+
137
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
138
+ return if file_path.nil?
139
+
140
+ @_response << Interface::DocumentLink.new(
141
+ range: range_from_location(comment.location),
142
+ target: "file://#{file_path}##{uri.line_number}",
143
+ tooltip: "Jump to #{file_path}##{uri.line_number}",
144
+ )
145
+ end
146
+
147
+ # Try to figure out the gem version for a source:// link. The order of precedence is:
148
+ # 1. The version in the URI
149
+ # 2. The version in the RBI file name
150
+ # 3. The version from the gemspec
151
+ sig { params(uri: URI::Source).returns(T.nilable(String)) }
152
+ def resolve_version(uri)
153
+ version = uri.gem_version
154
+ return version unless version.nil? || version.empty?
155
+
156
+ return @gem_version unless @gem_version.nil? || @gem_version.empty?
157
+
158
+ GEM_TO_VERSION_MAP[uri.gem_name]
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,223 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class DocumentSymbol < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T::Array[Interface::DocumentSymbol] } }
11
+
12
+ ATTR_ACCESSORS = T.let([:attr_reader, :attr_writer, :attr_accessor].freeze, T::Array[Symbol])
13
+
14
+ class SymbolHierarchyRoot
15
+ extend T::Sig
16
+
17
+ sig { returns(T::Array[Interface::DocumentSymbol]) }
18
+ attr_reader :children
19
+
20
+ sig { void }
21
+ def initialize
22
+ @children = T.let([], T::Array[Interface::DocumentSymbol])
23
+ end
24
+ end
25
+
26
+ sig { override.returns(T::Array[Interface::DocumentSymbol]) }
27
+ attr_reader :_response
28
+
29
+ sig { params(dispatcher: Prism::Dispatcher).void }
30
+ def initialize(dispatcher)
31
+ @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
32
+ @_response = T.let(@root.children, T::Array[Interface::DocumentSymbol])
33
+ @stack = T.let(
34
+ [@root],
35
+ T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
36
+ )
37
+
38
+ super
39
+
40
+ dispatcher.register(
41
+ self,
42
+ :on_class_node_enter,
43
+ :on_class_node_leave,
44
+ :on_call_node_enter,
45
+ :on_constant_path_write_node_enter,
46
+ :on_constant_write_node_enter,
47
+ :on_def_node_enter,
48
+ :on_def_node_leave,
49
+ :on_module_node_enter,
50
+ :on_module_node_leave,
51
+ :on_instance_variable_write_node_enter,
52
+ :on_class_variable_write_node_enter,
53
+ :on_singleton_class_node_enter,
54
+ :on_singleton_class_node_leave,
55
+ )
56
+ end
57
+
58
+ sig { params(node: Prism::ClassNode).void }
59
+ def on_class_node_enter(node)
60
+ @stack << create_document_symbol(
61
+ name: node.constant_path.location.slice,
62
+ kind: Constant::SymbolKind::CLASS,
63
+ range_location: node.location,
64
+ selection_range_location: node.constant_path.location,
65
+ )
66
+ end
67
+
68
+ sig { params(node: Prism::ClassNode).void }
69
+ def on_class_node_leave(node)
70
+ @stack.pop
71
+ end
72
+
73
+ sig { params(node: Prism::SingletonClassNode).void }
74
+ def on_singleton_class_node_enter(node)
75
+ expression = node.expression
76
+
77
+ @stack << create_document_symbol(
78
+ name: "<< #{expression.slice}",
79
+ kind: Constant::SymbolKind::NAMESPACE,
80
+ range_location: node.location,
81
+ selection_range_location: expression.location,
82
+ )
83
+ end
84
+
85
+ sig { params(node: Prism::SingletonClassNode).void }
86
+ def on_singleton_class_node_leave(node)
87
+ @stack.pop
88
+ end
89
+
90
+ sig { params(node: Prism::CallNode).void }
91
+ def on_call_node_enter(node)
92
+ return unless ATTR_ACCESSORS.include?(node.name) && node.receiver.nil?
93
+
94
+ arguments = node.arguments
95
+ return unless arguments
96
+
97
+ arguments.arguments.each do |argument|
98
+ next unless argument.is_a?(Prism::SymbolNode)
99
+
100
+ name = argument.value
101
+ next unless name
102
+
103
+ create_document_symbol(
104
+ name: name,
105
+ kind: Constant::SymbolKind::FIELD,
106
+ range_location: argument.location,
107
+ selection_range_location: T.must(argument.value_loc),
108
+ )
109
+ end
110
+ end
111
+
112
+ sig { params(node: Prism::ConstantPathWriteNode).void }
113
+ def on_constant_path_write_node_enter(node)
114
+ create_document_symbol(
115
+ name: node.target.location.slice,
116
+ kind: Constant::SymbolKind::CONSTANT,
117
+ range_location: node.location,
118
+ selection_range_location: node.target.location,
119
+ )
120
+ end
121
+
122
+ sig { params(node: Prism::ConstantWriteNode).void }
123
+ def on_constant_write_node_enter(node)
124
+ create_document_symbol(
125
+ name: node.name.to_s,
126
+ kind: Constant::SymbolKind::CONSTANT,
127
+ range_location: node.location,
128
+ selection_range_location: node.name_loc,
129
+ )
130
+ end
131
+
132
+ sig { params(node: Prism::DefNode).void }
133
+ def on_def_node_leave(node)
134
+ @stack.pop
135
+ end
136
+
137
+ sig { params(node: Prism::ModuleNode).void }
138
+ def on_module_node_enter(node)
139
+ @stack << create_document_symbol(
140
+ name: node.constant_path.location.slice,
141
+ kind: Constant::SymbolKind::MODULE,
142
+ range_location: node.location,
143
+ selection_range_location: node.constant_path.location,
144
+ )
145
+ end
146
+
147
+ sig { params(node: Prism::DefNode).void }
148
+ def on_def_node_enter(node)
149
+ receiver = node.receiver
150
+ previous_symbol = @stack.last
151
+
152
+ if receiver.is_a?(Prism::SelfNode)
153
+ name = "self.#{node.name}"
154
+ kind = Constant::SymbolKind::FUNCTION
155
+ elsif previous_symbol.is_a?(Interface::DocumentSymbol) && previous_symbol.name.start_with?("<<")
156
+ name = node.name.to_s
157
+ kind = Constant::SymbolKind::FUNCTION
158
+ else
159
+ name = node.name.to_s
160
+ kind = name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
161
+ end
162
+
163
+ symbol = create_document_symbol(
164
+ name: name,
165
+ kind: kind,
166
+ range_location: node.location,
167
+ selection_range_location: node.name_loc,
168
+ )
169
+
170
+ @stack << symbol
171
+ end
172
+
173
+ sig { params(node: Prism::ModuleNode).void }
174
+ def on_module_node_leave(node)
175
+ @stack.pop
176
+ end
177
+
178
+ sig { params(node: Prism::InstanceVariableWriteNode).void }
179
+ def on_instance_variable_write_node_enter(node)
180
+ create_document_symbol(
181
+ name: node.name.to_s,
182
+ kind: Constant::SymbolKind::VARIABLE,
183
+ range_location: node.name_loc,
184
+ selection_range_location: node.name_loc,
185
+ )
186
+ end
187
+
188
+ sig { params(node: Prism::ClassVariableWriteNode).void }
189
+ def on_class_variable_write_node_enter(node)
190
+ create_document_symbol(
191
+ name: node.name.to_s,
192
+ kind: Constant::SymbolKind::VARIABLE,
193
+ range_location: node.name_loc,
194
+ selection_range_location: node.name_loc,
195
+ )
196
+ end
197
+
198
+ private
199
+
200
+ sig do
201
+ params(
202
+ name: String,
203
+ kind: Integer,
204
+ range_location: Prism::Location,
205
+ selection_range_location: Prism::Location,
206
+ ).returns(Interface::DocumentSymbol)
207
+ end
208
+ def create_document_symbol(name:, kind:, range_location:, selection_range_location:)
209
+ symbol = Interface::DocumentSymbol.new(
210
+ name: name,
211
+ kind: kind,
212
+ range: range_from_location(range_location),
213
+ selection_range: range_from_location(selection_range_location),
214
+ children: [],
215
+ )
216
+
217
+ T.must(@stack.last).children << symbol
218
+
219
+ symbol
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,271 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class FoldingRanges < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T::Array[Interface::FoldingRange] } }
11
+
12
+ sig { params(comments: T::Array[Prism::Comment], dispatcher: Prism::Dispatcher).void }
13
+ def initialize(comments, dispatcher)
14
+ super(dispatcher)
15
+
16
+ @_response = T.let([], ResponseType)
17
+ @requires = T.let([], T::Array[Prism::CallNode])
18
+ @finalized_response = T.let(false, T::Boolean)
19
+ @comments = comments
20
+
21
+ dispatcher.register(
22
+ self,
23
+ :on_if_node_enter,
24
+ :on_in_node_enter,
25
+ :on_rescue_node_enter,
26
+ :on_when_node_enter,
27
+ :on_interpolated_string_node_enter,
28
+ :on_array_node_enter,
29
+ :on_block_node_enter,
30
+ :on_case_node_enter,
31
+ :on_case_match_node_enter,
32
+ :on_class_node_enter,
33
+ :on_module_node_enter,
34
+ :on_for_node_enter,
35
+ :on_hash_node_enter,
36
+ :on_singleton_class_node_enter,
37
+ :on_unless_node_enter,
38
+ :on_until_node_enter,
39
+ :on_while_node_enter,
40
+ :on_else_node_enter,
41
+ :on_ensure_node_enter,
42
+ :on_begin_node_enter,
43
+ :on_def_node_enter,
44
+ :on_call_node_enter,
45
+ :on_lambda_node_enter,
46
+ )
47
+ end
48
+
49
+ sig { override.returns(ResponseType) }
50
+ def _response
51
+ unless @finalized_response
52
+ push_comment_ranges
53
+ emit_requires_range
54
+ @finalized_response = true
55
+ end
56
+
57
+ @_response
58
+ end
59
+
60
+ sig { params(node: Prism::IfNode).void }
61
+ def on_if_node_enter(node)
62
+ add_statements_range(node)
63
+ end
64
+
65
+ sig { params(node: Prism::InNode).void }
66
+ def on_in_node_enter(node)
67
+ add_statements_range(node)
68
+ end
69
+
70
+ sig { params(node: Prism::RescueNode).void }
71
+ def on_rescue_node_enter(node)
72
+ add_statements_range(node)
73
+ end
74
+
75
+ sig { params(node: Prism::WhenNode).void }
76
+ def on_when_node_enter(node)
77
+ add_statements_range(node)
78
+ end
79
+
80
+ sig { params(node: Prism::InterpolatedStringNode).void }
81
+ def on_interpolated_string_node_enter(node)
82
+ opening_loc = node.opening_loc || node.location
83
+ closing_loc = node.closing_loc || node.parts.last&.location || node.location
84
+
85
+ add_lines_range(opening_loc.start_line, closing_loc.start_line - 1)
86
+ end
87
+
88
+ sig { params(node: Prism::ArrayNode).void }
89
+ def on_array_node_enter(node)
90
+ add_simple_range(node)
91
+ end
92
+
93
+ sig { params(node: Prism::BlockNode).void }
94
+ def on_block_node_enter(node)
95
+ add_simple_range(node)
96
+ end
97
+
98
+ sig { params(node: Prism::CaseNode).void }
99
+ def on_case_node_enter(node)
100
+ add_simple_range(node)
101
+ end
102
+
103
+ sig { params(node: Prism::CaseMatchNode).void }
104
+ def on_case_match_node_enter(node)
105
+ add_simple_range(node)
106
+ end
107
+
108
+ sig { params(node: Prism::ClassNode).void }
109
+ def on_class_node_enter(node)
110
+ add_simple_range(node)
111
+ end
112
+
113
+ sig { params(node: Prism::ModuleNode).void }
114
+ def on_module_node_enter(node)
115
+ add_simple_range(node)
116
+ end
117
+
118
+ sig { params(node: Prism::ForNode).void }
119
+ def on_for_node_enter(node)
120
+ add_simple_range(node)
121
+ end
122
+
123
+ sig { params(node: Prism::HashNode).void }
124
+ def on_hash_node_enter(node)
125
+ add_simple_range(node)
126
+ end
127
+
128
+ sig { params(node: Prism::SingletonClassNode).void }
129
+ def on_singleton_class_node_enter(node)
130
+ add_simple_range(node)
131
+ end
132
+
133
+ sig { params(node: Prism::UnlessNode).void }
134
+ def on_unless_node_enter(node)
135
+ add_simple_range(node)
136
+ end
137
+
138
+ sig { params(node: Prism::UntilNode).void }
139
+ def on_until_node_enter(node)
140
+ add_simple_range(node)
141
+ end
142
+
143
+ sig { params(node: Prism::WhileNode).void }
144
+ def on_while_node_enter(node)
145
+ add_simple_range(node)
146
+ end
147
+
148
+ sig { params(node: Prism::ElseNode).void }
149
+ def on_else_node_enter(node)
150
+ add_simple_range(node)
151
+ end
152
+
153
+ sig { params(node: Prism::EnsureNode).void }
154
+ def on_ensure_node_enter(node)
155
+ add_simple_range(node)
156
+ end
157
+
158
+ sig { params(node: Prism::BeginNode).void }
159
+ def on_begin_node_enter(node)
160
+ add_simple_range(node)
161
+ end
162
+
163
+ sig { params(node: Prism::DefNode).void }
164
+ def on_def_node_enter(node)
165
+ params = node.parameters
166
+ parameter_loc = params&.location
167
+ location = node.location
168
+
169
+ if params && parameter_loc.end_line > location.start_line
170
+ # Multiline parameters
171
+ add_lines_range(location.start_line, parameter_loc.end_line)
172
+ add_lines_range(parameter_loc.end_line + 1, location.end_line - 1)
173
+ else
174
+ add_lines_range(location.start_line, location.end_line - 1)
175
+ end
176
+ end
177
+
178
+ sig { params(node: Prism::CallNode).void }
179
+ def on_call_node_enter(node)
180
+ # If we find a require, don't visit the child nodes (prevent `super`), so that we can keep accumulating into
181
+ # the `@requires` array and then push the range whenever we find a node that isn't a CallNode
182
+ if require?(node)
183
+ @requires << node
184
+ return
185
+ end
186
+
187
+ location = node.location
188
+ add_lines_range(location.start_line, location.end_line - 1)
189
+ end
190
+
191
+ sig { params(node: Prism::LambdaNode).void }
192
+ def on_lambda_node_enter(node)
193
+ add_simple_range(node)
194
+ end
195
+
196
+ private
197
+
198
+ sig { void }
199
+ def push_comment_ranges
200
+ # Group comments that are on consecutive lines and then push ranges for each group that has at least 2 comments
201
+ @comments.chunk_while do |this, other|
202
+ this.location.end_line + 1 == other.location.start_line
203
+ end.each do |chunk|
204
+ next if chunk.length == 1
205
+
206
+ @_response << Interface::FoldingRange.new(
207
+ start_line: T.must(chunk.first).location.start_line - 1,
208
+ end_line: T.must(chunk.last).location.end_line - 1,
209
+ kind: "comment",
210
+ )
211
+ end
212
+ end
213
+
214
+ sig { void }
215
+ def emit_requires_range
216
+ if @requires.length > 1
217
+ @_response << Interface::FoldingRange.new(
218
+ start_line: T.must(@requires.first).location.start_line - 1,
219
+ end_line: T.must(@requires.last).location.end_line - 1,
220
+ kind: "imports",
221
+ )
222
+ end
223
+
224
+ @requires.clear
225
+ end
226
+
227
+ sig { params(node: Prism::CallNode).returns(T::Boolean) }
228
+ def require?(node)
229
+ message = node.message
230
+ return false unless message == "require" || message == "require_relative"
231
+
232
+ receiver = node.receiver
233
+ return false unless receiver.nil? || receiver.slice == "Kernel"
234
+
235
+ arguments = node.arguments&.arguments
236
+ return false unless arguments
237
+
238
+ arguments.length == 1 && arguments.first.is_a?(Prism::StringNode)
239
+ end
240
+
241
+ sig { params(node: T.any(Prism::IfNode, Prism::InNode, Prism::RescueNode, Prism::WhenNode)).void }
242
+ def add_statements_range(node)
243
+ statements = node.statements
244
+ return unless statements
245
+
246
+ body = statements.body
247
+ return if body.empty?
248
+
249
+ add_lines_range(node.location.start_line, T.must(body.last).location.end_line)
250
+ end
251
+
252
+ sig { params(node: Prism::Node).void }
253
+ def add_simple_range(node)
254
+ location = node.location
255
+ add_lines_range(location.start_line, location.end_line - 1)
256
+ end
257
+
258
+ sig { params(start_line: Integer, end_line: Integer).void }
259
+ def add_lines_range(start_line, end_line)
260
+ emit_requires_range
261
+ return if start_line >= end_line
262
+
263
+ @_response << Interface::FoldingRange.new(
264
+ start_line: start_line - 1,
265
+ end_line: end_line - 1,
266
+ kind: "region",
267
+ )
268
+ end
269
+ end
270
+ end
271
+ end