Skip to content

Commit f6901c9

Browse files
bors[bot]Moritz Vetter
and
Moritz Vetter
authored
Merge #11462
11462: 11422 highlight continue and break r=Veykril a=HansAuger Closes #11422 Co-authored-by: Moritz Vetter <[email protected]>
2 parents 90f7899 + 71d158b commit f6901c9

File tree

3 files changed

+249
-54
lines changed

3 files changed

+249
-54
lines changed

crates/ide/src/highlight_related.rs

Lines changed: 155 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ use hir::Semantics;
22
use ide_db::{
33
base_db::{FileId, FilePosition},
44
defs::{Definition, IdentClass},
5-
helpers::{for_each_break_expr, for_each_tail_expr, node_ext::walk_expr, pick_best_token},
5+
helpers::{
6+
for_each_break_and_continue_expr, for_each_tail_expr, node_ext::walk_expr, pick_best_token,
7+
},
68
search::{FileReference, ReferenceCategory, SearchScope},
79
RootDatabase,
810
};
911
use rustc_hash::FxHashSet;
1012
use syntax::{
1113
ast::{self, HasLoopBody},
1214
match_ast, AstNode,
13-
SyntaxKind::{IDENT, INT_NUMBER},
15+
SyntaxKind::{self, IDENT, INT_NUMBER},
1416
SyntaxNode, SyntaxToken, TextRange, T,
1517
};
1618

@@ -66,7 +68,9 @@ pub(crate) fn highlight_related(
6668
T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
6769
highlight_break_points(token)
6870
}
69-
T![break] | T![loop] | T![while] if config.break_points => highlight_break_points(token),
71+
T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
72+
highlight_break_points(token)
73+
}
7074
_ if config.references => highlight_references(sema, &syntax, token, file_id),
7175
_ => None,
7276
}
@@ -187,6 +191,7 @@ fn highlight_exit_points(
187191

188192
fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
189193
fn hl(
194+
cursor_token_kind: SyntaxKind,
190195
token: Option<SyntaxToken>,
191196
label: Option<ast::Label>,
192197
body: Option<ast::StmtList>,
@@ -197,11 +202,23 @@ fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
197202
label.as_ref().map(|it| it.syntax().text_range()),
198203
);
199204
highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
200-
for_each_break_expr(label, body, &mut |break_| {
201-
let range = cover_range(
202-
break_.break_token().map(|it| it.text_range()),
203-
break_.lifetime().map(|it| it.syntax().text_range()),
204-
);
205+
for_each_break_and_continue_expr(label, body, &mut |expr| {
206+
let range: Option<TextRange> = match (cursor_token_kind, expr) {
207+
(T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
208+
cover_range(
209+
break_.break_token().map(|it| it.text_range()),
210+
break_.lifetime().map(|it| it.syntax().text_range()),
211+
)
212+
}
213+
(
214+
T![for] | T![while] | T![loop] | T![continue],
215+
ast::Expr::ContinueExpr(continue_),
216+
) => cover_range(
217+
continue_.continue_token().map(|it| it.text_range()),
218+
continue_.lifetime().map(|it| it.syntax().text_range()),
219+
),
220+
_ => None,
221+
};
205222
highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
206223
});
207224
Some(highlights)
@@ -210,6 +227,7 @@ fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
210227
let lbl = match_ast! {
211228
match parent {
212229
ast::BreakExpr(b) => b.lifetime(),
230+
ast::ContinueExpr(c) => c.lifetime(),
213231
ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
214232
ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
215233
ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
@@ -224,19 +242,29 @@ fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
224242
}
225243
None => true,
226244
};
245+
let token_kind = token.kind();
227246
for anc in token.ancestors().flat_map(ast::Expr::cast) {
228247
return match anc {
229-
ast::Expr::LoopExpr(l) if label_matches(l.label()) => {
230-
hl(l.loop_token(), l.label(), l.loop_body().and_then(|it| it.stmt_list()))
231-
}
232-
ast::Expr::ForExpr(f) if label_matches(f.label()) => {
233-
hl(f.for_token(), f.label(), f.loop_body().and_then(|it| it.stmt_list()))
234-
}
235-
ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
236-
hl(w.while_token(), w.label(), w.loop_body().and_then(|it| it.stmt_list()))
237-
}
248+
ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl(
249+
token_kind,
250+
l.loop_token(),
251+
l.label(),
252+
l.loop_body().and_then(|it| it.stmt_list()),
253+
),
254+
ast::Expr::ForExpr(f) if label_matches(f.label()) => hl(
255+
token_kind,
256+
f.for_token(),
257+
f.label(),
258+
f.loop_body().and_then(|it| it.stmt_list()),
259+
),
260+
ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl(
261+
token_kind,
262+
w.while_token(),
263+
w.label(),
264+
w.loop_body().and_then(|it| it.stmt_list()),
265+
),
238266
ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
239-
hl(None, e.label(), e.stmt_list())
267+
hl(token_kind, None, e.label(), e.stmt_list())
240268
}
241269
_ => continue,
242270
};
@@ -804,6 +832,115 @@ fn foo() {
804832
);
805833
}
806834

835+
#[test]
836+
fn test_hl_break_for_but_not_continue() {
837+
check(
838+
r#"
839+
fn foo() {
840+
'outer: for _ in () {
841+
// ^^^^^^^^^^^
842+
break;
843+
// ^^^^^
844+
continue;
845+
'inner: for _ in () {
846+
break;
847+
continue;
848+
'innermost: for _ in () {
849+
continue 'outer;
850+
break 'outer;
851+
// ^^^^^^^^^^^^
852+
continue 'inner;
853+
break 'inner;
854+
}
855+
break$0 'outer;
856+
// ^^^^^^^^^^^^
857+
continue 'outer;
858+
break;
859+
continue;
860+
}
861+
break;
862+
// ^^^^^
863+
continue;
864+
}
865+
}
866+
"#,
867+
);
868+
}
869+
870+
#[test]
871+
fn test_hl_continue_for_but_not_break() {
872+
check(
873+
r#"
874+
fn foo() {
875+
'outer: for _ in () {
876+
// ^^^^^^^^^^^
877+
break;
878+
continue;
879+
// ^^^^^^^^
880+
'inner: for _ in () {
881+
break;
882+
continue;
883+
'innermost: for _ in () {
884+
continue 'outer;
885+
// ^^^^^^^^^^^^^^^
886+
break 'outer;
887+
continue 'inner;
888+
break 'inner;
889+
}
890+
break 'outer;
891+
continue$0 'outer;
892+
// ^^^^^^^^^^^^^^^
893+
break;
894+
continue;
895+
}
896+
break;
897+
continue;
898+
// ^^^^^^^^
899+
}
900+
}
901+
"#,
902+
);
903+
}
904+
905+
#[test]
906+
fn test_hl_break_and_continue() {
907+
check(
908+
r#"
909+
fn foo() {
910+
'outer: fo$0r _ in () {
911+
// ^^^^^^^^^^^
912+
break;
913+
// ^^^^^
914+
continue;
915+
// ^^^^^^^^
916+
'inner: for _ in () {
917+
break;
918+
continue;
919+
'innermost: for _ in () {
920+
continue 'outer;
921+
// ^^^^^^^^^^^^^^^
922+
break 'outer;
923+
// ^^^^^^^^^^^^
924+
continue 'inner;
925+
break 'inner;
926+
}
927+
break 'outer;
928+
// ^^^^^^^^^^^^
929+
continue 'outer;
930+
// ^^^^^^^^^^^^^^^
931+
break;
932+
continue;
933+
}
934+
break;
935+
// ^^^^^
936+
continue;
937+
// ^^^^^^^^
938+
}
939+
}
940+
"#,
941+
);
942+
}
943+
807944
#[test]
808945
fn test_hl_break_while() {
809946
check(

crates/ide_db/src/helpers.rs

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use hir::{ItemInNs, MacroDef, ModuleDef, Name, Semantics};
1616
use itertools::Itertools;
1717
use syntax::{
1818
ast::{self, make, HasLoopBody},
19-
AstNode, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
19+
AstNode, AstToken, Preorder, RustLanguage, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
20+
T,
2021
};
2122

2223
use crate::{defs::Definition, RootDatabase};
@@ -190,46 +191,102 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
190191
}
191192
}
192193

193-
/// Calls `cb` on each break expr inside of `body` that is applicable for the given label.
194-
pub fn for_each_break_expr(
194+
pub fn for_each_break_and_continue_expr(
195+
label: Option<ast::Label>,
196+
body: Option<ast::StmtList>,
197+
cb: &mut dyn FnMut(ast::Expr),
198+
) {
199+
let label = label.and_then(|lbl| lbl.lifetime());
200+
if let Some(b) = body {
201+
let tree_depth_iterator = TreeWithDepthIterator::new(b);
202+
for (expr, depth) in tree_depth_iterator {
203+
match expr {
204+
ast::Expr::BreakExpr(b)
205+
if (depth == 0 && b.lifetime().is_none())
206+
|| eq_label_lt(&label, &b.lifetime()) =>
207+
{
208+
cb(ast::Expr::BreakExpr(b));
209+
}
210+
ast::Expr::ContinueExpr(c)
211+
if (depth == 0 && c.lifetime().is_none())
212+
|| eq_label_lt(&label, &c.lifetime()) =>
213+
{
214+
cb(ast::Expr::ContinueExpr(c));
215+
}
216+
_ => (),
217+
}
218+
}
219+
}
220+
}
221+
222+
fn for_each_break_expr(
195223
label: Option<ast::Label>,
196224
body: Option<ast::StmtList>,
197225
cb: &mut dyn FnMut(ast::BreakExpr),
198226
) {
199227
let label = label.and_then(|lbl| lbl.lifetime());
200-
let mut depth = 0;
201228
if let Some(b) = body {
202-
let preorder = &mut b.syntax().preorder();
203-
let ev_as_expr = |ev| match ev {
204-
WalkEvent::Enter(it) => Some(WalkEvent::Enter(ast::Expr::cast(it)?)),
205-
WalkEvent::Leave(it) => Some(WalkEvent::Leave(ast::Expr::cast(it)?)),
206-
};
207-
let eq_label = |lt: Option<ast::Lifetime>| {
208-
lt.zip(label.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text())
209-
};
210-
while let Some(node) = preorder.find_map(ev_as_expr) {
211-
match node {
212-
WalkEvent::Enter(expr) => match expr {
213-
ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => {
214-
depth += 1
215-
}
216-
ast::Expr::BlockExpr(e) if e.label().is_some() => depth += 1,
217-
ast::Expr::BreakExpr(b)
218-
if (depth == 0 && b.lifetime().is_none()) || eq_label(b.lifetime()) =>
219-
{
220-
cb(b);
221-
}
222-
_ => (),
223-
},
224-
WalkEvent::Leave(expr) => match expr {
225-
ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => {
226-
depth -= 1
227-
}
228-
ast::Expr::BlockExpr(e) if e.label().is_some() => depth -= 1,
229-
_ => (),
230-
},
229+
let tree_depth_iterator = TreeWithDepthIterator::new(b);
230+
for (expr, depth) in tree_depth_iterator {
231+
match expr {
232+
ast::Expr::BreakExpr(b)
233+
if (depth == 0 && b.lifetime().is_none())
234+
|| eq_label_lt(&label, &b.lifetime()) =>
235+
{
236+
cb(b);
237+
}
238+
_ => (),
239+
}
240+
}
241+
}
242+
}
243+
244+
fn eq_label_lt(lt1: &Option<ast::Lifetime>, lt2: &Option<ast::Lifetime>) -> bool {
245+
lt1.as_ref().zip(lt2.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text())
246+
}
247+
248+
struct TreeWithDepthIterator {
249+
preorder: Preorder<RustLanguage>,
250+
depth: u32,
251+
}
252+
253+
impl TreeWithDepthIterator {
254+
fn new(body: ast::StmtList) -> Self {
255+
let preorder = body.syntax().preorder();
256+
Self { preorder, depth: 0 }
257+
}
258+
}
259+
260+
impl<'a> Iterator for TreeWithDepthIterator {
261+
type Item = (ast::Expr, u32);
262+
263+
fn next(&mut self) -> Option<Self::Item> {
264+
while let Some(event) = self.preorder.find_map(|ev| match ev {
265+
WalkEvent::Enter(it) => ast::Expr::cast(it).map(WalkEvent::Enter),
266+
WalkEvent::Leave(it) => ast::Expr::cast(it).map(WalkEvent::Leave),
267+
}) {
268+
match event {
269+
WalkEvent::Enter(
270+
ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
271+
) => {
272+
self.depth += 1;
273+
}
274+
WalkEvent::Leave(
275+
ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
276+
) => {
277+
self.depth -= 1;
278+
}
279+
WalkEvent::Enter(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
280+
self.depth += 1;
281+
}
282+
WalkEvent::Leave(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
283+
self.depth -= 1;
284+
}
285+
WalkEvent::Enter(expr) => return Some((expr, self.depth)),
286+
_ => (),
231287
}
232288
}
289+
None
233290
}
234291
}
235292

crates/syntax/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ pub use crate::{
5252
ptr::{AstPtr, SyntaxNodePtr},
5353
syntax_error::SyntaxError,
5454
syntax_node::{
55-
PreorderWithTokens, SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren,
56-
SyntaxToken, SyntaxTreeBuilder,
55+
PreorderWithTokens, RustLanguage, SyntaxElement, SyntaxElementChildren, SyntaxNode,
56+
SyntaxNodeChildren, SyntaxToken, SyntaxTreeBuilder,
5757
},
5858
token_text::TokenText,
5959
};
6060
pub use parser::{SyntaxKind, T};
6161
pub use rowan::{
62-
Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize, TokenAtOffset, WalkEvent,
62+
api::Preorder, Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize,
63+
TokenAtOffset, WalkEvent,
6364
};
6465
pub use smol_str::SmolStr;
6566

0 commit comments

Comments
 (0)