Skip to content

Commit d4064db

Browse files
authored
Highlight definition and control block structures (#2779)
### Motivation Closes #2494 ### Implementation If the cursor is on a definition or control node, both the start and end of the node will be highlighted. ### Automated Tests Amended existing test case. ### Manual Tests Place the cursor on one of the definition or control nodes.
1 parent fbe2350 commit d4064db

File tree

3 files changed

+127
-7
lines changed

3 files changed

+127
-7
lines changed

lib/ruby_lsp/listeners/document_highlight.rb

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,15 @@ class DocumentHighlight
9292
target: T.nilable(Prism::Node),
9393
parent: T.nilable(Prism::Node),
9494
dispatcher: Prism::Dispatcher,
95+
position: T::Hash[Symbol, T.untyped],
9596
).void
9697
end
97-
def initialize(response_builder, target, parent, dispatcher)
98+
def initialize(response_builder, target, parent, dispatcher, position)
9899
@response_builder = response_builder
99100

100101
return unless target && parent
101102

102-
highlight_target =
103+
highlight_target, highlight_target_value =
103104
case target
104105
when Prism::GlobalVariableReadNode, Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode,
105106
Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode, Prism::GlobalVariableWriteNode,
@@ -116,13 +117,17 @@ def initialize(response_builder, target, parent, dispatcher)
116117
Prism::CallNode, Prism::BlockParameterNode, Prism::RequiredKeywordParameterNode,
117118
Prism::RequiredKeywordParameterNode, Prism::KeywordRestParameterNode, Prism::OptionalParameterNode,
118119
Prism::RequiredParameterNode, Prism::RestParameterNode
120+
[target, node_value(target)]
121+
when Prism::ModuleNode, Prism::ClassNode, Prism::SingletonClassNode, Prism::DefNode, Prism::CaseNode,
122+
Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode
119123
target
120124
end
121125

122126
@target = T.let(highlight_target, T.nilable(Prism::Node))
123-
@target_value = T.let(node_value(highlight_target), T.nilable(String))
127+
@target_value = T.let(highlight_target_value, T.nilable(String))
128+
@target_position = position
124129

125-
if @target && @target_value
130+
if @target
126131
dispatcher.register(
127132
self,
128133
:on_call_node_enter,
@@ -172,6 +177,13 @@ def initialize(response_builder, target, parent, dispatcher)
172177
:on_global_variable_or_write_node_enter,
173178
:on_global_variable_and_write_node_enter,
174179
:on_global_variable_operator_write_node_enter,
180+
:on_singleton_class_node_enter,
181+
:on_case_node_enter,
182+
:on_while_node_enter,
183+
:on_until_node_enter,
184+
:on_for_node_enter,
185+
:on_if_node_enter,
186+
:on_unless_node_enter,
175187
)
176188
end
177189
end
@@ -189,6 +201,8 @@ def on_call_node_enter(node)
189201

190202
sig { params(node: Prism::DefNode).void }
191203
def on_def_node_enter(node)
204+
add_matching_end_highlights(node.def_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::DefNode)
205+
192206
return unless matches?(node, [Prism::CallNode, Prism::DefNode])
193207

194208
add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
@@ -252,13 +266,17 @@ def on_required_parameter_node_enter(node)
252266

253267
sig { params(node: Prism::ClassNode).void }
254268
def on_class_node_enter(node)
269+
add_matching_end_highlights(node.class_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::ClassNode)
270+
255271
return unless matches?(node, CONSTANT_NODES + CONSTANT_PATH_NODES + [Prism::ClassNode])
256272

257273
add_highlight(Constant::DocumentHighlightKind::WRITE, node.constant_path.location)
258274
end
259275

260276
sig { params(node: Prism::ModuleNode).void }
261277
def on_module_node_enter(node)
278+
add_matching_end_highlights(node.module_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::ModuleNode)
279+
262280
return unless matches?(node, CONSTANT_NODES + CONSTANT_PATH_NODES + [Prism::ModuleNode])
263281

264282
add_highlight(Constant::DocumentHighlightKind::WRITE, node.constant_path.location)
@@ -511,6 +529,55 @@ def on_global_variable_operator_write_node_enter(node)
511529
add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
512530
end
513531

532+
sig { params(node: Prism::SingletonClassNode).void }
533+
def on_singleton_class_node_enter(node)
534+
return unless @target.is_a?(Prism::SingletonClassNode)
535+
536+
add_matching_end_highlights(node.class_keyword_loc, node.end_keyword_loc)
537+
end
538+
539+
sig { params(node: Prism::CaseNode).void }
540+
def on_case_node_enter(node)
541+
return unless @target.is_a?(Prism::CaseNode)
542+
543+
add_matching_end_highlights(node.case_keyword_loc, node.end_keyword_loc)
544+
end
545+
546+
sig { params(node: Prism::WhileNode).void }
547+
def on_while_node_enter(node)
548+
return unless @target.is_a?(Prism::WhileNode)
549+
550+
add_matching_end_highlights(node.keyword_loc, node.closing_loc)
551+
end
552+
553+
sig { params(node: Prism::UntilNode).void }
554+
def on_until_node_enter(node)
555+
return unless @target.is_a?(Prism::UntilNode)
556+
557+
add_matching_end_highlights(node.keyword_loc, node.closing_loc)
558+
end
559+
560+
sig { params(node: Prism::ForNode).void }
561+
def on_for_node_enter(node)
562+
return unless @target.is_a?(Prism::ForNode)
563+
564+
add_matching_end_highlights(node.for_keyword_loc, node.end_keyword_loc)
565+
end
566+
567+
sig { params(node: Prism::IfNode).void }
568+
def on_if_node_enter(node)
569+
return unless @target.is_a?(Prism::IfNode)
570+
571+
add_matching_end_highlights(node.if_keyword_loc, node.end_keyword_loc)
572+
end
573+
574+
sig { params(node: Prism::UnlessNode).void }
575+
def on_unless_node_enter(node)
576+
return unless @target.is_a?(Prism::UnlessNode)
577+
578+
add_matching_end_highlights(node.keyword_loc, node.end_keyword_loc)
579+
end
580+
514581
private
515582

516583
sig { params(node: Prism::Node, classes: T::Array[T.class_of(Prism::Node)]).returns(T.nilable(T::Boolean)) }
@@ -550,6 +617,26 @@ def node_value(node)
550617
node.constant_path.slice
551618
end
552619
end
620+
621+
sig { params(keyword_loc: T.nilable(Prism::Location), end_loc: T.nilable(Prism::Location)).void }
622+
def add_matching_end_highlights(keyword_loc, end_loc)
623+
return unless keyword_loc && end_loc && end_loc.length.positive?
624+
return unless covers_target_position?(keyword_loc) || covers_target_position?(end_loc)
625+
626+
add_highlight(Constant::DocumentHighlightKind::TEXT, keyword_loc)
627+
add_highlight(Constant::DocumentHighlightKind::TEXT, end_loc)
628+
end
629+
630+
sig { params(location: Prism::Location).returns(T::Boolean) }
631+
def covers_target_position?(location)
632+
start_line = location.start_line - 1
633+
end_line = location.end_line - 1
634+
start_covered = start_line < @target_position[:line] ||
635+
(start_line == @target_position[:line] && location.start_column <= @target_position[:character])
636+
end_covered = end_line > @target_position[:line] ||
637+
(end_line == @target_position[:line] && location.end_column >= @target_position[:character])
638+
start_covered && end_covered
639+
end
553640
end
554641
end
555642
end

lib/ruby_lsp/requests/document_highlight.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ def initialize(global_state, document, position, dispatcher)
3838
ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight].new,
3939
ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight],
4040
)
41-
Listeners::DocumentHighlight.new(@response_builder, node_context.node, node_context.parent, dispatcher)
41+
Listeners::DocumentHighlight.new(
42+
@response_builder,
43+
node_context.node,
44+
node_context.parent,
45+
dispatcher,
46+
position,
47+
)
4248
end
4349

4450
sig { override.returns(T::Array[Interface::DocumentHighlight]) }
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
11
{
22
"params": [
33
{
4-
"line": 1,
4+
"line": 0,
55
"character": 2
66
}
77
],
8-
"result": []
8+
"result": [
9+
{
10+
"range": {
11+
"start": {
12+
"line": 0,
13+
"character": 0
14+
},
15+
"end": {
16+
"line": 0,
17+
"character": 5
18+
}
19+
},
20+
"kind": 1
21+
},
22+
{
23+
"range": {
24+
"start": {
25+
"line": 4,
26+
"character": 0
27+
},
28+
"end": {
29+
"line": 4,
30+
"character": 3
31+
}
32+
},
33+
"kind": 1
34+
}
35+
]
936
}

0 commit comments

Comments
 (0)