diff options
author | Pascal Hertleif <[email protected]> | 2019-05-23 18:42:42 +0100 |
---|---|---|
committer | Pascal Hertleif <[email protected]> | 2019-05-27 10:26:33 +0100 |
commit | 5bf3e949e8470a138a61c806769e1a329761cab6 (patch) | |
tree | 9885346944b4aa82804514580944673a53605ee2 | |
parent | 4b48cff022a1606bde596f01fbf44361640b10d8 (diff) |
Semantic highlighting spike
Very simple approach: For each identifier, set the hash of the range
where it's defined as its 'id' and use it in the VSCode extension to
generate unique colors.
Thus, the generated colors are per-file. They are also quite fragile,
and I'm not entirely sure why. Looks like we need to make sure the
same ranges aren't overwritten by a later request?
-rw-r--r-- | crates/ra_ide_api/src/snapshots/tests__highlighting.snap | 192 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap | 87 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_highlighting.rs | 101 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 6 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 1 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_node.rs | 4 | ||||
-rw-r--r-- | editors/code/package-lock.json | 10 | ||||
-rw-r--r-- | editors/code/package.json | 2 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 45 |
9 files changed, 409 insertions, 39 deletions
diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap new file mode 100644 index 000000000..208681f10 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap | |||
@@ -0,0 +1,192 @@ | |||
1 | --- | ||
2 | created: "2019-05-25T10:53:54.439877Z" | ||
3 | creator: [email protected] | ||
4 | source: crates/ra_ide_api/src/syntax_highlighting.rs | ||
5 | expression: result | ||
6 | --- | ||
7 | Ok( | ||
8 | [ | ||
9 | HighlightedRange { | ||
10 | range: [1; 24), | ||
11 | tag: "attribute", | ||
12 | id: None, | ||
13 | }, | ||
14 | HighlightedRange { | ||
15 | range: [25; 31), | ||
16 | tag: "keyword", | ||
17 | id: None, | ||
18 | }, | ||
19 | HighlightedRange { | ||
20 | range: [32; 35), | ||
21 | tag: "variable", | ||
22 | id: Some( | ||
23 | 461893210254723387, | ||
24 | ), | ||
25 | }, | ||
26 | HighlightedRange { | ||
27 | range: [42; 45), | ||
28 | tag: "keyword", | ||
29 | id: None, | ||
30 | }, | ||
31 | HighlightedRange { | ||
32 | range: [46; 47), | ||
33 | tag: "variable", | ||
34 | id: Some( | ||
35 | 8312289520117458465, | ||
36 | ), | ||
37 | }, | ||
38 | HighlightedRange { | ||
39 | range: [49; 52), | ||
40 | tag: "text", | ||
41 | id: None, | ||
42 | }, | ||
43 | HighlightedRange { | ||
44 | range: [58; 61), | ||
45 | tag: "keyword", | ||
46 | id: None, | ||
47 | }, | ||
48 | HighlightedRange { | ||
49 | range: [62; 63), | ||
50 | tag: "variable", | ||
51 | id: Some( | ||
52 | 4497542318236667727, | ||
53 | ), | ||
54 | }, | ||
55 | HighlightedRange { | ||
56 | range: [65; 68), | ||
57 | tag: "text", | ||
58 | id: None, | ||
59 | }, | ||
60 | HighlightedRange { | ||
61 | range: [73; 75), | ||
62 | tag: "keyword", | ||
63 | id: None, | ||
64 | }, | ||
65 | HighlightedRange { | ||
66 | range: [76; 79), | ||
67 | tag: "variable", | ||
68 | id: Some( | ||
69 | 4506850079084802999, | ||
70 | ), | ||
71 | }, | ||
72 | HighlightedRange { | ||
73 | range: [80; 81), | ||
74 | tag: "type", | ||
75 | id: None, | ||
76 | }, | ||
77 | HighlightedRange { | ||
78 | range: [80; 81), | ||
79 | tag: "variable", | ||
80 | id: Some( | ||
81 | 16968185728268100018, | ||
82 | ), | ||
83 | }, | ||
84 | HighlightedRange { | ||
85 | range: [88; 89), | ||
86 | tag: "type", | ||
87 | id: None, | ||
88 | }, | ||
89 | HighlightedRange { | ||
90 | range: [96; 110), | ||
91 | tag: "macro", | ||
92 | id: None, | ||
93 | }, | ||
94 | HighlightedRange { | ||
95 | range: [117; 127), | ||
96 | tag: "comment", | ||
97 | id: None, | ||
98 | }, | ||
99 | HighlightedRange { | ||
100 | range: [128; 130), | ||
101 | tag: "keyword", | ||
102 | id: None, | ||
103 | }, | ||
104 | HighlightedRange { | ||
105 | range: [131; 135), | ||
106 | tag: "variable", | ||
107 | id: Some( | ||
108 | 14467718814232352107, | ||
109 | ), | ||
110 | }, | ||
111 | HighlightedRange { | ||
112 | range: [145; 153), | ||
113 | tag: "macro", | ||
114 | id: None, | ||
115 | }, | ||
116 | HighlightedRange { | ||
117 | range: [154; 166), | ||
118 | tag: "string", | ||
119 | id: None, | ||
120 | }, | ||
121 | HighlightedRange { | ||
122 | range: [168; 170), | ||
123 | tag: "literal", | ||
124 | id: None, | ||
125 | }, | ||
126 | HighlightedRange { | ||
127 | range: [178; 181), | ||
128 | tag: "keyword", | ||
129 | id: None, | ||
130 | }, | ||
131 | HighlightedRange { | ||
132 | range: [182; 185), | ||
133 | tag: "keyword", | ||
134 | id: None, | ||
135 | }, | ||
136 | HighlightedRange { | ||
137 | range: [186; 189), | ||
138 | tag: "macro", | ||
139 | id: None, | ||
140 | }, | ||
141 | HighlightedRange { | ||
142 | range: [197; 200), | ||
143 | tag: "macro", | ||
144 | id: None, | ||
145 | }, | ||
146 | HighlightedRange { | ||
147 | range: [192; 195), | ||
148 | tag: "text", | ||
149 | id: None, | ||
150 | }, | ||
151 | HighlightedRange { | ||
152 | range: [208; 211), | ||
153 | tag: "macro", | ||
154 | id: None, | ||
155 | }, | ||
156 | HighlightedRange { | ||
157 | range: [212; 216), | ||
158 | tag: "macro", | ||
159 | id: None, | ||
160 | }, | ||
161 | HighlightedRange { | ||
162 | range: [226; 227), | ||
163 | tag: "literal", | ||
164 | id: None, | ||
165 | }, | ||
166 | HighlightedRange { | ||
167 | range: [232; 233), | ||
168 | tag: "literal", | ||
169 | id: None, | ||
170 | }, | ||
171 | HighlightedRange { | ||
172 | range: [242; 248), | ||
173 | tag: "keyword.unsafe", | ||
174 | id: None, | ||
175 | }, | ||
176 | HighlightedRange { | ||
177 | range: [251; 254), | ||
178 | tag: "text", | ||
179 | id: None, | ||
180 | }, | ||
181 | HighlightedRange { | ||
182 | range: [255; 262), | ||
183 | tag: "text", | ||
184 | id: None, | ||
185 | }, | ||
186 | HighlightedRange { | ||
187 | range: [263; 264), | ||
188 | tag: "literal", | ||
189 | id: None, | ||
190 | }, | ||
191 | ], | ||
192 | ) | ||
diff --git a/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap new file mode 100644 index 000000000..3b3fe32e9 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap | |||
@@ -0,0 +1,87 @@ | |||
1 | --- | ||
2 | created: "2019-05-25T10:25:13.898113Z" | ||
3 | creator: [email protected] | ||
4 | source: crates/ra_ide_api/src/syntax_highlighting.rs | ||
5 | expression: result | ||
6 | --- | ||
7 | Ok( | ||
8 | [ | ||
9 | HighlightedRange { | ||
10 | range: [1; 3), | ||
11 | tag: "keyword", | ||
12 | id: None, | ||
13 | }, | ||
14 | HighlightedRange { | ||
15 | range: [4; 8), | ||
16 | tag: "variable", | ||
17 | id: Some( | ||
18 | 17119830160611610240, | ||
19 | ), | ||
20 | }, | ||
21 | HighlightedRange { | ||
22 | range: [17; 20), | ||
23 | tag: "keyword", | ||
24 | id: None, | ||
25 | }, | ||
26 | HighlightedRange { | ||
27 | range: [21; 26), | ||
28 | tag: "variable", | ||
29 | id: Some( | ||
30 | 2744494144922727377, | ||
31 | ), | ||
32 | }, | ||
33 | HighlightedRange { | ||
34 | range: [29; 36), | ||
35 | tag: "string", | ||
36 | id: None, | ||
37 | }, | ||
38 | HighlightedRange { | ||
39 | range: [42; 45), | ||
40 | tag: "keyword", | ||
41 | id: None, | ||
42 | }, | ||
43 | HighlightedRange { | ||
44 | range: [46; 47), | ||
45 | tag: "variable", | ||
46 | id: Some( | ||
47 | 10375904121795371996, | ||
48 | ), | ||
49 | }, | ||
50 | HighlightedRange { | ||
51 | range: [50; 55), | ||
52 | tag: "variable", | ||
53 | id: Some( | ||
54 | 2744494144922727377, | ||
55 | ), | ||
56 | }, | ||
57 | HighlightedRange { | ||
58 | range: [56; 65), | ||
59 | tag: "text", | ||
60 | id: None, | ||
61 | }, | ||
62 | HighlightedRange { | ||
63 | range: [73; 76), | ||
64 | tag: "keyword", | ||
65 | id: None, | ||
66 | }, | ||
67 | HighlightedRange { | ||
68 | range: [77; 78), | ||
69 | tag: "variable", | ||
70 | id: Some( | ||
71 | 8228548264153724449, | ||
72 | ), | ||
73 | }, | ||
74 | HighlightedRange { | ||
75 | range: [81; 86), | ||
76 | tag: "variable", | ||
77 | id: Some( | ||
78 | 2744494144922727377, | ||
79 | ), | ||
80 | }, | ||
81 | HighlightedRange { | ||
82 | range: [87; 96), | ||
83 | tag: "text", | ||
84 | id: None, | ||
85 | }, | ||
86 | ], | ||
87 | ) | ||
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 87e053364..da000c0c3 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs | |||
@@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase}; | |||
10 | pub struct HighlightedRange { | 10 | pub struct HighlightedRange { |
11 | pub range: TextRange, | 11 | pub range: TextRange, |
12 | pub tag: &'static str, | 12 | pub tag: &'static str, |
13 | pub id: Option<u64>, | ||
13 | } | 14 | } |
14 | 15 | ||
15 | fn is_control_keyword(kind: SyntaxKind) -> bool { | 16 | fn is_control_keyword(kind: SyntaxKind) -> bool { |
@@ -32,6 +33,14 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
32 | 33 | ||
33 | let source_file = db.parse(file_id); | 34 | let source_file = db.parse(file_id); |
34 | 35 | ||
36 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | ||
37 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | ||
38 | |||
39 | let mut hasher = DefaultHasher::new(); | ||
40 | x.hash(&mut hasher); | ||
41 | hasher.finish() | ||
42 | } | ||
43 | |||
35 | // Visited nodes to handle highlighting priorities | 44 | // Visited nodes to handle highlighting priorities |
36 | let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default(); | 45 | let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default(); |
37 | let mut res = Vec::new(); | 46 | let mut res = Vec::new(); |
@@ -39,52 +48,59 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
39 | if highlighted.contains(&node) { | 48 | if highlighted.contains(&node) { |
40 | continue; | 49 | continue; |
41 | } | 50 | } |
42 | let tag = match node.kind() { | 51 | let (tag, id) = match node.kind() { |
43 | COMMENT => "comment", | 52 | COMMENT => ("comment", None), |
44 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", | 53 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => ("string", None), |
45 | ATTR => "attribute", | 54 | ATTR => ("attribute", None), |
46 | NAME_REF => { | 55 | NAME_REF => { |
47 | if let Some(name_ref) = node.as_node().and_then(|n| ast::NameRef::cast(n)) { | 56 | if let Some(name_ref) = node.as_ast_node::<ast::NameRef>() { |
48 | use crate::name_ref_kind::{classify_name_ref, NameRefKind::*}; | 57 | use crate::name_ref_kind::{classify_name_ref, NameRefKind::*}; |
49 | use hir::{ModuleDef, ImplItem}; | 58 | use hir::{ModuleDef, ImplItem}; |
50 | 59 | ||
51 | // FIXME: try to reuse the SourceAnalyzers | 60 | // FIXME: try to reuse the SourceAnalyzers |
52 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | 61 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); |
53 | match classify_name_ref(db, &analyzer, name_ref) { | 62 | match classify_name_ref(db, &analyzer, name_ref) { |
54 | Some(Method(_)) => "function", | 63 | Some(Method(_)) => ("function", None), |
55 | Some(Macro(_)) => "macro", | 64 | Some(Macro(_)) => ("macro", None), |
56 | Some(FieldAccess(_)) => "field", | 65 | Some(FieldAccess(_)) => ("field", None), |
57 | Some(AssocItem(ImplItem::Method(_))) => "function", | 66 | Some(AssocItem(ImplItem::Method(_))) => ("function", None), |
58 | Some(AssocItem(ImplItem::Const(_))) => "constant", | 67 | Some(AssocItem(ImplItem::Const(_))) => ("constant", None), |
59 | Some(AssocItem(ImplItem::TypeAlias(_))) => "type", | 68 | Some(AssocItem(ImplItem::TypeAlias(_))) => ("type", None), |
60 | Some(Def(ModuleDef::Module(_))) => "module", | 69 | Some(Def(ModuleDef::Module(_))) => ("module", None), |
61 | Some(Def(ModuleDef::Function(_))) => "function", | 70 | Some(Def(ModuleDef::Function(_))) => ("function", None), |
62 | Some(Def(ModuleDef::Struct(_))) => "type", | 71 | Some(Def(ModuleDef::Struct(_))) => ("type", None), |
63 | Some(Def(ModuleDef::Union(_))) => "type", | 72 | Some(Def(ModuleDef::Union(_))) => ("type", None), |
64 | Some(Def(ModuleDef::Enum(_))) => "type", | 73 | Some(Def(ModuleDef::Enum(_))) => ("type", None), |
65 | Some(Def(ModuleDef::EnumVariant(_))) => "constant", | 74 | Some(Def(ModuleDef::EnumVariant(_))) => ("constant", None), |
66 | Some(Def(ModuleDef::Const(_))) => "constant", | 75 | Some(Def(ModuleDef::Const(_))) => ("constant", None), |
67 | Some(Def(ModuleDef::Static(_))) => "constant", | 76 | Some(Def(ModuleDef::Static(_))) => ("constant", None), |
68 | Some(Def(ModuleDef::Trait(_))) => "type", | 77 | Some(Def(ModuleDef::Trait(_))) => ("type", None), |
69 | Some(Def(ModuleDef::TypeAlias(_))) => "type", | 78 | Some(Def(ModuleDef::TypeAlias(_))) => ("type", None), |
70 | Some(SelfType(_)) => "type", | 79 | Some(SelfType(_)) => ("type", None), |
71 | Some(Pat(_)) => "text", | 80 | Some(Pat(ptr)) => ("variable", Some(hash(ptr.syntax_node_ptr().range()))), |
72 | Some(SelfParam(_)) => "type", | 81 | Some(SelfParam(_)) => ("type", None), |
73 | Some(GenericParam(_)) => "type", | 82 | Some(GenericParam(_)) => ("type", None), |
74 | None => "text", | 83 | None => ("text", None), |
75 | } | 84 | } |
76 | } else { | 85 | } else { |
77 | "text" | 86 | ("text", None) |
78 | } | 87 | } |
79 | } | 88 | } |
80 | NAME => "function", | 89 | NAME => { |
81 | TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type", | 90 | if let Some(name) = node.as_ast_node::<ast::Name>() { |
82 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", | 91 | ("variable", Some(hash(name.syntax().range()))) |
83 | LIFETIME => "parameter", | 92 | } else { |
84 | T![unsafe] => "keyword.unsafe", | 93 | ("text", None) |
85 | k if is_control_keyword(k) => "keyword.control", | 94 | } |
86 | k if k.is_keyword() => "keyword", | 95 | } |
96 | TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => ("type", None), | ||
97 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => ("literal", None), | ||
98 | LIFETIME => ("parameter", None), | ||
99 | T![unsafe] => ("keyword.unsafe", None), | ||
100 | k if is_control_keyword(k) => ("keyword.control", None), | ||
101 | k if k.is_keyword() => ("keyword", None), | ||
87 | _ => { | 102 | _ => { |
103 | // let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
88 | if let Some(macro_call) = node.as_node().and_then(ast::MacroCall::cast) { | 104 | if let Some(macro_call) = node.as_node().and_then(ast::MacroCall::cast) { |
89 | if let Some(path) = macro_call.path() { | 105 | if let Some(path) = macro_call.path() { |
90 | if let Some(segment) = path.segment() { | 106 | if let Some(segment) = path.segment() { |
@@ -101,6 +117,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
101 | res.push(HighlightedRange { | 117 | res.push(HighlightedRange { |
102 | range: TextRange::from_to(range_start, range_end), | 118 | range: TextRange::from_to(range_start, range_end), |
103 | tag: "macro", | 119 | tag: "macro", |
120 | id: None, | ||
104 | }) | 121 | }) |
105 | } | 122 | } |
106 | } | 123 | } |
@@ -109,7 +126,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
109 | continue; | 126 | continue; |
110 | } | 127 | } |
111 | }; | 128 | }; |
112 | res.push(HighlightedRange { range: node.range(), tag }) | 129 | res.push(HighlightedRange { range: node.range(), tag, id }) |
113 | } | 130 | } |
114 | res | 131 | res |
115 | } | 132 | } |
@@ -221,4 +238,18 @@ fn main() { | |||
221 | // std::fs::write(dst_file, &actual_html).unwrap(); | 238 | // std::fs::write(dst_file, &actual_html).unwrap(); |
222 | assert_eq_text!(expected_html, actual_html); | 239 | assert_eq_text!(expected_html, actual_html); |
223 | } | 240 | } |
241 | |||
242 | #[test] | ||
243 | fn test_sematic_highlighting() { | ||
244 | let (analysis, file_id) = single_file( | ||
245 | r#" | ||
246 | fn main() { | ||
247 | let hello = "hello"; | ||
248 | let x = hello.to_string(); | ||
249 | let y = hello.to_string(); | ||
250 | }"#, | ||
251 | ); | ||
252 | let result = analysis.highlight(file_id); | ||
253 | assert_debug_snapshot_matches!("sematic_highlighting", result); | ||
254 | } | ||
224 | } | 255 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index a82ae696b..5dfd64ed4 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -872,7 +872,11 @@ fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> { | |||
872 | .analysis() | 872 | .analysis() |
873 | .highlight(file_id)? | 873 | .highlight(file_id)? |
874 | .into_iter() | 874 | .into_iter() |
875 | .map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag }) | 875 | .map(|h| Decoration { |
876 | range: h.range.conv_with(&line_index), | ||
877 | tag: h.tag, | ||
878 | id: h.id.map(|x| x.to_string()), | ||
879 | }) | ||
876 | .collect(); | 880 | .collect(); |
877 | Ok(res) | 881 | Ok(res) |
878 | } | 882 | } |
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 6090eb7b9..cea0e6ce7 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs | |||
@@ -129,6 +129,7 @@ pub struct PublishDecorationsParams { | |||
129 | pub struct Decoration { | 129 | pub struct Decoration { |
130 | pub range: Range, | 130 | pub range: Range, |
131 | pub tag: &'static str, | 131 | pub tag: &'static str, |
132 | pub id: Option<String>, | ||
132 | } | 133 | } |
133 | 134 | ||
134 | pub enum ParentModule {} | 135 | pub enum ParentModule {} |
diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index 80054f529..89f92e0b7 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs | |||
@@ -523,6 +523,10 @@ impl<'a> SyntaxElement<'a> { | |||
523 | } | 523 | } |
524 | } | 524 | } |
525 | 525 | ||
526 | pub fn as_ast_node<T: AstNode>(&self) -> Option<&T> { | ||
527 | self.as_node().and_then(|x| <T as AstNode>::cast(x)) | ||
528 | } | ||
529 | |||
526 | pub fn as_token(&self) -> Option<SyntaxToken<'a>> { | 530 | pub fn as_token(&self) -> Option<SyntaxToken<'a>> { |
527 | match self { | 531 | match self { |
528 | SyntaxElement::Node(_) => None, | 532 | SyntaxElement::Node(_) => None, |
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 29cd260a4..6b3a12f91 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json | |||
@@ -36,6 +36,11 @@ | |||
36 | "integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==", | 36 | "integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==", |
37 | "dev": true | 37 | "dev": true |
38 | }, | 38 | }, |
39 | "@types/seedrandom": { | ||
40 | "version": "2.4.28", | ||
41 | "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz", | ||
42 | "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==" | ||
43 | }, | ||
39 | "agent-base": { | 44 | "agent-base": { |
40 | "version": "4.2.1", | 45 | "version": "4.2.1", |
41 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", | 46 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", |
@@ -984,6 +989,11 @@ | |||
984 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", | 989 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", |
985 | "dev": true | 990 | "dev": true |
986 | }, | 991 | }, |
992 | "seedrandom": { | ||
993 | "version": "3.0.1", | ||
994 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.1.tgz", | ||
995 | "integrity": "sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg==" | ||
996 | }, | ||
987 | "semver": { | 997 | "semver": { |
988 | "version": "5.7.0", | 998 | "version": "5.7.0", |
989 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", | 999 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", |
diff --git a/editors/code/package.json b/editors/code/package.json index cde5fbcb8..d8ba914f5 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -31,11 +31,13 @@ | |||
31 | "singleQuote": true | 31 | "singleQuote": true |
32 | }, | 32 | }, |
33 | "dependencies": { | 33 | "dependencies": { |
34 | "seedrandom": "^3.0.1", | ||
34 | "vscode-languageclient": "^5.3.0-next.4" | 35 | "vscode-languageclient": "^5.3.0-next.4" |
35 | }, | 36 | }, |
36 | "devDependencies": { | 37 | "devDependencies": { |
37 | "@types/mocha": "^5.2.6", | 38 | "@types/mocha": "^5.2.6", |
38 | "@types/node": "^10.14.5", | 39 | "@types/node": "^10.14.5", |
40 | "@types/seedrandom": "^2.4.28", | ||
39 | "prettier": "^1.17.0", | 41 | "prettier": "^1.17.0", |
40 | "shx": "^0.3.1", | 42 | "shx": "^0.3.1", |
41 | "tslint": "^5.16.0", | 43 | "tslint": "^5.16.0", |
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 8389d94b8..4597db08f 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import seedrandom = require('seedrandom'); | ||
1 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 3 | import * as lc from 'vscode-languageclient'; |
3 | 4 | ||
@@ -6,6 +7,20 @@ import { Server } from './server'; | |||
6 | export interface Decoration { | 7 | export interface Decoration { |
7 | range: lc.Range; | 8 | range: lc.Range; |
8 | tag: string; | 9 | tag: string; |
10 | id?: string; | ||
11 | } | ||
12 | |||
13 | // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 | ||
14 | function fancify(seed: string, shade: 'light' | 'dark') { | ||
15 | const random = seedrandom(seed); | ||
16 | const randomInt = (min: number, max: number) => { | ||
17 | return Math.floor(random() * (max - min + 1)) + min; | ||
18 | }; | ||
19 | |||
20 | const h = randomInt(0, 360); | ||
21 | const s = randomInt(42, 98); | ||
22 | const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90); | ||
23 | return `hsl(${h},${s}%,${l}%)`; | ||
9 | } | 24 | } |
10 | 25 | ||
11 | export class Highlighter { | 26 | export class Highlighter { |
@@ -76,6 +91,8 @@ export class Highlighter { | |||
76 | } | 91 | } |
77 | 92 | ||
78 | const byTag: Map<string, vscode.Range[]> = new Map(); | 93 | const byTag: Map<string, vscode.Range[]> = new Map(); |
94 | const colorfulIdents: Map<string, vscode.Range[]> = new Map(); | ||
95 | |||
79 | for (const tag of this.decorations.keys()) { | 96 | for (const tag of this.decorations.keys()) { |
80 | byTag.set(tag, []); | 97 | byTag.set(tag, []); |
81 | } | 98 | } |
@@ -84,9 +101,23 @@ export class Highlighter { | |||
84 | if (!byTag.get(d.tag)) { | 101 | if (!byTag.get(d.tag)) { |
85 | continue; | 102 | continue; |
86 | } | 103 | } |
87 | byTag | 104 | |
88 | .get(d.tag)! | 105 | if (d.id) { |
89 | .push(Server.client.protocol2CodeConverter.asRange(d.range)); | 106 | if (!colorfulIdents.has(d.id)) { |
107 | colorfulIdents.set(d.id, []); | ||
108 | } | ||
109 | colorfulIdents | ||
110 | .get(d.id)! | ||
111 | .push( | ||
112 | Server.client.protocol2CodeConverter.asRange(d.range) | ||
113 | ); | ||
114 | } else { | ||
115 | byTag | ||
116 | .get(d.tag)! | ||
117 | .push( | ||
118 | Server.client.protocol2CodeConverter.asRange(d.range) | ||
119 | ); | ||
120 | } | ||
90 | } | 121 | } |
91 | 122 | ||
92 | for (const tag of byTag.keys()) { | 123 | for (const tag of byTag.keys()) { |
@@ -96,5 +127,13 @@ export class Highlighter { | |||
96 | const ranges = byTag.get(tag)!; | 127 | const ranges = byTag.get(tag)!; |
97 | editor.setDecorations(dec, ranges); | 128 | editor.setDecorations(dec, ranges); |
98 | } | 129 | } |
130 | |||
131 | for (const [hash, ranges] of colorfulIdents.entries()) { | ||
132 | const dec = vscode.window.createTextEditorDecorationType({ | ||
133 | light: { color: fancify(hash, 'light') }, | ||
134 | dark: { color: fancify(hash, 'dark') } | ||
135 | }); | ||
136 | editor.setDecorations(dec, ranges); | ||
137 | } | ||
99 | } | 138 | } |
100 | } | 139 | } |