diff options
author | Aleksey Kladov <[email protected]> | 2020-02-27 15:48:06 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-27 15:48:06 +0000 |
commit | de492d439f0cda73fe2d9b25e4d9f028f70eb095 (patch) | |
tree | 6c7053d80c08ba65266a86771e965ce572521c1f /crates | |
parent | 062c12e3cdad887d751defa6f448edb5426ebf01 (diff) | |
parent | c6247f74c72857de3619a080698237d58ff9e960 (diff) |
Merge pull request #3349 from matklad/yo-dawg
Put syntax highlighting into syntax highlighting
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir_ty/src/tests.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide/src/call_info.rs | 34 | ||||
-rw-r--r-- | crates/ra_ide/src/mock_analysis.rs | 16 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 59 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 30 |
5 files changed, 120 insertions, 23 deletions
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index 240cc03a2..087edcc92 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs | |||
@@ -51,8 +51,8 @@ fn type_at(content: &str) -> String { | |||
51 | type_at_pos(&db, file_pos) | 51 | type_at_pos(&db, file_pos) |
52 | } | 52 | } |
53 | 53 | ||
54 | fn infer(content: &str) -> String { | 54 | fn infer(ra_fixture: &str) -> String { |
55 | infer_with_mismatches(content, false) | 55 | infer_with_mismatches(ra_fixture, false) |
56 | } | 56 | } |
57 | 57 | ||
58 | fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { | 58 | fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { |
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs index 9a1fc0d35..2b35a3803 100644 --- a/crates/ra_ide/src/call_info.rs +++ b/crates/ra_ide/src/call_info.rs | |||
@@ -3,7 +3,7 @@ use hir::Semantics; | |||
3 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | ast::{self, ArgListOwner}, | 5 | ast::{self, ArgListOwner}, |
6 | match_ast, AstNode, SyntaxNode, | 6 | match_ast, AstNode, SyntaxNode, SyntaxToken, |
7 | }; | 7 | }; |
8 | use test_utils::tested_by; | 8 | use test_utils::tested_by; |
9 | 9 | ||
@@ -16,7 +16,13 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal | |||
16 | let file = file.syntax(); | 16 | let file = file.syntax(); |
17 | let token = file.token_at_offset(position.offset).next()?; | 17 | let token = file.token_at_offset(position.offset).next()?; |
18 | let token = sema.descend_into_macros(token); | 18 | let token = sema.descend_into_macros(token); |
19 | call_info_for_token(&sema, token) | ||
20 | } | ||
19 | 21 | ||
22 | pub(crate) fn call_info_for_token( | ||
23 | sema: &Semantics<RootDatabase>, | ||
24 | token: SyntaxToken, | ||
25 | ) -> Option<CallInfo> { | ||
20 | // Find the calling expression and it's NameRef | 26 | // Find the calling expression and it's NameRef |
21 | let calling_node = FnCallNode::with_node(&token.parent())?; | 27 | let calling_node = FnCallNode::with_node(&token.parent())?; |
22 | 28 | ||
@@ -27,21 +33,23 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal | |||
27 | match callable_def { | 33 | match callable_def { |
28 | hir::CallableDef::FunctionId(it) => { | 34 | hir::CallableDef::FunctionId(it) => { |
29 | let fn_def = it.into(); | 35 | let fn_def = it.into(); |
30 | (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db)) | 36 | (CallInfo::with_fn(sema.db, fn_def), fn_def.has_self_param(sema.db)) |
37 | } | ||
38 | hir::CallableDef::StructId(it) => { | ||
39 | (CallInfo::with_struct(sema.db, it.into())?, false) | ||
31 | } | 40 | } |
32 | hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false), | ||
33 | hir::CallableDef::EnumVariantId(it) => { | 41 | hir::CallableDef::EnumVariantId(it) => { |
34 | (CallInfo::with_enum_variant(db, it.into())?, false) | 42 | (CallInfo::with_enum_variant(sema.db, it.into())?, false) |
35 | } | 43 | } |
36 | } | 44 | } |
37 | } | 45 | } |
38 | FnCallNode::MethodCallExpr(method_call) => { | 46 | FnCallNode::MethodCallExpr(method_call) => { |
39 | let function = sema.resolve_method_call(&method_call)?; | 47 | let function = sema.resolve_method_call(&method_call)?; |
40 | (CallInfo::with_fn(db, function), function.has_self_param(db)) | 48 | (CallInfo::with_fn(sema.db, function), function.has_self_param(sema.db)) |
41 | } | 49 | } |
42 | FnCallNode::MacroCallExpr(macro_call) => { | 50 | FnCallNode::MacroCallExpr(macro_call) => { |
43 | let macro_def = sema.resolve_macro_call(¯o_call)?; | 51 | let macro_def = sema.resolve_macro_call(¯o_call)?; |
44 | (CallInfo::with_macro(db, macro_def)?, false) | 52 | (CallInfo::with_macro(sema.db, macro_def)?, false) |
45 | } | 53 | } |
46 | }; | 54 | }; |
47 | 55 | ||
@@ -61,7 +69,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal | |||
61 | let num_args_at_callsite = arg_list.args().count(); | 69 | let num_args_at_callsite = arg_list.args().count(); |
62 | 70 | ||
63 | let arg_list_range = arg_list.syntax().text_range(); | 71 | let arg_list_range = arg_list.syntax().text_range(); |
64 | if !arg_list_range.contains_inclusive(position.offset) { | 72 | if !arg_list_range.contains_inclusive(token.text_range().start()) { |
65 | tested_by!(call_info_bad_offset); | 73 | tested_by!(call_info_bad_offset); |
66 | return None; | 74 | return None; |
67 | } | 75 | } |
@@ -70,7 +78,9 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal | |||
70 | num_args_at_callsite, | 78 | num_args_at_callsite, |
71 | arg_list | 79 | arg_list |
72 | .args() | 80 | .args() |
73 | .take_while(|arg| arg.syntax().text_range().end() < position.offset) | 81 | .take_while(|arg| { |
82 | arg.syntax().text_range().end() < token.text_range().start() | ||
83 | }) | ||
74 | .count(), | 84 | .count(), |
75 | ); | 85 | ); |
76 | 86 | ||
@@ -100,7 +110,13 @@ impl FnCallNode { | |||
100 | match_ast! { | 110 | match_ast! { |
101 | match node { | 111 | match node { |
102 | ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, | 112 | ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, |
103 | ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, | 113 | ast::MethodCallExpr(it) => { |
114 | let arg_list = it.arg_list()?; | ||
115 | if !syntax.text_range().is_subrange(&arg_list.syntax().text_range()) { | ||
116 | return None; | ||
117 | } | ||
118 | Some(FnCallNode::MethodCallExpr(it)) | ||
119 | }, | ||
104 | ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, | 120 | ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, |
105 | _ => { None }, | 121 | _ => { None }, |
106 | } | 122 | } |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 081aaee8c..f4cd6deb7 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -124,28 +124,28 @@ impl MockAnalysis { | |||
124 | } | 124 | } |
125 | 125 | ||
126 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. | 126 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. |
127 | pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { | 127 | pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) { |
128 | let (mock, position) = MockAnalysis::with_files_and_position(fixture); | 128 | let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture); |
129 | (mock.analysis(), position) | 129 | (mock.analysis(), position) |
130 | } | 130 | } |
131 | 131 | ||
132 | /// Creates analysis for a single file. | 132 | /// Creates analysis for a single file. |
133 | pub fn single_file(code: &str) -> (Analysis, FileId) { | 133 | pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) { |
134 | let mut mock = MockAnalysis::new(); | 134 | let mut mock = MockAnalysis::new(); |
135 | let file_id = mock.add_file("/main.rs", code); | 135 | let file_id = mock.add_file("/main.rs", ra_fixture); |
136 | (mock.analysis(), file_id) | 136 | (mock.analysis(), file_id) |
137 | } | 137 | } |
138 | 138 | ||
139 | /// Creates analysis for a single file, returns position marked with <|>. | 139 | /// Creates analysis for a single file, returns position marked with <|>. |
140 | pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { | 140 | pub fn single_file_with_position(ra_fixture: &str) -> (Analysis, FilePosition) { |
141 | let mut mock = MockAnalysis::new(); | 141 | let mut mock = MockAnalysis::new(); |
142 | let pos = mock.add_file_with_position("/main.rs", code); | 142 | let pos = mock.add_file_with_position("/main.rs", ra_fixture); |
143 | (mock.analysis(), pos) | 143 | (mock.analysis(), pos) |
144 | } | 144 | } |
145 | 145 | ||
146 | /// Creates analysis for a single file, returns range marked with a pair of <|>. | 146 | /// Creates analysis for a single file, returns range marked with a pair of <|>. |
147 | pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) { | 147 | pub fn single_file_with_range(ra_fixture: &str) -> (Analysis, FileRange) { |
148 | let mut mock = MockAnalysis::new(); | 148 | let mut mock = MockAnalysis::new(); |
149 | let pos = mock.add_file_with_range("/main.rs", code); | 149 | let pos = mock.add_file_with_range("/main.rs", ra_fixture); |
150 | (mock.analysis(), pos) | 150 | (mock.analysis(), pos) |
151 | } | 151 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 796f0e545..3a5cbee9b 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -12,11 +12,12 @@ use ra_ide_db::{ | |||
12 | }; | 12 | }; |
13 | use ra_prof::profile; | 13 | use ra_prof::profile; |
14 | use ra_syntax::{ | 14 | use ra_syntax::{ |
15 | ast, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, TextRange, WalkEvent, T, | 15 | ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, SyntaxToken, |
16 | TextRange, WalkEvent, T, | ||
16 | }; | 17 | }; |
17 | use rustc_hash::FxHashMap; | 18 | use rustc_hash::FxHashMap; |
18 | 19 | ||
19 | use crate::{references::classify_name_ref, FileId}; | 20 | use crate::{call_info::call_info_for_token, references::classify_name_ref, Analysis, FileId}; |
20 | 21 | ||
21 | pub(crate) use html::highlight_as_html; | 22 | pub(crate) use html::highlight_as_html; |
22 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 23 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
@@ -94,11 +95,12 @@ pub(crate) fn highlight( | |||
94 | WalkEvent::Enter(it) => it, | 95 | WalkEvent::Enter(it) => it, |
95 | WalkEvent::Leave(_) => continue, | 96 | WalkEvent::Leave(_) => continue, |
96 | }; | 97 | }; |
98 | |||
97 | let range = element.text_range(); | 99 | let range = element.text_range(); |
98 | 100 | ||
99 | let element_to_highlight = if current_macro_call.is_some() { | 101 | let element_to_highlight = if current_macro_call.is_some() { |
100 | // Inside a macro -- expand it first | 102 | // Inside a macro -- expand it first |
101 | let token = match element.into_token() { | 103 | let token = match element.clone().into_token() { |
102 | Some(it) if it.parent().kind() == TOKEN_TREE => it, | 104 | Some(it) if it.parent().kind() == TOKEN_TREE => it, |
103 | _ => continue, | 105 | _ => continue, |
104 | }; | 106 | }; |
@@ -110,9 +112,17 @@ pub(crate) fn highlight( | |||
110 | _ => token.into(), | 112 | _ => token.into(), |
111 | } | 113 | } |
112 | } else { | 114 | } else { |
113 | element | 115 | element.clone() |
114 | }; | 116 | }; |
115 | 117 | ||
118 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | ||
119 | let expanded = element_to_highlight.as_token().unwrap().clone(); | ||
120 | if highlight_injection(&mut res, &sema, token, expanded).is_some() { | ||
121 | eprintln!("res = {:?}", res); | ||
122 | continue; | ||
123 | } | ||
124 | } | ||
125 | |||
116 | if let Some((highlight, binding_hash)) = | 126 | if let Some((highlight, binding_hash)) = |
117 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) | 127 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) |
118 | { | 128 | { |
@@ -281,3 +291,44 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
281 | _ => default, | 291 | _ => default, |
282 | } | 292 | } |
283 | } | 293 | } |
294 | |||
295 | fn highlight_injection( | ||
296 | acc: &mut Vec<HighlightedRange>, | ||
297 | sema: &Semantics<RootDatabase>, | ||
298 | literal: ast::RawString, | ||
299 | expanded: SyntaxToken, | ||
300 | ) -> Option<()> { | ||
301 | let call_info = call_info_for_token(&sema, expanded)?; | ||
302 | let idx = call_info.active_parameter?; | ||
303 | let name = call_info.signature.parameter_names.get(idx)?; | ||
304 | if name != "ra_fixture" { | ||
305 | return None; | ||
306 | } | ||
307 | let value = literal.value()?; | ||
308 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | ||
309 | |||
310 | if let Some(range) = literal.open_quote_text_range() { | ||
311 | acc.push(HighlightedRange { | ||
312 | range, | ||
313 | highlight: HighlightTag::LiteralString.into(), | ||
314 | binding_hash: None, | ||
315 | }) | ||
316 | } | ||
317 | |||
318 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
319 | if let Some(r) = literal.map_range_up(h.range) { | ||
320 | h.range = r; | ||
321 | acc.push(h) | ||
322 | } | ||
323 | } | ||
324 | |||
325 | if let Some(range) = literal.close_quote_text_range() { | ||
326 | acc.push(HighlightedRange { | ||
327 | range, | ||
328 | highlight: HighlightTag::LiteralString.into(), | ||
329 | binding_hash: None, | ||
330 | }) | ||
331 | } | ||
332 | |||
333 | Some(()) | ||
334 | } | ||
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index ed8661faf..693b35feb 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -171,6 +171,36 @@ impl RawString { | |||
171 | let inside_str = &text[start_of_inside..end_of_inside]; | 171 | let inside_str = &text[start_of_inside..end_of_inside]; |
172 | Some(inside_str.to_string()) | 172 | Some(inside_str.to_string()) |
173 | } | 173 | } |
174 | |||
175 | pub fn open_quote_text_range(&self) -> Option<TextRange> { | ||
176 | let text = self.text().as_str(); | ||
177 | let usual_string_range = find_usual_string_range(text)?; | ||
178 | |||
179 | let start = self.syntax().text_range().start(); | ||
180 | let len = usual_string_range.start() + TextUnit::of_char('"'); | ||
181 | Some(TextRange::offset_len(start, len)) | ||
182 | } | ||
183 | |||
184 | pub fn close_quote_text_range(&self) -> Option<TextRange> { | ||
185 | let text = self.text().as_str(); | ||
186 | let usual_string_range = find_usual_string_range(text)?; | ||
187 | |||
188 | let end = self.syntax().text_range().end(); | ||
189 | let len = TextUnit::of_str(text) - usual_string_range.end(); | ||
190 | Some(TextRange::from_to(end - len, end)) | ||
191 | } | ||
192 | |||
193 | pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { | ||
194 | // FIXME: handle escapes here properly | ||
195 | let text = self.text().as_str(); | ||
196 | let usual_string_range = find_usual_string_range(text)?; | ||
197 | Some( | ||
198 | range | ||
199 | + self.syntax().text_range().start() | ||
200 | + TextUnit::of_char('"') | ||
201 | + usual_string_range.start(), | ||
202 | ) | ||
203 | } | ||
174 | } | 204 | } |
175 | 205 | ||
176 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | 206 | fn find_usual_string_range(s: &str) -> Option<TextRange> { |