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.rs197
1 files changed, 58 insertions, 139 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index d422930bf..4e95b9ce5 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,9 +1,9 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod highlight_tag; 3mod highlight;
4mod html;
4 5
5use hir::{Name, Semantics}; 6use hir::{Name, Semantics};
6use ra_db::SourceDatabase;
7use ra_ide_db::{ 7use ra_ide_db::{
8 defs::{classify_name, NameDefinition}, 8 defs::{classify_name, NameDefinition},
9 RootDatabase, 9 RootDatabase,
@@ -17,12 +17,14 @@ use rustc_hash::FxHashMap;
17 17
18use crate::{references::classify_name_ref, FileId}; 18use crate::{references::classify_name_ref, FileId};
19 19
20pub use highlight_tag::HighlightTag; 20pub use highlight::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
21
22pub(crate) use html::highlight_as_html;
21 23
22#[derive(Debug)] 24#[derive(Debug)]
23pub struct HighlightedRange { 25pub struct HighlightedRange {
24 pub range: TextRange, 26 pub range: TextRange,
25 pub tag: HighlightTag, 27 pub highlight: Highlight,
26 pub binding_hash: Option<u64>, 28 pub binding_hash: Option<u64>,
27} 29}
28 30
@@ -79,33 +81,33 @@ pub(crate) fn highlight(
79 if let Some(range) = highlight_macro(node) { 81 if let Some(range) = highlight_macro(node) {
80 res.push(HighlightedRange { 82 res.push(HighlightedRange {
81 range, 83 range,
82 tag: HighlightTag::MACRO, 84 highlight: HighlightTag::Macro.into(),
83 binding_hash: None, 85 binding_hash: None,
84 }); 86 });
85 } 87 }
86 } 88 }
87 _ if in_macro_call.is_some() => { 89 _ if in_macro_call.is_some() => {
88 if let Some(token) = node.as_token() { 90 if let Some(token) = node.as_token() {
89 if let Some((tag, binding_hash)) = highlight_token_tree( 91 if let Some((highlight, binding_hash)) = highlight_token_tree(
90 &sema, 92 &sema,
91 &mut bindings_shadow_count, 93 &mut bindings_shadow_count,
92 token.clone(), 94 token.clone(),
93 ) { 95 ) {
94 res.push(HighlightedRange { 96 res.push(HighlightedRange {
95 range: node.text_range(), 97 range: node.text_range(),
96 tag, 98 highlight,
97 binding_hash, 99 binding_hash,
98 }); 100 });
99 } 101 }
100 } 102 }
101 } 103 }
102 _ => { 104 _ => {
103 if let Some((tag, binding_hash)) = 105 if let Some((highlight, binding_hash)) =
104 highlight_node(&sema, &mut bindings_shadow_count, node.clone()) 106 highlight_node(&sema, &mut bindings_shadow_count, node.clone())
105 { 107 {
106 res.push(HighlightedRange { 108 res.push(HighlightedRange {
107 range: node.text_range(), 109 range: node.text_range(),
108 tag, 110 highlight,
109 binding_hash, 111 binding_hash,
110 }); 112 });
111 } 113 }
@@ -150,7 +152,7 @@ fn highlight_token_tree(
150 sema: &Semantics<RootDatabase>, 152 sema: &Semantics<RootDatabase>,
151 bindings_shadow_count: &mut FxHashMap<Name, u32>, 153 bindings_shadow_count: &mut FxHashMap<Name, u32>,
152 token: SyntaxToken, 154 token: SyntaxToken,
153) -> Option<(HighlightTag, Option<u64>)> { 155) -> Option<(Highlight, Option<u64>)> {
154 if token.parent().kind() != TOKEN_TREE { 156 if token.parent().kind() != TOKEN_TREE {
155 return None; 157 return None;
156 } 158 }
@@ -171,19 +173,21 @@ fn highlight_node(
171 sema: &Semantics<RootDatabase>, 173 sema: &Semantics<RootDatabase>,
172 bindings_shadow_count: &mut FxHashMap<Name, u32>, 174 bindings_shadow_count: &mut FxHashMap<Name, u32>,
173 node: SyntaxElement, 175 node: SyntaxElement,
174) -> Option<(HighlightTag, Option<u64>)> { 176) -> Option<(Highlight, Option<u64>)> {
175 let db = sema.db; 177 let db = sema.db;
176 let mut binding_hash = None; 178 let mut binding_hash = None;
177 let tag = match node.kind() { 179 let highlight: Highlight = match node.kind() {
178 FN_DEF => { 180 FN_DEF => {
179 bindings_shadow_count.clear(); 181 bindings_shadow_count.clear();
180 return None; 182 return None;
181 } 183 }
182 COMMENT => HighlightTag::LITERAL_COMMENT, 184 COMMENT => HighlightTag::Comment.into(),
183 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LITERAL_STRING, 185 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LiteralString.into(),
184 ATTR => HighlightTag::LITERAL_ATTRIBUTE, 186 ATTR => HighlightTag::Attribute.into(),
185 // Special-case field init shorthand 187 // Special-case field init shorthand
186 NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => HighlightTag::FIELD, 188 NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => {
189 HighlightTag::Field.into()
190 }
187 NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => return None, 191 NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => return None,
188 NAME_REF => { 192 NAME_REF => {
189 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); 193 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
@@ -217,26 +221,30 @@ fn highlight_node(
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(HighlightTag::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 => HighlightTag::TYPE, 225 match x.kind() {
222 TYPE_PARAM => HighlightTag::TYPE_PARAM, 226 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => {
223 RECORD_FIELD_DEF => HighlightTag::FIELD, 227 HighlightTag::Type.into()
224 _ => HighlightTag::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 => HighlightTag::LITERAL_NUMERIC, 236 INT_NUMBER | FLOAT_NUMBER => HighlightTag::LiteralNumeric.into(),
229 BYTE => HighlightTag::LITERAL_BYTE, 237 BYTE => HighlightTag::LiteralByte.into(),
230 CHAR => HighlightTag::LITERAL_CHAR, 238 CHAR => HighlightTag::LiteralChar.into(),
231 LIFETIME => HighlightTag::TYPE_LIFETIME, 239 LIFETIME => HighlightTag::TypeLifetime.into(),
232 T![unsafe] => HighlightTag::KEYWORD_UNSAFE, 240 T![unsafe] => HighlightTag::Keyword | HighlightModifier::Unsafe,
233 k if is_control_keyword(k) => HighlightTag::KEYWORD_CONTROL, 241 k if is_control_keyword(k) => HighlightTag::Keyword | HighlightModifier::Control,
234 k if k.is_keyword() => HighlightTag::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(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 {
@@ -251,123 +259,34 @@ fn highlight_node(
251 } 259 }
252} 260}
253 261
254pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { 262fn 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, None);
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.to_string()).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
313fn highlight_name(db: &RootDatabase, def: NameDefinition) -> HighlightTag {
314 match def { 263 match def {
315 NameDefinition::Macro(_) => HighlightTag::MACRO, 264 NameDefinition::Macro(_) => HighlightTag::Macro,
316 NameDefinition::StructField(_) => HighlightTag::FIELD, 265 NameDefinition::StructField(_) => HighlightTag::Field,
317 NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::MODULE, 266 NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::Module,
318 NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::FUNCTION, 267 NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::Function,
319 NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::TYPE, 268 NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::Type,
320 NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::CONSTANT, 269 NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::Constant,
321 NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::CONSTANT, 270 NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::Constant,
322 NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::CONSTANT, 271 NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::Constant,
323 NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::TYPE, 272 NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::Type,
324 NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::TYPE, 273 NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::Type,
325 NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => HighlightTag::TYPE_BUILTIN, 274 NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
326 NameDefinition::SelfType(_) => HighlightTag::TYPE_SELF, 275 return HighlightTag::Type | HighlightModifier::Builtin
327 NameDefinition::TypeParam(_) => HighlightTag::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 HighlightTag::VARIABLE_MUT 282 h |= HighlightModifier::Mutable;
331 } else {
332 HighlightTag::VARIABLE
333 } 283 }
284 return h;
334 } 285 }
335 } 286 }
287 .into()
336} 288}
337 289
338//FIXME: like, real html escaping
339fn html_escape(text: &str) -> String {
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)] 290#[cfg(test)]
372mod tests { 291mod tests {
373 use std::fs; 292 use std::fs;
@@ -498,6 +417,6 @@ fn bar() {
498 }) 417 })
499 .unwrap(); 418 .unwrap();
500 419
501 assert_eq!(&highlights[0].tag.to_string(), "field"); 420 assert_eq!(&highlights[0].highlight.to_string(), "field");
502 } 421 }
503} 422}