ruby-lsp 0.13.2 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +3 -3
  5. data/lib/ruby_lsp/document.rb +12 -0
  6. data/lib/ruby_lsp/executor.rb +77 -266
  7. data/lib/ruby_lsp/listener.rb +1 -50
  8. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  9. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  10. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  11. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  12. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  13. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  14. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  15. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  16. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  17. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  18. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
  20. data/lib/ruby_lsp/requests/code_actions.rb +13 -4
  21. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  22. data/lib/ruby_lsp/requests/completion.rb +64 -244
  23. data/lib/ruby_lsp/requests/definition.rb +34 -147
  24. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  25. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  26. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  27. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  28. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  29. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  30. data/lib/ruby_lsp/requests/hover.rb +48 -92
  31. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  32. data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
  33. data/lib/ruby_lsp/requests/request.rb +17 -0
  34. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  36. data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
  37. data/lib/ruby_lsp/requests/signature_help.rb +43 -51
  38. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  39. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  40. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  42. data/lib/ruby_lsp/requests.rb +1 -1
  43. data/lib/ruby_lsp/utils.rb +8 -0
  44. metadata +15 -4
  45. 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