aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
authorLeander Tentrup <[email protected]>2020-04-16 13:29:58 +0100
committerLeander Tentrup <[email protected]>2020-04-18 14:02:51 +0100
commit29a846464b63259f5152d61a5520bffcc2cb8703 (patch)
tree996c0c95a03db806c84da492156a1d95ff40f486 /crates/ra_ide/src
parent84e3304a9bf0d68e30d58b1e37a6db2e9ec97525 (diff)
Refactor flattening logic for highlighted syntax ranges
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs139
1 files changed, 85 insertions, 54 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 7b15b82bd..e7d9bf696 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -31,6 +31,80 @@ pub struct HighlightedRange {
31 pub binding_hash: Option<u64>, 31 pub binding_hash: Option<u64>,
32} 32}
33 33
34#[derive(Debug)]
35struct HighlightedRangeStack {
36 stack: Vec<Vec<HighlightedRange>>,
37}
38
39/// We use a stack to implement the flattening logic for the highlighted
40/// syntax ranges.
41impl 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
34pub(crate) fn highlight( 108pub(crate) fn highlight(
35 db: &RootDatabase, 109 db: &RootDatabase,
36 file_id: FileId, 110 file_id: FileId,
@@ -57,7 +131,7 @@ pub(crate) fn highlight(
57 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 131 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
58 // We use a stack for the DFS traversal below. 132 // We use a stack for the DFS traversal below.
59 // When we leave a node, the we use it to flatten the highlighted ranges. 133 // 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();
61 135
62 let mut current_macro_call: Option<ast::MacroCall> = None; 136 let mut current_macro_call: Option<ast::MacroCall> = None;
63 137
@@ -65,44 +139,9 @@ pub(crate) fn highlight(
65 // If in macro, expand it first and highlight the expanded code. 139 // If in macro, expand it first and highlight the expanded code.
66 for event in root.preorder_with_tokens() { 140 for event in root.preorder_with_tokens() {
67 match &event { 141 match &event {
68 WalkEvent::Enter(_) => res.push(Vec::new()), 142 WalkEvent::Enter(_) => stack.push(),
69 WalkEvent::Leave(_) => { 143 WalkEvent::Leave(_) => stack.pop(),
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 }
104 }; 144 };
105 let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
106 145
107 let event_range = match &event { 146 let event_range = match &event {
108 WalkEvent::Enter(it) => it.text_range(), 147 WalkEvent::Enter(it) => it.text_range(),
@@ -119,7 +158,7 @@ pub(crate) fn highlight(
119 WalkEvent::Enter(Some(mc)) => { 158 WalkEvent::Enter(Some(mc)) => {
120 current_macro_call = Some(mc.clone()); 159 current_macro_call = Some(mc.clone());
121 if let Some(range) = macro_call_range(&mc) { 160 if let Some(range) = macro_call_range(&mc) {
122 current.push(HighlightedRange { 161 stack.add(HighlightedRange {
123 range, 162 range,
124 highlight: HighlightTag::Macro.into(), 163 highlight: HighlightTag::Macro.into(),
125 binding_hash: None, 164 binding_hash: None,
@@ -161,7 +200,7 @@ pub(crate) fn highlight(
161 200
162 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 201 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
163 let expanded = element_to_highlight.as_token().unwrap().clone(); 202 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() {
165 continue; 204 continue;
166 } 205 }
167 } 206 }
@@ -169,19 +208,11 @@ pub(crate) fn highlight(
169 if let Some((highlight, binding_hash)) = 208 if let Some((highlight, binding_hash)) =
170 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) 209 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
171 { 210 {
172 current.push(HighlightedRange { range, highlight, binding_hash }); 211 stack.add(HighlightedRange { range, highlight, binding_hash });
173 } 212 }
174 } 213 }
175 214
176 assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); 215 stack.flattened()
177 let mut res = res.pop().unwrap();
178 res.sort_by_key(|range| range.range.start());
179 // Check that ranges are sorted and disjoint
180 assert!(res
181 .iter()
182 .zip(res.iter().skip(1))
183 .all(|(left, right)| left.range.end() <= right.range.start()));
184 res
185} 216}
186 217
187fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 218fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -358,7 +389,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
358} 389}
359 390
360fn highlight_injection( 391fn highlight_injection(
361 acc: &mut Vec<HighlightedRange>, 392 acc: &mut HighlightedRangeStack,
362 sema: &Semantics<RootDatabase>, 393 sema: &Semantics<RootDatabase>,
363 literal: ast::RawString, 394 literal: ast::RawString,
364 expanded: SyntaxToken, 395 expanded: SyntaxToken,
@@ -373,7 +404,7 @@ fn highlight_injection(
373 let (analysis, tmp_file_id) = Analysis::from_single_file(value); 404 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
374 405
375 if let Some(range) = literal.open_quote_text_range() { 406 if let Some(range) = literal.open_quote_text_range() {
376 acc.push(HighlightedRange { 407 acc.add(HighlightedRange {
377 range, 408 range,
378 highlight: HighlightTag::StringLiteral.into(), 409 highlight: HighlightTag::StringLiteral.into(),
379 binding_hash: None, 410 binding_hash: None,
@@ -383,12 +414,12 @@ fn highlight_injection(
383 for mut h in analysis.highlight(tmp_file_id).unwrap() { 414 for mut h in analysis.highlight(tmp_file_id).unwrap() {
384 if let Some(r) = literal.map_range_up(h.range) { 415 if let Some(r) = literal.map_range_up(h.range) {
385 h.range = r; 416 h.range = r;
386 acc.push(h) 417 acc.add(h)
387 } 418 }
388 } 419 }
389 420
390 if let Some(range) = literal.close_quote_text_range() { 421 if let Some(range) = literal.close_quote_text_range() {
391 acc.push(HighlightedRange { 422 acc.add(HighlightedRange {
392 range, 423 range,
393 highlight: HighlightTag::StringLiteral.into(), 424 highlight: HighlightTag::StringLiteral.into(),
394 binding_hash: None, 425 binding_hash: None,