aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs222
1 files changed, 108 insertions, 114 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index e8ca7d652..d31c09abb 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Implements syntax highlighting.
2 2
3mod tags; 3mod tags;
4mod html; 4mod html;
@@ -10,16 +10,14 @@ use ra_ide_db::{
10}; 10};
11use ra_prof::profile; 11use ra_prof::profile;
12use ra_syntax::{ 12use ra_syntax::{
13 ast, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, 13 ast, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, TextRange, WalkEvent, T,
14 TextRange, WalkEvent, T,
15}; 14};
16use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
17 16
18use crate::{references::classify_name_ref, FileId}; 17use crate::{references::classify_name_ref, FileId};
19 18
20pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
21
22pub(crate) use html::highlight_as_html; 19pub(crate) use html::highlight_as_html;
20pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
23 21
24#[derive(Debug)] 22#[derive(Debug)]
25pub struct HighlightedRange { 23pub struct HighlightedRange {
@@ -28,21 +26,6 @@ pub struct HighlightedRange {
28 pub binding_hash: Option<u64>, 26 pub binding_hash: Option<u64>,
29} 27}
30 28
31fn is_control_keyword(kind: SyntaxKind) -> bool {
32 match kind {
33 T![for]
34 | T![loop]
35 | T![while]
36 | T![continue]
37 | T![break]
38 | T![if]
39 | T![else]
40 | T![match]
41 | T![return] => true,
42 _ => false,
43 }
44}
45
46pub(crate) fn highlight( 29pub(crate) fn highlight(
47 db: &RootDatabase, 30 db: &RootDatabase,
48 file_id: FileId, 31 file_id: FileId,
@@ -71,20 +54,24 @@ pub(crate) fn highlight(
71 54
72 let mut current_macro_call: Option<ast::MacroCall> = None; 55 let mut current_macro_call: Option<ast::MacroCall> = None;
73 56
57 // Walk all nodes, keeping track of whether we are inside a macro or not.
58 // If in macro, expand it first and highlight the expanded code.
74 for event in root.preorder_with_tokens() { 59 for event in root.preorder_with_tokens() {
75 let event_range = match &event { 60 let event_range = match &event {
76 WalkEvent::Enter(it) => it.text_range(), 61 WalkEvent::Enter(it) => it.text_range(),
77 WalkEvent::Leave(it) => it.text_range(), 62 WalkEvent::Leave(it) => it.text_range(),
78 }; 63 };
79 64
80 if event_range.intersection(&range_to_highlight).is_none() { 65 // Element outside of the viewport, no need to highlight
66 if range_to_highlight.intersection(&event_range).is_none() {
81 continue; 67 continue;
82 } 68 }
83 69
70 // Track "inside macro" state
84 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { 71 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
85 WalkEvent::Enter(Some(mc)) => { 72 WalkEvent::Enter(Some(mc)) => {
86 current_macro_call = Some(mc.clone()); 73 current_macro_call = Some(mc.clone());
87 if let Some(range) = highlight_macro(&mc) { 74 if let Some(range) = macro_call_range(&mc) {
88 res.push(HighlightedRange { 75 res.push(HighlightedRange {
89 range, 76 range,
90 highlight: HighlightTag::Macro.into(), 77 highlight: HighlightTag::Macro.into(),
@@ -101,37 +88,40 @@ pub(crate) fn highlight(
101 _ => (), 88 _ => (),
102 } 89 }
103 90
104 let node = match event { 91 let element = match event {
105 WalkEvent::Enter(it) => it, 92 WalkEvent::Enter(it) => it,
106 WalkEvent::Leave(_) => continue, 93 WalkEvent::Leave(_) => continue,
107 }; 94 };
95 let range = element.text_range();
108 96
109 if current_macro_call.is_some() { 97 let element_to_highlight = if current_macro_call.is_some() {
110 if let Some(token) = node.into_token() { 98 // Inside a macro -- expand it first
111 if let Some((highlight, binding_hash)) = 99 let token = match element.into_token() {
112 highlight_token_tree(&sema, &mut bindings_shadow_count, token.clone()) 100 Some(it) if it.parent().kind() == TOKEN_TREE => it,
113 { 101 _ => continue,
114 res.push(HighlightedRange { 102 };
115 range: token.text_range(), 103 let token = sema.descend_into_macros(token.clone());
116 highlight, 104 let parent = token.parent();
117 binding_hash, 105 // We only care Name and Name_ref
118 }); 106 match (token.kind(), parent.kind()) {
119 } 107 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
108 _ => token.into(),
120 } 109 }
121 continue; 110 } else {
122 } 111 element
112 };
123 113
124 if let Some((highlight, binding_hash)) = 114 if let Some((highlight, binding_hash)) =
125 highlight_node(&sema, &mut bindings_shadow_count, node.clone()) 115 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
126 { 116 {
127 res.push(HighlightedRange { range: node.text_range(), highlight, binding_hash }); 117 res.push(HighlightedRange { range, highlight, binding_hash });
128 } 118 }
129 } 119 }
130 120
131 res 121 res
132} 122}
133 123
134fn highlight_macro(macro_call: &ast::MacroCall) -> Option<TextRange> { 124fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
135 let path = macro_call.path()?; 125 let path = macro_call.path()?;
136 let name_ref = path.segment()?.name_ref()?; 126 let name_ref = path.segment()?.name_ref()?;
137 127
@@ -147,67 +137,22 @@ fn highlight_macro(macro_call: &ast::MacroCall) -> Option<TextRange> {
147 Some(TextRange::from_to(range_start, range_end)) 137 Some(TextRange::from_to(range_start, range_end))
148} 138}
149 139
150fn highlight_token_tree( 140fn highlight_element(
151 sema: &Semantics<RootDatabase>,
152 bindings_shadow_count: &mut FxHashMap<Name, u32>,
153 token: SyntaxToken,
154) -> Option<(Highlight, Option<u64>)> {
155 if token.parent().kind() != TOKEN_TREE {
156 return None;
157 }
158 let token = sema.descend_into_macros(token.clone());
159 let expanded = {
160 let parent = token.parent();
161 // We only care Name and Name_ref
162 match (token.kind(), parent.kind()) {
163 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
164 _ => token.into(),
165 }
166 };
167
168 highlight_node(sema, bindings_shadow_count, expanded)
169}
170
171fn highlight_node(
172 sema: &Semantics<RootDatabase>, 141 sema: &Semantics<RootDatabase>,
173 bindings_shadow_count: &mut FxHashMap<Name, u32>, 142 bindings_shadow_count: &mut FxHashMap<Name, u32>,
174 node: SyntaxElement, 143 element: SyntaxElement,
175) -> Option<(Highlight, Option<u64>)> { 144) -> Option<(Highlight, Option<u64>)> {
176 let db = sema.db; 145 let db = sema.db;
177 let mut binding_hash = None; 146 let mut binding_hash = None;
178 let highlight: Highlight = match node.kind() { 147 let highlight: Highlight = match element.kind() {
179 FN_DEF => { 148 FN_DEF => {
180 bindings_shadow_count.clear(); 149 bindings_shadow_count.clear();
181 return None; 150 return None;
182 } 151 }
183 COMMENT => HighlightTag::Comment.into(), 152
184 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LiteralString.into(), 153 // Highlight definitions depending on the "type" of the definition.
185 ATTR => HighlightTag::Attribute.into(),
186 // Special-case field init shorthand
187 NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => {
188 HighlightTag::Field.into()
189 }
190 NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => return None,
191 NAME_REF => {
192 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
193 let name_kind = classify_name_ref(sema, &name_ref);
194 match name_kind {
195 Some(name_kind) => {
196 if let NameDefinition::Local(local) = &name_kind {
197 if let Some(name) = local.name(db) {
198 let shadow_count =
199 bindings_shadow_count.entry(name.clone()).or_default();
200 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
201 }
202 };
203
204 highlight_name(db, name_kind)
205 }
206 _ => return None,
207 }
208 }
209 NAME => { 154 NAME => {
210 let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap(); 155 let name = element.into_node().and_then(ast::Name::cast).unwrap();
211 let name_kind = classify_name(sema, &name); 156 let name_kind = classify_name(sema, &name);
212 157
213 if let Some(NameDefinition::Local(local)) = &name_kind { 158 if let Some(NameDefinition::Local(local)) = &name_kind {
@@ -220,25 +165,56 @@ fn highlight_node(
220 165
221 match name_kind { 166 match name_kind {
222 Some(name_kind) => highlight_name(db, name_kind), 167 Some(name_kind) => highlight_name(db, name_kind),
223 None => name.syntax().parent().map_or(HighlightTag::Function.into(), |x| { 168 None => highlight_name_by_syntax(name),
224 match x.kind() {
225 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => {
226 HighlightTag::Type.into()
227 }
228 TYPE_PARAM => HighlightTag::TypeParam.into(),
229 RECORD_FIELD_DEF => HighlightTag::Field.into(),
230 _ => HighlightTag::Function.into(),
231 }
232 }),
233 } 169 }
234 } 170 }
171
172 // Highlight references like the definitions they resolve to
173
174 // Special-case field init shorthand
175 NAME_REF if element.parent().and_then(ast::RecordField::cast).is_some() => {
176 HighlightTag::Field.into()
177 }
178 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None,
179 NAME_REF => {
180 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
181 let name_kind = classify_name_ref(sema, &name_ref)?;
182
183 if let NameDefinition::Local(local) = &name_kind {
184 if let Some(name) = local.name(db) {
185 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
186 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
187 }
188 };
189
190 highlight_name(db, name_kind)
191 }
192
193 // Simple token-based highlighting
194 COMMENT => HighlightTag::Comment.into(),
195 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LiteralString.into(),
196 ATTR => HighlightTag::Attribute.into(),
235 INT_NUMBER | FLOAT_NUMBER => HighlightTag::LiteralNumeric.into(), 197 INT_NUMBER | FLOAT_NUMBER => HighlightTag::LiteralNumeric.into(),
236 BYTE => HighlightTag::LiteralByte.into(), 198 BYTE => HighlightTag::LiteralByte.into(),
237 CHAR => HighlightTag::LiteralChar.into(), 199 CHAR => HighlightTag::LiteralChar.into(),
238 LIFETIME => HighlightTag::TypeLifetime.into(), 200 LIFETIME => HighlightTag::TypeLifetime.into(),
239 T![unsafe] => HighlightTag::Keyword | HighlightModifier::Unsafe, 201
240 k if is_control_keyword(k) => HighlightTag::Keyword | HighlightModifier::Control, 202 k if k.is_keyword() => {
241 k if k.is_keyword() => HighlightTag::Keyword.into(), 203 let h = Highlight::new(HighlightTag::Keyword);
204 match k {
205 T![break]
206 | T![continue]
207 | T![else]
208 | T![for]
209 | T![if]
210 | T![loop]
211 | T![match]
212 | T![return]
213 | T![while] => h | HighlightModifier::Control,
214 T![unsafe] => h | HighlightModifier::Unsafe,
215 _ => h,
216 }
217 }
242 218
243 _ => return None, 219 _ => return None,
244 }; 220 };
@@ -262,17 +238,19 @@ fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
262 match def { 238 match def {
263 NameDefinition::Macro(_) => HighlightTag::Macro, 239 NameDefinition::Macro(_) => HighlightTag::Macro,
264 NameDefinition::StructField(_) => HighlightTag::Field, 240 NameDefinition::StructField(_) => HighlightTag::Field,
265 NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::Module, 241 NameDefinition::ModuleDef(def) => match def {
266 NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::Function, 242 hir::ModuleDef::Module(_) => HighlightTag::Module,
267 NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::Type, 243 hir::ModuleDef::Function(_) => HighlightTag::Function,
268 NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::Constant, 244 hir::ModuleDef::Adt(_) => HighlightTag::Type,
269 NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::Constant, 245 hir::ModuleDef::EnumVariant(_) => HighlightTag::Constant,
270 NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::Constant, 246 hir::ModuleDef::Const(_) => HighlightTag::Constant,
271 NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::Type, 247 hir::ModuleDef::Static(_) => HighlightTag::Constant,
272 NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::Type, 248 hir::ModuleDef::Trait(_) => HighlightTag::Type,
273 NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { 249 hir::ModuleDef::TypeAlias(_) => HighlightTag::Type,
274 return HighlightTag::Type | HighlightModifier::Builtin 250 hir::ModuleDef::BuiltinType(_) => {
275 } 251 return HighlightTag::Type | HighlightModifier::Builtin
252 }
253 },
276 NameDefinition::SelfType(_) => HighlightTag::TypeSelf, 254 NameDefinition::SelfType(_) => HighlightTag::TypeSelf,
277 NameDefinition::TypeParam(_) => HighlightTag::TypeParam, 255 NameDefinition::TypeParam(_) => HighlightTag::TypeParam,
278 NameDefinition::Local(local) => { 256 NameDefinition::Local(local) => {
@@ -286,6 +264,22 @@ fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
286 .into() 264 .into()
287} 265}
288 266
267fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
268 let default = HighlightTag::Function.into();
269
270 let parent = match name.syntax().parent() {
271 Some(it) => it,
272 _ => return default,
273 };
274
275 match parent.kind() {
276 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => HighlightTag::Type.into(),
277 TYPE_PARAM => HighlightTag::TypeParam.into(),
278 RECORD_FIELD_DEF => HighlightTag::Field.into(),
279 _ => default,
280 }
281}
282
289#[cfg(test)] 283#[cfg(test)]
290mod tests { 284mod tests {
291 use std::fs; 285 use std::fs;