aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/syntax_highlighting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs632
1 files changed, 252 insertions, 380 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 812229b4e..b94b6a022 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,134 +1,141 @@
1//! FIXME: write short doc here 1//! Implements syntax highlighting.
2 2
3use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder}; 3mod tags;
4use ra_db::SourceDatabase; 4mod html;
5use ra_ide_db::{defs::NameDefinition, RootDatabase}; 5#[cfg(test)]
6mod tests;
7
8use hir::{Name, Semantics};
9use ra_ide_db::{
10 defs::{classify_name, NameClass, NameDefinition},
11 RootDatabase,
12};
6use ra_prof::profile; 13use ra_prof::profile;
7use ra_syntax::{ 14use ra_syntax::{
8 ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, TextRange, 15 ast::{self, HasQuotes, HasStringValue},
9 WalkEvent, T, 16 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
17 SyntaxKind::*,
18 SyntaxToken, TextRange, WalkEvent, T,
10}; 19};
11use rustc_hash::FxHashMap; 20use rustc_hash::FxHashMap;
12 21
13use crate::{ 22use crate::{call_info::call_info_for_token, references::classify_name_ref, Analysis, FileId};
14 expand::descend_into_macros_with_analyzer,
15 references::{classify_name, classify_name_ref},
16 FileId,
17};
18 23
19pub mod tags { 24pub(crate) use html::highlight_as_html;
20 pub const FIELD: &str = "field"; 25pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
21 pub const FUNCTION: &str = "function";
22 pub const MODULE: &str = "module";
23 pub const CONSTANT: &str = "constant";
24 pub const MACRO: &str = "macro";
25
26 pub const VARIABLE: &str = "variable";
27 pub const VARIABLE_MUT: &str = "variable.mut";
28
29 pub const TYPE: &str = "type";
30 pub const TYPE_BUILTIN: &str = "type.builtin";
31 pub const TYPE_SELF: &str = "type.self";
32 pub const TYPE_PARAM: &str = "type.param";
33 pub const TYPE_LIFETIME: &str = "type.lifetime";
34
35 pub const LITERAL_BYTE: &str = "literal.byte";
36 pub const LITERAL_NUMERIC: &str = "literal.numeric";
37 pub const LITERAL_CHAR: &str = "literal.char";
38
39 pub const LITERAL_COMMENT: &str = "comment";
40 pub const LITERAL_STRING: &str = "string";
41 pub const LITERAL_ATTRIBUTE: &str = "attribute";
42
43 pub const KEYWORD: &str = "keyword";
44 pub const KEYWORD_UNSAFE: &str = "keyword.unsafe";
45 pub const KEYWORD_CONTROL: &str = "keyword.control";
46}
47 26
48#[derive(Debug)] 27#[derive(Debug)]
49pub struct HighlightedRange { 28pub struct HighlightedRange {
50 pub range: TextRange, 29 pub range: TextRange,
51 pub tag: &'static str, 30 pub highlight: Highlight,
52 pub binding_hash: Option<u64>, 31 pub binding_hash: Option<u64>,
53} 32}
54 33
55fn is_control_keyword(kind: SyntaxKind) -> bool { 34pub(crate) fn highlight(
56 match kind { 35 db: &RootDatabase,
57 T![for] 36 file_id: FileId,
58 | T![loop] 37 range_to_highlight: Option<TextRange>,
59 | T![while] 38) -> Vec<HighlightedRange> {
60 | T![continue]
61 | T![break]
62 | T![if]
63 | T![else]
64 | T![match]
65 | T![return] => true,
66 _ => false,
67 }
68}
69
70pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> {
71 let _p = profile("highlight"); 39 let _p = profile("highlight");
72 let parse = db.parse(file_id); 40 let sema = Semantics::new(db);
73 let root = parse.tree().syntax().clone(); 41
42 // Determine the root based on the given range.
43 let (root, range_to_highlight) = {
44 let source_file = sema.parse(file_id);
45 match range_to_highlight {
46 Some(range) => {
47 let node = match source_file.syntax().covering_element(range) {
48 NodeOrToken::Node(it) => it,
49 NodeOrToken::Token(it) => it.parent(),
50 };
51 (node, range)
52 }
53 None => (source_file.syntax().clone(), source_file.syntax().text_range()),
54 }
55 };
74 56
75 let mut sb = SourceBinder::new(db);
76 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 57 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
77 let mut res = Vec::new(); 58 let mut res = Vec::new();
78 let analyzer = sb.analyze(InFile::new(file_id.into(), &root), None);
79 59
80 let mut in_macro_call = None; 60 let mut current_macro_call: Option<ast::MacroCall> = None;
81 61
62 // Walk all nodes, keeping track of whether we are inside a macro or not.
63 // If in macro, expand it first and highlight the expanded code.
82 for event in root.preorder_with_tokens() { 64 for event in root.preorder_with_tokens() {
83 match event { 65 let event_range = match &event {
84 WalkEvent::Enter(node) => match node.kind() { 66 WalkEvent::Enter(it) => it.text_range(),
85 MACRO_CALL => { 67 WalkEvent::Leave(it) => it.text_range(),
86 in_macro_call = Some(node.clone()); 68 };
87 if let Some(range) = highlight_macro(InFile::new(file_id.into(), node)) { 69
88 res.push(HighlightedRange { range, tag: tags::MACRO, binding_hash: None }); 70 // Element outside of the viewport, no need to highlight
89 } 71 if range_to_highlight.intersection(&event_range).is_none() {
90 } 72 continue;
91 _ if in_macro_call.is_some() => { 73 }
92 if let Some(token) = node.as_token() { 74
93 if let Some((tag, binding_hash)) = highlight_token_tree( 75 // Track "inside macro" state
94 &mut sb, 76 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
95 &analyzer, 77 WalkEvent::Enter(Some(mc)) => {
96 &mut bindings_shadow_count, 78 current_macro_call = Some(mc.clone());
97 InFile::new(file_id.into(), token.clone()), 79 if let Some(range) = macro_call_range(&mc) {
98 ) { 80 res.push(HighlightedRange {
99 res.push(HighlightedRange { 81 range,
100 range: node.text_range(), 82 highlight: HighlightTag::Macro.into(),
101 tag, 83 binding_hash: None,
102 binding_hash, 84 });
103 });
104 }
105 }
106 }
107 _ => {
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) => {
118 if let Some(m) = in_macro_call.as_ref() {
119 if *m == node {
120 in_macro_call = None;
121 }
122 } 85 }
86 continue;
123 } 87 }
88 WalkEvent::Leave(Some(mc)) => {
89 assert!(current_macro_call == Some(mc));
90 current_macro_call = None;
91 continue;
92 }
93 _ => (),
94 }
95
96 let element = match event {
97 WalkEvent::Enter(it) => it,
98 WalkEvent::Leave(_) => continue,
99 };
100
101 let range = element.text_range();
102
103 let element_to_highlight = if current_macro_call.is_some() {
104 // Inside a macro -- expand it first
105 let token = match element.clone().into_token() {
106 Some(it) if it.parent().kind() == TOKEN_TREE => it,
107 _ => continue,
108 };
109 let token = sema.descend_into_macros(token.clone());
110 let parent = token.parent();
111 // We only care Name and Name_ref
112 match (token.kind(), parent.kind()) {
113 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
114 _ => token.into(),
115 }
116 } else {
117 element.clone()
118 };
119
120 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
121 let expanded = element_to_highlight.as_token().unwrap().clone();
122 if highlight_injection(&mut res, &sema, token, expanded).is_some() {
123 eprintln!("res = {:?}", res);
124 continue;
125 }
126 }
127
128 if let Some((highlight, binding_hash)) =
129 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
130 {
131 res.push(HighlightedRange { range, highlight, binding_hash });
124 } 132 }
125 } 133 }
126 134
127 res 135 res
128} 136}
129 137
130fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> { 138fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
131 let macro_call = ast::MacroCall::cast(node.value.as_node()?.clone())?;
132 let path = macro_call.path()?; 139 let path = macro_call.path()?;
133 let name_ref = path.segment()?.name_ref()?; 140 let name_ref = path.segment()?.name_ref()?;
134 141
@@ -144,101 +151,100 @@ fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> {
144 Some(TextRange::from_to(range_start, range_end)) 151 Some(TextRange::from_to(range_start, range_end))
145} 152}
146 153
147fn highlight_token_tree( 154fn highlight_element(
148 sb: &mut SourceBinder<RootDatabase>, 155 sema: &Semantics<RootDatabase>,
149 analyzer: &SourceAnalyzer,
150 bindings_shadow_count: &mut FxHashMap<Name, u32>,
151 token: InFile<SyntaxToken>,
152) -> Option<(&'static str, Option<u64>)> {
153 if token.value.parent().kind() != TOKEN_TREE {
154 return None;
155 }
156 let token = descend_into_macros_with_analyzer(sb.db, analyzer, token);
157 let expanded = {
158 let parent = token.value.parent();
159 // We only care Name and Name_ref
160 match (token.value.kind(), parent.kind()) {
161 (IDENT, NAME) | (IDENT, NAME_REF) => token.with_value(parent.into()),
162 _ => token.map(|it| it.into()),
163 }
164 };
165
166 highlight_node(sb, bindings_shadow_count, expanded)
167}
168
169fn highlight_node(
170 sb: &mut SourceBinder<RootDatabase>,
171 bindings_shadow_count: &mut FxHashMap<Name, u32>, 156 bindings_shadow_count: &mut FxHashMap<Name, u32>,
172 node: InFile<SyntaxElement>, 157 element: SyntaxElement,
173) -> Option<(&'static str, Option<u64>)> { 158) -> Option<(Highlight, Option<u64>)> {
174 let db = sb.db; 159 let db = sema.db;
175 let mut binding_hash = None; 160 let mut binding_hash = None;
176 let tag = match node.value.kind() { 161 let highlight: Highlight = match element.kind() {
177 FN_DEF => { 162 FN_DEF => {
178 bindings_shadow_count.clear(); 163 bindings_shadow_count.clear();
179 return None; 164 return None;
180 } 165 }
181 COMMENT => tags::LITERAL_COMMENT, 166
182 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING, 167 // Highlight definitions depending on the "type" of the definition.
183 ATTR => tags::LITERAL_ATTRIBUTE, 168 NAME => {
184 // Special-case field init shorthand 169 let name = element.into_node().and_then(ast::Name::cast).unwrap();
185 NAME_REF if node.value.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD, 170 let name_kind = classify_name(sema, &name);
186 NAME_REF if node.value.ancestors().any(|it| it.kind() == ATTR) => return None, 171
187 NAME_REF => { 172 if let Some(NameClass::NameDefinition(NameDefinition::Local(local))) = &name_kind {
188 let name_ref = node.value.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); 173 if let Some(name) = local.name(db) {
189 let name_kind = classify_name_ref(sb, node.with_value(&name_ref)); 174 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
175 *shadow_count += 1;
176 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
177 }
178 };
179
190 match name_kind { 180 match name_kind {
191 Some(name_kind) => { 181 Some(NameClass::NameDefinition(def)) => {
192 if let NameDefinition::Local(local) = &name_kind { 182 highlight_name(db, def) | HighlightModifier::Definition
193 if let Some(name) = local.name(db) {
194 let shadow_count =
195 bindings_shadow_count.entry(name.clone()).or_default();
196 binding_hash =
197 Some(calc_binding_hash(node.file_id, &name, *shadow_count))
198 }
199 };
200
201 highlight_name(db, name_kind)
202 } 183 }
203 _ => return None, 184 Some(NameClass::ConstReference(def)) => highlight_name(db, def),
185 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
204 } 186 }
205 } 187 }
206 NAME => {
207 let name = node.value.as_node().cloned().and_then(ast::Name::cast).unwrap();
208 let name_kind = classify_name(sb, node.with_value(&name));
209 188
210 if let Some(NameDefinition::Local(local)) = &name_kind { 189 // Highlight references like the definitions they resolve to
190
191 // Special-case field init shorthand
192 NAME_REF if element.parent().and_then(ast::RecordField::cast).is_some() => {
193 HighlightTag::Field.into()
194 }
195 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None,
196 NAME_REF => {
197 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
198 let name_kind = classify_name_ref(sema, &name_ref)?;
199
200 if let NameDefinition::Local(local) = &name_kind {
211 if let Some(name) = local.name(db) { 201 if let Some(name) = local.name(db) {
212 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); 202 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
213 *shadow_count += 1; 203 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
214 binding_hash = Some(calc_binding_hash(node.file_id, &name, *shadow_count))
215 } 204 }
216 }; 205 };
217 206
218 match name_kind { 207 highlight_name(db, name_kind)
219 Some(name_kind) => highlight_name(db, name_kind), 208 }
220 None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() { 209
221 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE, 210 // Simple token-based highlighting
222 TYPE_PARAM => tags::TYPE_PARAM, 211 COMMENT => HighlightTag::Comment.into(),
223 RECORD_FIELD_DEF => tags::FIELD, 212 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
224 _ => tags::FUNCTION, 213 ATTR => HighlightTag::Attribute.into(),
225 }), 214 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
215 BYTE => HighlightTag::ByteLiteral.into(),
216 CHAR => HighlightTag::CharLiteral.into(),
217 LIFETIME => {
218 let h = Highlight::new(HighlightTag::Lifetime);
219 dbg!(match element.parent().map(|it| it.kind()) {
220 Some(LIFETIME_PARAM) | Some(LABEL) => h | HighlightModifier::Definition,
221 _ => h,
222 })
223 }
224
225 k if k.is_keyword() => {
226 let h = Highlight::new(HighlightTag::Keyword);
227 match k {
228 T![break]
229 | T![continue]
230 | T![else]
231 | T![for]
232 | T![if]
233 | T![loop]
234 | T![match]
235 | T![return]
236 | T![while] => h | HighlightModifier::Control,
237 T![unsafe] => h | HighlightModifier::Unsafe,
238 _ => h,
226 } 239 }
227 } 240 }
228 INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC,
229 BYTE => tags::LITERAL_BYTE,
230 CHAR => tags::LITERAL_CHAR,
231 LIFETIME => tags::TYPE_LIFETIME,
232 T![unsafe] => tags::KEYWORD_UNSAFE,
233 k if is_control_keyword(k) => tags::KEYWORD_CONTROL,
234 k if k.is_keyword() => tags::KEYWORD,
235 241
236 _ => return None, 242 _ => return None,
237 }; 243 };
238 244
239 return Some((tag, binding_hash)); 245 return Some((highlight, binding_hash));
240 246
241 fn calc_binding_hash(file_id: HirFileId, name: &Name, shadow_count: u32) -> u64 { 247 fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
242 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { 248 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
243 use std::{collections::hash_map::DefaultHasher, hash::Hasher}; 249 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
244 250
@@ -247,232 +253,98 @@ fn highlight_node(
247 hasher.finish() 253 hasher.finish()
248 } 254 }
249 255
250 hash((file_id, name, shadow_count)) 256 hash((name, shadow_count))
251 }
252}
253
254pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
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 } 257 }
309 buf.push_str("</code></pre>");
310 buf
311} 258}
312 259
313fn highlight_name(db: &RootDatabase, def: NameDefinition) -> &'static str { 260fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
314 match def { 261 match def {
315 NameDefinition::Macro(_) => tags::MACRO, 262 NameDefinition::Macro(_) => HighlightTag::Macro,
316 NameDefinition::StructField(_) => tags::FIELD, 263 NameDefinition::StructField(_) => HighlightTag::Field,
317 NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => tags::MODULE, 264 NameDefinition::ModuleDef(def) => match def {
318 NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => tags::FUNCTION, 265 hir::ModuleDef::Module(_) => HighlightTag::Module,
319 NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => tags::TYPE, 266 hir::ModuleDef::Function(_) => HighlightTag::Function,
320 NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => tags::CONSTANT, 267 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
321 NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => tags::CONSTANT, 268 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
322 NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => tags::CONSTANT, 269 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
323 NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => tags::TYPE, 270 hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant,
324 NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => tags::TYPE, 271 hir::ModuleDef::Const(_) => HighlightTag::Constant,
325 NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => tags::TYPE_BUILTIN, 272 hir::ModuleDef::Static(_) => HighlightTag::Static,
326 NameDefinition::SelfType(_) => tags::TYPE_SELF, 273 hir::ModuleDef::Trait(_) => HighlightTag::Trait,
327 NameDefinition::TypeParam(_) => tags::TYPE_PARAM, 274 hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias,
275 hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType,
276 },
277 NameDefinition::SelfType(_) => HighlightTag::SelfType,
278 NameDefinition::TypeParam(_) => HighlightTag::TypeParam,
279 // FIXME: distinguish between locals and parameters
328 NameDefinition::Local(local) => { 280 NameDefinition::Local(local) => {
281 let mut h = Highlight::new(HighlightTag::Local);
329 if local.is_mut(db) || local.ty(db).is_mutable_reference() { 282 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
330 tags::VARIABLE_MUT 283 h |= HighlightModifier::Mutable;
331 } else {
332 tags::VARIABLE
333 } 284 }
285 return h;
334 } 286 }
335 } 287 }
288 .into()
336} 289}
337 290
338//FIXME: like, real html escaping 291fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
339fn html_escape(text: &str) -> String { 292 let default = HighlightTag::Function.into();
340 text.replace("<", "&lt;").replace(">", "&gt;")
341}
342
343const STYLE: &str = "
344<style>
345body { margin: 0; }
346pre { 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)]
372mod tests {
373 use std::fs;
374
375 use test_utils::{assert_eq_text, project_dir, read_text};
376
377 use crate::mock_analysis::{single_file, MockAnalysis};
378
379 #[test]
380 fn test_highlighting() {
381 let (analysis, file_id) = single_file(
382 r#"
383#[derive(Clone, Debug)]
384struct Foo {
385 pub x: i32,
386 pub y: i32,
387}
388
389fn foo<T>() -> T {
390 unimplemented!();
391 foo::<i32>();
392}
393 293
394macro_rules! def_fn { 294 let parent = match name.syntax().parent() {
395 ($($tt:tt)*) => {$($tt)*} 295 Some(it) => it,
396} 296 _ => return default,
297 };
397 298
398def_fn!{ 299 match parent.kind() {
399 fn bar() -> u32 { 300 STRUCT_DEF => HighlightTag::Struct.into(),
400 100 301 ENUM_DEF => HighlightTag::Enum.into(),
302 UNION_DEF => HighlightTag::Union.into(),
303 TRAIT_DEF => HighlightTag::Trait.into(),
304 TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(),
305 TYPE_PARAM => HighlightTag::TypeParam.into(),
306 RECORD_FIELD_DEF => HighlightTag::Field.into(),
307 _ => default,
401 } 308 }
402} 309}
403 310
404// comment 311fn highlight_injection(
405fn main() { 312 acc: &mut Vec<HighlightedRange>,
406 println!("Hello, {}!", 92); 313 sema: &Semantics<RootDatabase>,
407 314 literal: ast::RawString,
408 let mut vec = Vec::new(); 315 expanded: SyntaxToken,
409 if true { 316) -> Option<()> {
410 let x = 92; 317 let call_info = call_info_for_token(&sema, expanded)?;
411 vec.push(Foo { x, y: 1 }); 318 let idx = call_info.active_parameter?;
319 let name = call_info.signature.parameter_names.get(idx)?;
320 if name != "ra_fixture" {
321 return None;
412 } 322 }
413 unsafe { vec.set_len(0); } 323 let value = literal.value()?;
414 324 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
415 let mut x = 42; 325
416 let y = &mut x; 326 if let Some(range) = literal.open_quote_text_range() {
417 let z = &y; 327 acc.push(HighlightedRange {
418 328 range,
419 y; 329 highlight: HighlightTag::StringLiteral.into(),
420} 330 binding_hash: None,
421 331 })
422enum E<X> {
423 V(X)
424}
425
426impl<X> E<X> {
427 fn new<T>() -> E<T> {}
428}
429"#
430 .trim(),
431 );
432 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html");
433 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
434 let expected_html = &read_text(&dst_file);
435 fs::write(dst_file, &actual_html).unwrap();
436 assert_eq_text!(expected_html, actual_html);
437 } 332 }
438 333
439 #[test] 334 for mut h in analysis.highlight(tmp_file_id).unwrap() {
440 fn test_rainbow_highlighting() { 335 if let Some(r) = literal.map_range_up(h.range) {
441 let (analysis, file_id) = single_file( 336 h.range = r;
442 r#" 337 acc.push(h)
443fn main() { 338 }
444 let hello = "hello";
445 let x = hello.to_string();
446 let y = hello.to_string();
447
448 let x = "other color please!";
449 let y = x.to_string();
450}
451
452fn bar() {
453 let mut hello = "hello";
454}
455"#
456 .trim(),
457 );
458 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html");
459 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
460 let expected_html = &read_text(&dst_file);
461 fs::write(dst_file, &actual_html).unwrap();
462 assert_eq_text!(expected_html, actual_html);
463 } 339 }
464 340
465 #[test] 341 if let Some(range) = literal.close_quote_text_range() {
466 fn accidentally_quadratic() { 342 acc.push(HighlightedRange {
467 let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic"); 343 range,
468 let src = fs::read_to_string(file).unwrap(); 344 highlight: HighlightTag::StringLiteral.into(),
469 345 binding_hash: None,
470 let mut mock = MockAnalysis::new(); 346 })
471 let file_id = mock.add_file("/main.rs", &src);
472 let host = mock.analysis_host();
473
474 // let t = std::time::Instant::now();
475 let _ = host.analysis().highlight(file_id).unwrap();
476 // eprintln!("elapsed: {:?}", t.elapsed());
477 } 347 }
348
349 Some(())
478} 350}