diff options
author | Lukas Wirth <[email protected]> | 2020-10-14 18:23:59 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-10-14 18:23:59 +0100 |
commit | 8c6dc5f28a5550acffbbb063335833304dac266d (patch) | |
tree | 6b5966bf5bff6436a47741ce25ace87812794eed /crates/ide/src | |
parent | df87be88d8500c8955f882d71467e01a7d4db9ab (diff) |
Factor macro_rules! highlighting out
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 135 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 136 |
2 files changed, 154 insertions, 117 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index f430006d7..8ecaff204 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | mod format; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod macro_rules; | ||
4 | mod tags; | 5 | mod tags; |
5 | #[cfg(test)] | 6 | #[cfg(test)] |
6 | mod tests; | 7 | mod tests; |
@@ -18,7 +19,10 @@ use syntax::{ | |||
18 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
19 | }; | 20 | }; |
20 | 21 | ||
21 | use crate::{syntax_highlighting::format::FormatStringHighlighter, FileId}; | 22 | use crate::{ |
23 | syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter}, | ||
24 | FileId, | ||
25 | }; | ||
22 | 26 | ||
23 | pub(crate) use html::highlight_as_html; | 27 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 28 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
@@ -68,8 +72,9 @@ pub(crate) fn highlight( | |||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | 72 | // When we leave a node, the we use it to flatten the highlighted ranges. |
69 | let mut stack = HighlightedRangeStack::new(); | 73 | let mut stack = HighlightedRangeStack::new(); |
70 | 74 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 75 | let mut current_macro_call: Option<ast::MacroCall> = None; |
72 | let mut format_string_highlighter = FormatStringHighlighter::default(); | 76 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
77 | let mut macro_rules_highlighter = MacroRulesHighlighter::new(); | ||
73 | 78 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 79 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 80 | // If in macro, expand it first and highlight the expanded code. |
@@ -99,9 +104,8 @@ pub(crate) fn highlight( | |||
99 | binding_hash: None, | 104 | binding_hash: None, |
100 | }); | 105 | }); |
101 | } | 106 | } |
102 | let mut is_macro_rules = None; | ||
103 | if let Some(name) = mc.is_macro_rules() { | 107 | if let Some(name) = mc.is_macro_rules() { |
104 | is_macro_rules = Some(MacroMatcherParseState::new()); | 108 | macro_rules_highlighter.init(); |
105 | if let Some((highlight, binding_hash)) = highlight_element( | 109 | if let Some((highlight, binding_hash)) = highlight_element( |
106 | &sema, | 110 | &sema, |
107 | &mut bindings_shadow_count, | 111 | &mut bindings_shadow_count, |
@@ -115,13 +119,14 @@ pub(crate) fn highlight( | |||
115 | }); | 119 | }); |
116 | } | 120 | } |
117 | } | 121 | } |
118 | current_macro_call = Some((mc.clone(), is_macro_rules)); | 122 | current_macro_call = Some(mc.clone()); |
119 | continue; | 123 | continue; |
120 | } | 124 | } |
121 | WalkEvent::Leave(Some(mc)) => { | 125 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 126 | assert!(current_macro_call == Some(mc)); |
123 | current_macro_call = None; | 127 | current_macro_call = None; |
124 | format_string_highlighter.reset(); | 128 | format_string_highlighter.reset(); |
129 | macro_rules_highlighter.reset(); | ||
125 | } | 130 | } |
126 | _ => (), | 131 | _ => (), |
127 | } | 132 | } |
@@ -148,20 +153,6 @@ pub(crate) fn highlight( | |||
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(_) => continue, |
149 | }; | 154 | }; |
150 | 155 | ||
151 | // check if in matcher part of a macro_rules rule | ||
152 | if let Some((_, Some(ref mut state))) = current_macro_call { | ||
153 | if let Some(tok) = element.as_token() { | ||
154 | if matches!( | ||
155 | update_macro_rules_state(tok, state), | ||
156 | RuleState::Matcher | RuleState::Expander | ||
157 | ) { | ||
158 | if skip_metavariables(element.clone()) { | ||
159 | continue; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | let range = element.text_range(); | 156 | let range = element.text_range(); |
166 | 157 | ||
167 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | 158 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { |
@@ -174,6 +165,9 @@ pub(crate) fn highlight( | |||
174 | let parent = token.parent(); | 165 | let parent = token.parent(); |
175 | 166 | ||
176 | format_string_highlighter.check_for_format_string(&parent); | 167 | format_string_highlighter.check_for_format_string(&parent); |
168 | if let Some(tok) = element.as_token() { | ||
169 | macro_rules_highlighter.advance(tok); | ||
170 | } | ||
177 | 171 | ||
178 | // We only care Name and Name_ref | 172 | // We only care Name and Name_ref |
179 | match (token.kind(), parent.kind()) { | 173 | match (token.kind(), parent.kind()) { |
@@ -197,7 +191,10 @@ pub(crate) fn highlight( | |||
197 | syntactic_name_ref_highlighting, | 191 | syntactic_name_ref_highlighting, |
198 | element_to_highlight.clone(), | 192 | element_to_highlight.clone(), |
199 | ) { | 193 | ) { |
200 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 194 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { |
195 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
196 | } | ||
197 | |||
201 | if let Some(string) = | 198 | if let Some(string) = |
202 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 199 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
203 | { | 200 | { |
@@ -867,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas | |||
867 | _ => default.into(), | 864 | _ => default.into(), |
868 | } | 865 | } |
869 | } | 866 | } |
870 | |||
871 | struct MacroMatcherParseState { | ||
872 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
873 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
874 | paren_level: usize, | ||
875 | rule_state: RuleState, | ||
876 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
877 | in_invoc_body: bool, | ||
878 | } | ||
879 | |||
880 | impl MacroMatcherParseState { | ||
881 | fn new() -> Self { | ||
882 | MacroMatcherParseState { | ||
883 | paren_ty: None, | ||
884 | paren_level: 0, | ||
885 | in_invoc_body: false, | ||
886 | rule_state: RuleState::None, | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | |||
891 | #[derive(Copy, Clone, PartialEq)] | ||
892 | enum RuleState { | ||
893 | Matcher, | ||
894 | Expander, | ||
895 | Between, | ||
896 | None, | ||
897 | } | ||
898 | |||
899 | impl RuleState { | ||
900 | fn transition(&mut self) { | ||
901 | *self = match self { | ||
902 | RuleState::Matcher => RuleState::Between, | ||
903 | RuleState::Expander => RuleState::None, | ||
904 | RuleState::Between => RuleState::Expander, | ||
905 | RuleState::None => RuleState::Matcher, | ||
906 | }; | ||
907 | } | ||
908 | } | ||
909 | |||
910 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
911 | if !state.in_invoc_body { | ||
912 | if tok.kind() == T!['{'] { | ||
913 | state.in_invoc_body = true; | ||
914 | } | ||
915 | return state.rule_state; | ||
916 | } | ||
917 | |||
918 | match state.paren_ty { | ||
919 | Some((open, close)) => { | ||
920 | if tok.kind() == open { | ||
921 | state.paren_level += 1; | ||
922 | } else if tok.kind() == close { | ||
923 | state.paren_level -= 1; | ||
924 | if state.paren_level == 0 { | ||
925 | let res = state.rule_state; | ||
926 | state.rule_state.transition(); | ||
927 | state.paren_ty = None; | ||
928 | return res; | ||
929 | } | ||
930 | } | ||
931 | } | ||
932 | None => { | ||
933 | match tok.kind() { | ||
934 | T!['('] => { | ||
935 | state.paren_ty = Some((T!['('], T![')'])); | ||
936 | } | ||
937 | T!['{'] => { | ||
938 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
939 | } | ||
940 | T!['['] => { | ||
941 | state.paren_ty = Some((T!['['], T![']'])); | ||
942 | } | ||
943 | _ => (), | ||
944 | } | ||
945 | if state.paren_ty.is_some() { | ||
946 | state.paren_level = 1; | ||
947 | state.rule_state.transition(); | ||
948 | } | ||
949 | } | ||
950 | } | ||
951 | state.rule_state | ||
952 | } | ||
953 | |||
954 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
955 | let tok = match element.as_token() { | ||
956 | Some(tok) => tok, | ||
957 | None => return false, | ||
958 | }; | ||
959 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
960 | match tok.kind() { | ||
961 | IDENT if is_fragment() => true, | ||
962 | kind if kind.is_keyword() && is_fragment() => true, | ||
963 | _ => false, | ||
964 | } | ||
965 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs new file mode 100644 index 000000000..0676e0972 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_rules.rs | |||
@@ -0,0 +1,136 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | pub(super) struct MacroRulesHighlighter { | ||
7 | state: Option<MacroMatcherParseState>, | ||
8 | } | ||
9 | |||
10 | impl MacroRulesHighlighter { | ||
11 | pub(super) fn new() -> Self { | ||
12 | MacroRulesHighlighter { state: None } | ||
13 | } | ||
14 | |||
15 | pub(super) fn init(&mut self) { | ||
16 | self.state = Some(MacroMatcherParseState::new()); | ||
17 | } | ||
18 | |||
19 | pub(super) fn reset(&mut self) { | ||
20 | self.state = None; | ||
21 | } | ||
22 | |||
23 | pub(super) fn advance(&mut self, token: &SyntaxToken) { | ||
24 | if let Some(state) = self.state.as_mut() { | ||
25 | update_macro_rules_state(state, token); | ||
26 | } | ||
27 | } | ||
28 | |||
29 | pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> { | ||
30 | if let Some(state) = self.state.as_ref() { | ||
31 | if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) { | ||
32 | if let Some(range) = is_metavariable(element) { | ||
33 | return Some(HighlightedRange { | ||
34 | range, | ||
35 | highlight: HighlightTag::UnresolvedReference.into(), | ||
36 | binding_hash: None, | ||
37 | }); | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | None | ||
42 | } | ||
43 | } | ||
44 | |||
45 | struct MacroMatcherParseState { | ||
46 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
47 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
48 | paren_level: usize, | ||
49 | rule_state: RuleState, | ||
50 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
51 | in_invoc_body: bool, | ||
52 | } | ||
53 | |||
54 | impl MacroMatcherParseState { | ||
55 | fn new() -> Self { | ||
56 | MacroMatcherParseState { | ||
57 | paren_ty: None, | ||
58 | paren_level: 0, | ||
59 | in_invoc_body: false, | ||
60 | rule_state: RuleState::None, | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | |||
65 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
66 | enum RuleState { | ||
67 | Matcher, | ||
68 | Expander, | ||
69 | Between, | ||
70 | None, | ||
71 | } | ||
72 | |||
73 | impl RuleState { | ||
74 | fn transition(&mut self) { | ||
75 | *self = match self { | ||
76 | RuleState::Matcher => RuleState::Between, | ||
77 | RuleState::Expander => RuleState::None, | ||
78 | RuleState::Between => RuleState::Expander, | ||
79 | RuleState::None => RuleState::Matcher, | ||
80 | }; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { | ||
85 | if !state.in_invoc_body { | ||
86 | if tok.kind() == T!['{'] { | ||
87 | state.in_invoc_body = true; | ||
88 | } | ||
89 | return; | ||
90 | } | ||
91 | |||
92 | match state.paren_ty { | ||
93 | Some((open, close)) => { | ||
94 | if tok.kind() == open { | ||
95 | state.paren_level += 1; | ||
96 | } else if tok.kind() == close { | ||
97 | state.paren_level -= 1; | ||
98 | if state.paren_level == 0 { | ||
99 | state.rule_state.transition(); | ||
100 | state.paren_ty = None; | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | None => { | ||
105 | match tok.kind() { | ||
106 | T!['('] => { | ||
107 | state.paren_ty = Some((T!['('], T![')'])); | ||
108 | } | ||
109 | T!['{'] => { | ||
110 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
111 | } | ||
112 | T!['['] => { | ||
113 | state.paren_ty = Some((T!['['], T![']'])); | ||
114 | } | ||
115 | _ => (), | ||
116 | } | ||
117 | if state.paren_ty.is_some() { | ||
118 | state.paren_level = 1; | ||
119 | state.rule_state.transition(); | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | fn is_metavariable(element: SyntaxElement) -> Option<TextRange> { | ||
126 | let tok = element.as_token()?; | ||
127 | match tok.kind() { | ||
128 | kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { | ||
129 | if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) { | ||
130 | return Some(tok.text_range()); | ||
131 | } | ||
132 | } | ||
133 | _ => (), | ||
134 | }; | ||
135 | None | ||
136 | } | ||