diff options
Diffstat (limited to 'crates/ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 135 |
1 files changed, 18 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 | } | ||