Skip to content

Commit cce49e2

Browse files
committed
Refactor flattening logic for highlighted sytax ranges
1 parent 8d296be commit cce49e2

File tree

1 file changed

+85
-53
lines changed

1 file changed

+85
-53
lines changed

crates/ra_ide/src/syntax_highlighting.rs

Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,80 @@ pub struct HighlightedRange {
3131
pub binding_hash: Option<u64>,
3232
}
3333

34+
#[derive(Debug)]
35+
struct HighlightedRangeStack {
36+
stack: Vec<Vec<HighlightedRange>>,
37+
}
38+
39+
/// We use a stack to implement the flattening logic for the highlighted
40+
/// syntax ranges.
41+
impl HighlightedRangeStack {
42+
fn new() -> Self {
43+
Self { stack: vec![Vec::new()] }
44+
}
45+
46+
fn push(&mut self) {
47+
self.stack.push(Vec::new());
48+
}
49+
50+
/// Flattens the highlighted ranges.
51+
///
52+
/// For example `#[cfg(feature = "foo")]` contains the nested ranges:
53+
/// 1) parent-range: Attribute [0, 23)
54+
/// 2) child-range: String [16, 21)
55+
///
56+
/// The following code implements the flattening, for our example this results to:
57+
/// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
58+
fn pop(&mut self) {
59+
let children = self.stack.pop().unwrap();
60+
let prev = self.stack.last_mut().unwrap();
61+
let needs_flattening = !children.is_empty()
62+
&& !prev.is_empty()
63+
&& children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
64+
if !needs_flattening {
65+
prev.extend(children);
66+
} else {
67+
let mut parent = prev.pop().unwrap();
68+
for ele in children {
69+
assert!(ele.range.is_subrange(&parent.range));
70+
let mut cloned = parent.clone();
71+
parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
72+
cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
73+
if !parent.range.is_empty() {
74+
prev.push(parent);
75+
}
76+
prev.push(ele);
77+
parent = cloned;
78+
}
79+
if !parent.range.is_empty() {
80+
prev.push(parent);
81+
}
82+
}
83+
}
84+
85+
fn add(&mut self, range: HighlightedRange) {
86+
self.stack
87+
.last_mut()
88+
.expect("during DFS traversal, the stack must not be empty")
89+
.push(range)
90+
}
91+
92+
fn flattened(mut self) -> Vec<HighlightedRange> {
93+
assert_eq!(
94+
self.stack.len(),
95+
1,
96+
"after DFS traversal, the stack should only contain a single element"
97+
);
98+
let res = self.stack.pop().unwrap();
99+
// Check that ranges are sorted and disjoint
100+
assert!(res
101+
.iter()
102+
.zip(res.iter().skip(1))
103+
.all(|(left, right)| left.range.end() <= right.range.start()));
104+
res
105+
}
106+
}
107+
34108
pub(crate) fn highlight(
35109
db: &RootDatabase,
36110
file_id: FileId,
@@ -57,52 +131,17 @@ pub(crate) fn highlight(
57131
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
58132
// We use a stack for the DFS traversal below.
59133
// When we leave a node, the we use it to flatten the highlighted ranges.
60-
let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()];
134+
let mut stack = HighlightedRangeStack::new();
61135

62136
let mut current_macro_call: Option<ast::MacroCall> = None;
63137

64138
// Walk all nodes, keeping track of whether we are inside a macro or not.
65139
// If in macro, expand it first and highlight the expanded code.
66140
for event in root.preorder_with_tokens() {
67141
match &event {
68-
WalkEvent::Enter(_) => res.push(Vec::new()),
69-
WalkEvent::Leave(_) => {
70-
/* Flattens the highlighted ranges.
71-
*
72-
* For example `#[cfg(feature = "foo")]` contains the nested ranges:
73-
* 1) parent-range: Attribute [0, 23)
74-
* 2) child-range: String [16, 21)
75-
*
76-
* The following code implements the flattening, for our example this results to:
77-
* `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
78-
*/
79-
let children = res.pop().unwrap();
80-
let prev = res.last_mut().unwrap();
81-
let needs_flattening = !children.is_empty()
82-
&& !prev.is_empty()
83-
&& children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
84-
if !needs_flattening {
85-
prev.extend(children);
86-
} else {
87-
let mut parent = prev.pop().unwrap();
88-
for ele in children {
89-
assert!(ele.range.is_subrange(&parent.range));
90-
let mut cloned = parent.clone();
91-
parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
92-
cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
93-
if !parent.range.is_empty() {
94-
prev.push(parent);
95-
}
96-
prev.push(ele);
97-
parent = cloned;
98-
}
99-
if !parent.range.is_empty() {
100-
prev.push(parent);
101-
}
102-
}
103-
}
142+
WalkEvent::Enter(_) => stack.push(),
143+
WalkEvent::Leave(_) => stack.pop(),
104144
};
105-
let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
106145

107146
let event_range = match &event {
108147
WalkEvent::Enter(it) => it.text_range(),
@@ -119,7 +158,7 @@ pub(crate) fn highlight(
119158
WalkEvent::Enter(Some(mc)) => {
120159
current_macro_call = Some(mc.clone());
121160
if let Some(range) = macro_call_range(&mc) {
122-
current.push(HighlightedRange {
161+
stack.add(HighlightedRange {
123162
range,
124163
highlight: HighlightTag::Macro.into(),
125164
binding_hash: None,
@@ -161,26 +200,19 @@ pub(crate) fn highlight(
161200

162201
if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
163202
let expanded = element_to_highlight.as_token().unwrap().clone();
164-
if highlight_injection(current, &sema, token, expanded).is_some() {
203+
if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
165204
continue;
166205
}
167206
}
168207

169208
if let Some((highlight, binding_hash)) =
170209
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
171210
{
172-
current.push(HighlightedRange { range, highlight, binding_hash });
211+
stack.add(HighlightedRange { range, highlight, binding_hash });
173212
}
174213
}
175214

176-
assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
177-
let res = res.pop().unwrap();
178-
// Check that ranges are sorted and disjoint
179-
assert!(res
180-
.iter()
181-
.zip(res.iter().skip(1))
182-
.all(|(left, right)| left.range.end() <= right.range.start()));
183-
res
215+
stack.flattened()
184216
}
185217

186218
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -357,7 +389,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
357389
}
358390

359391
fn highlight_injection(
360-
acc: &mut Vec<HighlightedRange>,
392+
acc: &mut HighlightedRangeStack,
361393
sema: &Semantics<RootDatabase>,
362394
literal: ast::RawString,
363395
expanded: SyntaxToken,
@@ -372,7 +404,7 @@ fn highlight_injection(
372404
let (analysis, tmp_file_id) = Analysis::from_single_file(value);
373405

374406
if let Some(range) = literal.open_quote_text_range() {
375-
acc.push(HighlightedRange {
407+
acc.add(HighlightedRange {
376408
range,
377409
highlight: HighlightTag::StringLiteral.into(),
378410
binding_hash: None,
@@ -382,12 +414,12 @@ fn highlight_injection(
382414
for mut h in analysis.highlight(tmp_file_id).unwrap() {
383415
if let Some(r) = literal.map_range_up(h.range) {
384416
h.range = r;
385-
acc.push(h)
417+
acc.add(h)
386418
}
387419
}
388420

389421
if let Some(range) = literal.close_quote_text_range() {
390-
acc.push(HighlightedRange {
422+
acc.add(HighlightedRange {
391423
range,
392424
highlight: HighlightTag::StringLiteral.into(),
393425
binding_hash: None,

0 commit comments

Comments
 (0)