diff options
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 392 |
1 files changed, 168 insertions, 224 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index d873f153e..5f11b091c 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,54 +1,30 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder}; | 3 | mod tags; |
4 | use ra_db::SourceDatabase; | 4 | mod html; |
5 | use ra_ide_db::{defs::NameDefinition, RootDatabase}; | 5 | |
6 | use hir::{Name, Semantics}; | ||
7 | use ra_ide_db::{ | ||
8 | defs::{classify_name, NameDefinition}, | ||
9 | RootDatabase, | ||
10 | }; | ||
6 | use ra_prof::profile; | 11 | use ra_prof::profile; |
7 | use ra_syntax::{ | 12 | use ra_syntax::{ |
8 | ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, TextRange, | 13 | ast, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, |
9 | WalkEvent, T, | 14 | TextRange, WalkEvent, T, |
10 | }; | 15 | }; |
11 | use rustc_hash::FxHashMap; | 16 | use rustc_hash::FxHashMap; |
12 | 17 | ||
13 | use crate::{ | 18 | use crate::{references::classify_name_ref, FileId}; |
14 | expand::descend_into_macros_with_analyzer, | ||
15 | references::{classify_name, classify_name_ref}, | ||
16 | FileId, | ||
17 | }; | ||
18 | 19 | ||
19 | pub mod tags { | 20 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
20 | pub(crate) const FIELD: &str = "field"; | 21 | |
21 | pub(crate) const FUNCTION: &str = "function"; | 22 | pub(crate) use html::highlight_as_html; |
22 | pub(crate) const MODULE: &str = "module"; | ||
23 | pub(crate) const CONSTANT: &str = "constant"; | ||
24 | pub(crate) const MACRO: &str = "macro"; | ||
25 | |||
26 | pub(crate) const VARIABLE: &str = "variable"; | ||
27 | pub(crate) const VARIABLE_MUT: &str = "variable.mut"; | ||
28 | |||
29 | pub(crate) const TYPE: &str = "type"; | ||
30 | pub(crate) const TYPE_BUILTIN: &str = "type.builtin"; | ||
31 | pub(crate) const TYPE_SELF: &str = "type.self"; | ||
32 | pub(crate) const TYPE_PARAM: &str = "type.param"; | ||
33 | pub(crate) const TYPE_LIFETIME: &str = "type.lifetime"; | ||
34 | |||
35 | pub(crate) const LITERAL_BYTE: &str = "literal.byte"; | ||
36 | pub(crate) const LITERAL_NUMERIC: &str = "literal.numeric"; | ||
37 | pub(crate) const LITERAL_CHAR: &str = "literal.char"; | ||
38 | |||
39 | pub(crate) const LITERAL_COMMENT: &str = "comment"; | ||
40 | pub(crate) const LITERAL_STRING: &str = "string"; | ||
41 | pub(crate) const LITERAL_ATTRIBUTE: &str = "attribute"; | ||
42 | |||
43 | pub(crate) const KEYWORD: &str = "keyword"; | ||
44 | pub(crate) const KEYWORD_UNSAFE: &str = "keyword.unsafe"; | ||
45 | pub(crate) const KEYWORD_CONTROL: &str = "keyword.control"; | ||
46 | } | ||
47 | 23 | ||
48 | #[derive(Debug)] | 24 | #[derive(Debug)] |
49 | pub struct HighlightedRange { | 25 | pub struct HighlightedRange { |
50 | pub range: TextRange, | 26 | pub range: TextRange, |
51 | pub tag: &'static str, | 27 | pub highlight: Highlight, |
52 | pub binding_hash: Option<u64>, | 28 | pub binding_hash: Option<u64>, |
53 | } | 29 | } |
54 | 30 | ||
@@ -67,54 +43,82 @@ fn is_control_keyword(kind: SyntaxKind) -> bool { | |||
67 | } | 43 | } |
68 | } | 44 | } |
69 | 45 | ||
70 | pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> { | 46 | pub(crate) fn highlight( |
47 | db: &RootDatabase, | ||
48 | file_id: FileId, | ||
49 | range: Option<TextRange>, | ||
50 | ) -> Vec<HighlightedRange> { | ||
71 | let _p = profile("highlight"); | 51 | let _p = profile("highlight"); |
72 | let parse = db.parse(file_id); | 52 | let sema = Semantics::new(db); |
73 | let root = parse.tree().syntax().clone(); | 53 | let root = sema.parse(file_id).syntax().clone(); |
74 | 54 | ||
75 | let mut sb = SourceBinder::new(db); | ||
76 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 55 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
77 | let mut res = Vec::new(); | 56 | let mut res = Vec::new(); |
78 | let analyzer = sb.analyze(InFile::new(file_id.into(), &root), None); | ||
79 | 57 | ||
80 | let mut in_macro_call = None; | 58 | let mut in_macro_call = None; |
81 | 59 | ||
60 | // Determine the root based on the given range. | ||
61 | let (root, highlight_range) = if let Some(range) = range { | ||
62 | let root = match root.covering_element(range) { | ||
63 | NodeOrToken::Node(node) => node, | ||
64 | NodeOrToken::Token(token) => token.parent(), | ||
65 | }; | ||
66 | (root, range) | ||
67 | } else { | ||
68 | (root.clone(), root.text_range()) | ||
69 | }; | ||
70 | |||
82 | for event in root.preorder_with_tokens() { | 71 | for event in root.preorder_with_tokens() { |
83 | match event { | 72 | match event { |
84 | WalkEvent::Enter(node) => match node.kind() { | 73 | WalkEvent::Enter(node) => { |
85 | MACRO_CALL => { | 74 | if node.text_range().intersection(&highlight_range).is_none() { |
86 | in_macro_call = Some(node.clone()); | 75 | continue; |
87 | if let Some(range) = highlight_macro(InFile::new(file_id.into(), node)) { | ||
88 | res.push(HighlightedRange { range, tag: tags::MACRO, binding_hash: None }); | ||
89 | } | ||
90 | } | 76 | } |
91 | _ if in_macro_call.is_some() => { | 77 | |
92 | if let Some(token) = node.as_token() { | 78 | match node.kind() { |
93 | if let Some((tag, binding_hash)) = highlight_token_tree( | 79 | MACRO_CALL => { |
94 | &mut sb, | 80 | in_macro_call = Some(node.clone()); |
95 | &analyzer, | 81 | if let Some(range) = highlight_macro(node) { |
96 | &mut bindings_shadow_count, | 82 | res.push(HighlightedRange { |
97 | InFile::new(file_id.into(), token.clone()), | 83 | range, |
98 | ) { | 84 | highlight: HighlightTag::Macro.into(), |
85 | binding_hash: None, | ||
86 | }); | ||
87 | } | ||
88 | } | ||
89 | _ if in_macro_call.is_some() => { | ||
90 | if let Some(token) = node.as_token() { | ||
91 | if let Some((highlight, binding_hash)) = highlight_token_tree( | ||
92 | &sema, | ||
93 | &mut bindings_shadow_count, | ||
94 | token.clone(), | ||
95 | ) { | ||
96 | res.push(HighlightedRange { | ||
97 | range: node.text_range(), | ||
98 | highlight, | ||
99 | binding_hash, | ||
100 | }); | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | _ => { | ||
105 | if let Some((highlight, binding_hash)) = | ||
106 | highlight_node(&sema, &mut bindings_shadow_count, node.clone()) | ||
107 | { | ||
99 | res.push(HighlightedRange { | 108 | res.push(HighlightedRange { |
100 | range: node.text_range(), | 109 | range: node.text_range(), |
101 | tag, | 110 | highlight, |
102 | binding_hash, | 111 | binding_hash, |
103 | }); | 112 | }); |
104 | } | 113 | } |
105 | } | 114 | } |
106 | } | 115 | } |
107 | _ => { | 116 | } |
108 | if let Some((tag, binding_hash)) = highlight_node( | ||
109 | &mut sb, | ||
110 | &mut bindings_shadow_count, | ||
111 | InFile::new(file_id.into(), node.clone()), | ||
112 | ) { | ||
113 | res.push(HighlightedRange { range: node.text_range(), tag, binding_hash }); | ||
114 | } | ||
115 | } | ||
116 | }, | ||
117 | WalkEvent::Leave(node) => { | 117 | WalkEvent::Leave(node) => { |
118 | if node.text_range().intersection(&highlight_range).is_none() { | ||
119 | continue; | ||
120 | } | ||
121 | |||
118 | if let Some(m) = in_macro_call.as_ref() { | 122 | if let Some(m) = in_macro_call.as_ref() { |
119 | if *m == node { | 123 | if *m == node { |
120 | in_macro_call = None; | 124 | in_macro_call = None; |
@@ -127,8 +131,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
127 | res | 131 | res |
128 | } | 132 | } |
129 | 133 | ||
130 | fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> { | 134 | fn highlight_macro(node: SyntaxElement) -> Option<TextRange> { |
131 | let macro_call = ast::MacroCall::cast(node.value.as_node()?.clone())?; | 135 | let macro_call = ast::MacroCall::cast(node.as_node()?.clone())?; |
132 | let path = macro_call.path()?; | 136 | let path = macro_call.path()?; |
133 | let name_ref = path.segment()?.name_ref()?; | 137 | let name_ref = path.segment()?.name_ref()?; |
134 | 138 | ||
@@ -145,56 +149,56 @@ fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> { | |||
145 | } | 149 | } |
146 | 150 | ||
147 | fn highlight_token_tree( | 151 | fn highlight_token_tree( |
148 | sb: &mut SourceBinder<RootDatabase>, | 152 | sema: &Semantics<RootDatabase>, |
149 | analyzer: &SourceAnalyzer, | ||
150 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 153 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
151 | token: InFile<SyntaxToken>, | 154 | token: SyntaxToken, |
152 | ) -> Option<(&'static str, Option<u64>)> { | 155 | ) -> Option<(Highlight, Option<u64>)> { |
153 | if token.value.parent().kind() != TOKEN_TREE { | 156 | if token.parent().kind() != TOKEN_TREE { |
154 | return None; | 157 | return None; |
155 | } | 158 | } |
156 | let token = descend_into_macros_with_analyzer(sb.db, analyzer, token); | 159 | let token = sema.descend_into_macros(token.clone()); |
157 | let expanded = { | 160 | let expanded = { |
158 | let parent = token.value.parent(); | 161 | let parent = token.parent(); |
159 | // We only care Name and Name_ref | 162 | // We only care Name and Name_ref |
160 | match (token.value.kind(), parent.kind()) { | 163 | match (token.kind(), parent.kind()) { |
161 | (IDENT, NAME) | (IDENT, NAME_REF) => token.with_value(parent.into()), | 164 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), |
162 | _ => token.map(|it| it.into()), | 165 | _ => token.into(), |
163 | } | 166 | } |
164 | }; | 167 | }; |
165 | 168 | ||
166 | highlight_node(sb, bindings_shadow_count, expanded) | 169 | highlight_node(sema, bindings_shadow_count, expanded) |
167 | } | 170 | } |
168 | 171 | ||
169 | fn highlight_node( | 172 | fn highlight_node( |
170 | sb: &mut SourceBinder<RootDatabase>, | 173 | sema: &Semantics<RootDatabase>, |
171 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 174 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
172 | node: InFile<SyntaxElement>, | 175 | node: SyntaxElement, |
173 | ) -> Option<(&'static str, Option<u64>)> { | 176 | ) -> Option<(Highlight, Option<u64>)> { |
174 | let db = sb.db; | 177 | let db = sema.db; |
175 | let mut binding_hash = None; | 178 | let mut binding_hash = None; |
176 | let tag = match node.value.kind() { | 179 | let highlight: Highlight = match node.kind() { |
177 | FN_DEF => { | 180 | FN_DEF => { |
178 | bindings_shadow_count.clear(); | 181 | bindings_shadow_count.clear(); |
179 | return None; | 182 | return None; |
180 | } | 183 | } |
181 | COMMENT => tags::LITERAL_COMMENT, | 184 | COMMENT => HighlightTag::Comment.into(), |
182 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING, | 185 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LiteralString.into(), |
183 | ATTR => tags::LITERAL_ATTRIBUTE, | 186 | ATTR => HighlightTag::Attribute.into(), |
184 | // Special-case field init shorthand | 187 | // Special-case field init shorthand |
185 | NAME_REF if node.value.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD, | 188 | NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => { |
186 | NAME_REF if node.value.ancestors().any(|it| it.kind() == ATTR) => return None, | 189 | HighlightTag::Field.into() |
190 | } | ||
191 | NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => return None, | ||
187 | NAME_REF => { | 192 | NAME_REF => { |
188 | let name_ref = node.value.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); | 193 | let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); |
189 | let name_kind = classify_name_ref(sb, node.with_value(&name_ref)); | 194 | let name_kind = classify_name_ref(sema, &name_ref); |
190 | match name_kind { | 195 | match name_kind { |
191 | Some(name_kind) => { | 196 | Some(name_kind) => { |
192 | if let NameDefinition::Local(local) = &name_kind { | 197 | if let NameDefinition::Local(local) = &name_kind { |
193 | if let Some(name) = local.name(db) { | 198 | if let Some(name) = local.name(db) { |
194 | let shadow_count = | 199 | let shadow_count = |
195 | bindings_shadow_count.entry(name.clone()).or_default(); | 200 | bindings_shadow_count.entry(name.clone()).or_default(); |
196 | binding_hash = | 201 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) |
197 | Some(calc_binding_hash(node.file_id, &name, *shadow_count)) | ||
198 | } | 202 | } |
199 | }; | 203 | }; |
200 | 204 | ||
@@ -204,41 +208,45 @@ fn highlight_node( | |||
204 | } | 208 | } |
205 | } | 209 | } |
206 | NAME => { | 210 | NAME => { |
207 | let name = node.value.as_node().cloned().and_then(ast::Name::cast).unwrap(); | 211 | let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap(); |
208 | let name_kind = classify_name(sb, node.with_value(&name)); | 212 | let name_kind = classify_name(sema, &name); |
209 | 213 | ||
210 | if let Some(NameDefinition::Local(local)) = &name_kind { | 214 | if let Some(NameDefinition::Local(local)) = &name_kind { |
211 | if let Some(name) = local.name(db) { | 215 | if let Some(name) = local.name(db) { |
212 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); | 216 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); |
213 | *shadow_count += 1; | 217 | *shadow_count += 1; |
214 | binding_hash = Some(calc_binding_hash(node.file_id, &name, *shadow_count)) | 218 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) |
215 | } | 219 | } |
216 | }; | 220 | }; |
217 | 221 | ||
218 | match name_kind { | 222 | match name_kind { |
219 | Some(name_kind) => highlight_name(db, name_kind), | 223 | Some(name_kind) => highlight_name(db, name_kind), |
220 | None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() { | 224 | None => name.syntax().parent().map_or(HighlightTag::Function.into(), |x| { |
221 | STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE, | 225 | match x.kind() { |
222 | TYPE_PARAM => tags::TYPE_PARAM, | 226 | STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => { |
223 | RECORD_FIELD_DEF => tags::FIELD, | 227 | HighlightTag::Type.into() |
224 | _ => tags::FUNCTION, | 228 | } |
229 | TYPE_PARAM => HighlightTag::TypeParam.into(), | ||
230 | RECORD_FIELD_DEF => HighlightTag::Field.into(), | ||
231 | _ => HighlightTag::Function.into(), | ||
232 | } | ||
225 | }), | 233 | }), |
226 | } | 234 | } |
227 | } | 235 | } |
228 | INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC, | 236 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::LiteralNumeric.into(), |
229 | BYTE => tags::LITERAL_BYTE, | 237 | BYTE => HighlightTag::LiteralByte.into(), |
230 | CHAR => tags::LITERAL_CHAR, | 238 | CHAR => HighlightTag::LiteralChar.into(), |
231 | LIFETIME => tags::TYPE_LIFETIME, | 239 | LIFETIME => HighlightTag::TypeLifetime.into(), |
232 | T![unsafe] => tags::KEYWORD_UNSAFE, | 240 | T![unsafe] => HighlightTag::Keyword | HighlightModifier::Unsafe, |
233 | k if is_control_keyword(k) => tags::KEYWORD_CONTROL, | 241 | k if is_control_keyword(k) => HighlightTag::Keyword | HighlightModifier::Control, |
234 | k if k.is_keyword() => tags::KEYWORD, | 242 | k if k.is_keyword() => HighlightTag::Keyword.into(), |
235 | 243 | ||
236 | _ => return None, | 244 | _ => return None, |
237 | }; | 245 | }; |
238 | 246 | ||
239 | return Some((tag, binding_hash)); | 247 | return Some((highlight, binding_hash)); |
240 | 248 | ||
241 | fn calc_binding_hash(file_id: HirFileId, name: &Name, shadow_count: u32) -> u64 { | 249 | fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 { |
242 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | 250 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { |
243 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | 251 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; |
244 | 252 | ||
@@ -247,134 +255,48 @@ fn highlight_node( | |||
247 | hasher.finish() | 255 | hasher.finish() |
248 | } | 256 | } |
249 | 257 | ||
250 | hash((file_id, name, shadow_count)) | 258 | hash((name, shadow_count)) |
251 | } | 259 | } |
252 | } | 260 | } |
253 | 261 | ||
254 | pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { | 262 | fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight { |
255 | let parse = db.parse(file_id); | ||
256 | |||
257 | fn rainbowify(seed: u64) -> String { | ||
258 | use rand::prelude::*; | ||
259 | let mut rng = SmallRng::seed_from_u64(seed); | ||
260 | format!( | ||
261 | "hsl({h},{s}%,{l}%)", | ||
262 | h = rng.gen_range::<u16, _, _>(0, 361), | ||
263 | s = rng.gen_range::<u16, _, _>(42, 99), | ||
264 | l = rng.gen_range::<u16, _, _>(40, 91), | ||
265 | ) | ||
266 | } | ||
267 | |||
268 | let mut ranges = highlight(db, file_id); | ||
269 | ranges.sort_by_key(|it| it.range.start()); | ||
270 | // quick non-optimal heuristic to intersect token ranges and highlighted ranges | ||
271 | let mut frontier = 0; | ||
272 | let mut could_intersect: Vec<&HighlightedRange> = Vec::new(); | ||
273 | |||
274 | let mut buf = String::new(); | ||
275 | buf.push_str(&STYLE); | ||
276 | buf.push_str("<pre><code>"); | ||
277 | let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token()); | ||
278 | for token in tokens { | ||
279 | could_intersect.retain(|it| token.text_range().start() <= it.range.end()); | ||
280 | while let Some(r) = ranges.get(frontier) { | ||
281 | if r.range.start() <= token.text_range().end() { | ||
282 | could_intersect.push(r); | ||
283 | frontier += 1; | ||
284 | } else { | ||
285 | break; | ||
286 | } | ||
287 | } | ||
288 | let text = html_escape(&token.text()); | ||
289 | let ranges = could_intersect | ||
290 | .iter() | ||
291 | .filter(|it| token.text_range().is_subrange(&it.range)) | ||
292 | .collect::<Vec<_>>(); | ||
293 | if ranges.is_empty() { | ||
294 | buf.push_str(&text); | ||
295 | } else { | ||
296 | let classes = ranges.iter().map(|x| x.tag).collect::<Vec<_>>().join(" "); | ||
297 | let binding_hash = ranges.first().and_then(|x| x.binding_hash); | ||
298 | let color = match (rainbow, binding_hash) { | ||
299 | (true, Some(hash)) => format!( | ||
300 | " data-binding-hash=\"{}\" style=\"color: {};\"", | ||
301 | hash, | ||
302 | rainbowify(hash) | ||
303 | ), | ||
304 | _ => "".into(), | ||
305 | }; | ||
306 | buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text)); | ||
307 | } | ||
308 | } | ||
309 | buf.push_str("</code></pre>"); | ||
310 | buf | ||
311 | } | ||
312 | |||
313 | fn highlight_name(db: &RootDatabase, def: NameDefinition) -> &'static str { | ||
314 | match def { | 263 | match def { |
315 | NameDefinition::Macro(_) => tags::MACRO, | 264 | NameDefinition::Macro(_) => HighlightTag::Macro, |
316 | NameDefinition::StructField(_) => tags::FIELD, | 265 | NameDefinition::StructField(_) => HighlightTag::Field, |
317 | NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => tags::MODULE, | 266 | NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::Module, |
318 | NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => tags::FUNCTION, | 267 | NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::Function, |
319 | NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => tags::TYPE, | 268 | NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::Type, |
320 | NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => tags::CONSTANT, | 269 | NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::Constant, |
321 | NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => tags::CONSTANT, | 270 | NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::Constant, |
322 | NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => tags::CONSTANT, | 271 | NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::Constant, |
323 | NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => tags::TYPE, | 272 | NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::Type, |
324 | NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => tags::TYPE, | 273 | NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::Type, |
325 | NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => tags::TYPE_BUILTIN, | 274 | NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { |
326 | NameDefinition::SelfType(_) => tags::TYPE_SELF, | 275 | return HighlightTag::Type | HighlightModifier::Builtin |
327 | NameDefinition::TypeParam(_) => tags::TYPE_PARAM, | 276 | } |
277 | NameDefinition::SelfType(_) => HighlightTag::TypeSelf, | ||
278 | NameDefinition::TypeParam(_) => HighlightTag::TypeParam, | ||
328 | NameDefinition::Local(local) => { | 279 | NameDefinition::Local(local) => { |
280 | let mut h = Highlight::new(HighlightTag::Variable); | ||
329 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | 281 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { |
330 | tags::VARIABLE_MUT | 282 | h |= HighlightModifier::Mutable; |
331 | } else { | ||
332 | tags::VARIABLE | ||
333 | } | 283 | } |
284 | return h; | ||
334 | } | 285 | } |
335 | } | 286 | } |
287 | .into() | ||
336 | } | 288 | } |
337 | 289 | ||
338 | //FIXME: like, real html escaping | ||
339 | fn html_escape(text: &str) -> String { | ||
340 | text.replace("<", "<").replace(">", ">") | ||
341 | } | ||
342 | |||
343 | const STYLE: &str = " | ||
344 | <style> | ||
345 | body { margin: 0; } | ||
346 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
347 | |||
348 | .comment { color: #7F9F7F; } | ||
349 | .string { color: #CC9393; } | ||
350 | .field { color: #94BFF3; } | ||
351 | .function { color: #93E0E3; } | ||
352 | .parameter { color: #94BFF3; } | ||
353 | .text { color: #DCDCCC; } | ||
354 | .type { color: #7CB8BB; } | ||
355 | .type\\.builtin { color: #8CD0D3; } | ||
356 | .type\\.param { color: #20999D; } | ||
357 | .attribute { color: #94BFF3; } | ||
358 | .literal { color: #BFEBBF; } | ||
359 | .literal\\.numeric { color: #6A8759; } | ||
360 | .macro { color: #94BFF3; } | ||
361 | .module { color: #AFD8AF; } | ||
362 | .variable { color: #DCDCCC; } | ||
363 | .variable\\.mut { color: #DCDCCC; text-decoration: underline; } | ||
364 | |||
365 | .keyword { color: #F0DFAF; } | ||
366 | .keyword\\.unsafe { color: #DFAF8F; } | ||
367 | .keyword\\.control { color: #F0DFAF; font-weight: bold; } | ||
368 | </style> | ||
369 | "; | ||
370 | |||
371 | #[cfg(test)] | 290 | #[cfg(test)] |
372 | mod tests { | 291 | mod tests { |
373 | use std::fs; | 292 | use std::fs; |
374 | 293 | ||
375 | use test_utils::{assert_eq_text, project_dir, read_text}; | 294 | use test_utils::{assert_eq_text, project_dir, read_text}; |
376 | 295 | ||
377 | use crate::mock_analysis::{single_file, MockAnalysis}; | 296 | use crate::{ |
297 | mock_analysis::{single_file, MockAnalysis}, | ||
298 | FileRange, TextRange, | ||
299 | }; | ||
378 | 300 | ||
379 | #[test] | 301 | #[test] |
380 | fn test_highlighting() { | 302 | fn test_highlighting() { |
@@ -475,4 +397,26 @@ fn bar() { | |||
475 | let _ = host.analysis().highlight(file_id).unwrap(); | 397 | let _ = host.analysis().highlight(file_id).unwrap(); |
476 | // eprintln!("elapsed: {:?}", t.elapsed()); | 398 | // eprintln!("elapsed: {:?}", t.elapsed()); |
477 | } | 399 | } |
400 | |||
401 | #[test] | ||
402 | fn test_ranges() { | ||
403 | let (analysis, file_id) = single_file( | ||
404 | r#" | ||
405 | #[derive(Clone, Debug)] | ||
406 | struct Foo { | ||
407 | pub x: i32, | ||
408 | pub y: i32, | ||
409 | }"#, | ||
410 | ); | ||
411 | |||
412 | // The "x" | ||
413 | let highlights = &analysis | ||
414 | .highlight_range(FileRange { | ||
415 | file_id, | ||
416 | range: TextRange::offset_len(82.into(), 1.into()), | ||
417 | }) | ||
418 | .unwrap(); | ||
419 | |||
420 | assert_eq!(&highlights[0].highlight.to_string(), "field"); | ||
421 | } | ||
478 | } | 422 | } |