diff options
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/html.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/injection.rs | 188 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tags.rs | 18 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 201 |
4 files changed, 374 insertions, 44 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index edfe61f39..0be55bca9 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
19 | ) | 19 | ) |
20 | } | 20 | } |
21 | 21 | ||
22 | let ranges = highlight(db, file_id, None); | 22 | let ranges = highlight(db, file_id, None, false); |
23 | let text = parse.tree().syntax().to_string(); | 23 | let text = parse.tree().syntax().to_string(); |
24 | let mut prev_pos = TextSize::from(0); | 24 | let mut prev_pos = TextSize::from(0); |
25 | let mut buf = String::new(); | 25 | let mut buf = String::new(); |
@@ -64,11 +64,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
64 | 64 | ||
65 | .lifetime { color: #DFAF8F; font-style: italic; } | 65 | .lifetime { color: #DFAF8F; font-style: italic; } |
66 | .comment { color: #7F9F7F; } | 66 | .comment { color: #7F9F7F; } |
67 | .documentation { color: #629755; } | ||
68 | .injected { opacity: 0.65 ; } | ||
67 | .struct, .enum { color: #7CB8BB; } | 69 | .struct, .enum { color: #7CB8BB; } |
68 | .enum_variant { color: #BDE0F3; } | 70 | .enum_variant { color: #BDE0F3; } |
69 | .string_literal { color: #CC9393; } | 71 | .string_literal { color: #CC9393; } |
70 | .field { color: #94BFF3; } | 72 | .field { color: #94BFF3; } |
71 | .function { color: #93E0E3; } | 73 | .function { color: #93E0E3; } |
74 | .function.unsafe { color: #BC8383; } | ||
75 | .operator.unsafe { color: #BC8383; } | ||
72 | .parameter { color: #94BFF3; } | 76 | .parameter { color: #94BFF3; } |
73 | .text { color: #DCDCCC; } | 77 | .text { color: #DCDCCC; } |
74 | .type { color: #7CB8BB; } | 78 | .type { color: #7CB8BB; } |
@@ -79,12 +83,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
79 | .bool_literal { color: #BFE6EB; } | 83 | .bool_literal { color: #BFE6EB; } |
80 | .macro { color: #94BFF3; } | 84 | .macro { color: #94BFF3; } |
81 | .module { color: #AFD8AF; } | 85 | .module { color: #AFD8AF; } |
86 | .value_param { color: #DCDCCC; } | ||
82 | .variable { color: #DCDCCC; } | 87 | .variable { color: #DCDCCC; } |
83 | .format_specifier { color: #CC696B; } | 88 | .format_specifier { color: #CC696B; } |
84 | .mutable { text-decoration: underline; } | 89 | .mutable { text-decoration: underline; } |
85 | 90 | .escape_sequence { color: #94BFF3; } | |
86 | .keyword { color: #F0DFAF; font-weight: bold; } | 91 | .keyword { color: #F0DFAF; font-weight: bold; } |
87 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 92 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
88 | .control { font-style: italic; } | 93 | .control { font-style: italic; } |
94 | |||
95 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } | ||
89 | </style> | 96 | </style> |
90 | "; | 97 | "; |
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs new file mode 100644 index 000000000..8665b480f --- /dev/null +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -0,0 +1,188 @@ | |||
1 | //! Syntax highlighting injections such as highlighting of documentation tests. | ||
2 | |||
3 | use std::{collections::BTreeMap, convert::TryFrom}; | ||
4 | |||
5 | use ast::{HasQuotes, HasStringValue}; | ||
6 | use hir::Semantics; | ||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | ||
8 | use stdx::SepBy; | ||
9 | |||
10 | use crate::{ | ||
11 | call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag, | ||
12 | HighlightedRange, RootDatabase, | ||
13 | }; | ||
14 | |||
15 | use super::HighlightedRangeStack; | ||
16 | |||
17 | pub(super) fn highlight_injection( | ||
18 | acc: &mut HighlightedRangeStack, | ||
19 | sema: &Semantics<RootDatabase>, | ||
20 | literal: ast::RawString, | ||
21 | expanded: SyntaxToken, | ||
22 | ) -> Option<()> { | ||
23 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | ||
24 | if !active_parameter.name.starts_with("ra_fixture") { | ||
25 | return None; | ||
26 | } | ||
27 | let value = literal.value()?; | ||
28 | let (analysis, tmp_file_id) = Analysis::from_single_file(value.into_owned()); | ||
29 | |||
30 | if let Some(range) = literal.open_quote_text_range() { | ||
31 | acc.add(HighlightedRange { | ||
32 | range, | ||
33 | highlight: HighlightTag::StringLiteral.into(), | ||
34 | binding_hash: None, | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
39 | if let Some(r) = literal.map_range_up(h.range) { | ||
40 | h.range = r; | ||
41 | acc.add(h) | ||
42 | } | ||
43 | } | ||
44 | |||
45 | if let Some(range) = literal.close_quote_text_range() { | ||
46 | acc.add(HighlightedRange { | ||
47 | range, | ||
48 | highlight: HighlightTag::StringLiteral.into(), | ||
49 | binding_hash: None, | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | Some(()) | ||
54 | } | ||
55 | |||
56 | /// Mapping from extracted documentation code to original code | ||
57 | type RangesMap = BTreeMap<TextSize, TextSize>; | ||
58 | |||
59 | const RUSTDOC_FENCE: &'static str = "```"; | ||
60 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = | ||
61 | &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"]; | ||
62 | |||
63 | /// Extracts Rust code from documentation comments as well as a mapping from | ||
64 | /// the extracted source code back to the original source ranges. | ||
65 | /// Lastly, a vector of new comment highlight ranges (spanning only the | ||
66 | /// comment prefix) is returned which is used in the syntax highlighting | ||
67 | /// injection to replace the previous (line-spanning) comment ranges. | ||
68 | pub(super) fn extract_doc_comments( | ||
69 | node: &SyntaxNode, | ||
70 | ) -> Option<(String, RangesMap, Vec<HighlightedRange>)> { | ||
71 | // wrap the doctest into function body to get correct syntax highlighting | ||
72 | let prefix = "fn doctest() {\n"; | ||
73 | let suffix = "}\n"; | ||
74 | // Mapping from extracted documentation code to original code | ||
75 | let mut range_mapping: RangesMap = BTreeMap::new(); | ||
76 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | ||
77 | let mut is_codeblock = false; | ||
78 | let mut is_doctest = false; | ||
79 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | ||
80 | // spanning comment ranges. | ||
81 | let mut new_comments = Vec::new(); | ||
82 | let doctest = node | ||
83 | .children_with_tokens() | ||
84 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | ||
85 | .filter(|comment| comment.kind().doc.is_some()) | ||
86 | .filter(|comment| { | ||
87 | if let Some(idx) = comment.text().find(RUSTDOC_FENCE) { | ||
88 | is_codeblock = !is_codeblock; | ||
89 | // Check whether code is rust by inspecting fence guards | ||
90 | let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; | ||
91 | let is_rust = | ||
92 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | ||
93 | is_doctest = is_codeblock && is_rust; | ||
94 | false | ||
95 | } else { | ||
96 | is_doctest | ||
97 | } | ||
98 | }) | ||
99 | .map(|comment| { | ||
100 | let prefix_len = comment.prefix().len(); | ||
101 | let line: &str = comment.text().as_str(); | ||
102 | let range = comment.syntax().text_range(); | ||
103 | |||
104 | // whitespace after comment is ignored | ||
105 | let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) { | ||
106 | prefix_len + ws.len_utf8() | ||
107 | } else { | ||
108 | prefix_len | ||
109 | }; | ||
110 | |||
111 | // lines marked with `#` should be ignored in output, we skip the `#` char | ||
112 | let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') { | ||
113 | pos + ws.len_utf8() | ||
114 | } else { | ||
115 | pos | ||
116 | }; | ||
117 | |||
118 | range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap()); | ||
119 | new_comments.push(HighlightedRange { | ||
120 | range: TextRange::new( | ||
121 | range.start(), | ||
122 | range.start() + TextSize::try_from(pos).unwrap(), | ||
123 | ), | ||
124 | highlight: HighlightTag::Comment | HighlightModifier::Documentation, | ||
125 | binding_hash: None, | ||
126 | }); | ||
127 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | ||
128 | line_start += TextSize::try_from('\n'.len_utf8()).unwrap(); | ||
129 | |||
130 | line[pos..].to_owned() | ||
131 | }) | ||
132 | .sep_by("\n") | ||
133 | .to_string(); | ||
134 | |||
135 | if doctest.is_empty() { | ||
136 | return None; | ||
137 | } | ||
138 | |||
139 | let doctest = format!("{}{}{}", prefix, doctest, suffix); | ||
140 | Some((doctest, range_mapping, new_comments)) | ||
141 | } | ||
142 | |||
143 | /// Injection of syntax highlighting of doctests. | ||
144 | pub(super) fn highlight_doc_comment( | ||
145 | text: String, | ||
146 | range_mapping: RangesMap, | ||
147 | new_comments: Vec<HighlightedRange>, | ||
148 | stack: &mut HighlightedRangeStack, | ||
149 | ) { | ||
150 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | ||
151 | |||
152 | stack.push(); | ||
153 | for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { | ||
154 | // Determine start offset and end offset in case of multi-line ranges | ||
155 | let mut start_offset = None; | ||
156 | let mut end_offset = None; | ||
157 | for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() { | ||
158 | // It's possible for orig_line_start - line_start to be negative. Add h.range.start() | ||
159 | // here and remove it from the end range after the loop below so that the values are | ||
160 | // always non-negative. | ||
161 | let offset = h.range.start() + orig_line_start - line_start; | ||
162 | if line_start <= &h.range.start() { | ||
163 | start_offset.get_or_insert(offset); | ||
164 | break; | ||
165 | } else { | ||
166 | end_offset.get_or_insert(offset); | ||
167 | } | ||
168 | } | ||
169 | if let Some(start_offset) = start_offset { | ||
170 | h.range = TextRange::new( | ||
171 | start_offset, | ||
172 | h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(), | ||
173 | ); | ||
174 | |||
175 | h.highlight |= HighlightModifier::Injected; | ||
176 | stack.add(h); | ||
177 | } | ||
178 | } | ||
179 | |||
180 | // Inject the comment prefix highlight ranges | ||
181 | stack.push(); | ||
182 | for comment in new_comments { | ||
183 | stack.add(comment); | ||
184 | } | ||
185 | stack.pop_and_inject(None); | ||
186 | stack | ||
187 | .pop_and_inject(Some(Highlight::from(HighlightTag::Generic) | HighlightModifier::Injected)); | ||
188 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 1514531de..49ec94bdc 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
@@ -23,13 +23,16 @@ pub enum HighlightTag { | |||
23 | Constant, | 23 | Constant, |
24 | Enum, | 24 | Enum, |
25 | EnumVariant, | 25 | EnumVariant, |
26 | EscapeSequence, | ||
26 | Field, | 27 | Field, |
27 | Function, | 28 | Function, |
29 | Generic, | ||
28 | Keyword, | 30 | Keyword, |
29 | Lifetime, | 31 | Lifetime, |
30 | Macro, | 32 | Macro, |
31 | Module, | 33 | Module, |
32 | NumericLiteral, | 34 | NumericLiteral, |
35 | Punctuation, | ||
33 | SelfKeyword, | 36 | SelfKeyword, |
34 | SelfType, | 37 | SelfType, |
35 | Static, | 38 | Static, |
@@ -39,6 +42,7 @@ pub enum HighlightTag { | |||
39 | TypeAlias, | 42 | TypeAlias, |
40 | TypeParam, | 43 | TypeParam, |
41 | Union, | 44 | Union, |
45 | ValueParam, | ||
42 | Local, | 46 | Local, |
43 | UnresolvedReference, | 47 | UnresolvedReference, |
44 | FormatSpecifier, | 48 | FormatSpecifier, |
@@ -55,6 +59,8 @@ pub enum HighlightModifier { | |||
55 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | 59 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is |
56 | /// not. | 60 | /// not. |
57 | Definition, | 61 | Definition, |
62 | Documentation, | ||
63 | Injected, | ||
58 | Mutable, | 64 | Mutable, |
59 | Unsafe, | 65 | Unsafe, |
60 | } | 66 | } |
@@ -71,13 +77,18 @@ impl HighlightTag { | |||
71 | HighlightTag::Constant => "constant", | 77 | HighlightTag::Constant => "constant", |
72 | HighlightTag::Enum => "enum", | 78 | HighlightTag::Enum => "enum", |
73 | HighlightTag::EnumVariant => "enum_variant", | 79 | HighlightTag::EnumVariant => "enum_variant", |
80 | HighlightTag::EscapeSequence => "escape_sequence", | ||
74 | HighlightTag::Field => "field", | 81 | HighlightTag::Field => "field", |
82 | HighlightTag::FormatSpecifier => "format_specifier", | ||
75 | HighlightTag::Function => "function", | 83 | HighlightTag::Function => "function", |
84 | HighlightTag::Generic => "generic", | ||
76 | HighlightTag::Keyword => "keyword", | 85 | HighlightTag::Keyword => "keyword", |
77 | HighlightTag::Lifetime => "lifetime", | 86 | HighlightTag::Lifetime => "lifetime", |
87 | HighlightTag::Punctuation => "punctuation", | ||
78 | HighlightTag::Macro => "macro", | 88 | HighlightTag::Macro => "macro", |
79 | HighlightTag::Module => "module", | 89 | HighlightTag::Module => "module", |
80 | HighlightTag::NumericLiteral => "numeric_literal", | 90 | HighlightTag::NumericLiteral => "numeric_literal", |
91 | HighlightTag::Operator => "operator", | ||
81 | HighlightTag::SelfKeyword => "self_keyword", | 92 | HighlightTag::SelfKeyword => "self_keyword", |
82 | HighlightTag::SelfType => "self_type", | 93 | HighlightTag::SelfType => "self_type", |
83 | HighlightTag::Static => "static", | 94 | HighlightTag::Static => "static", |
@@ -87,10 +98,9 @@ impl HighlightTag { | |||
87 | HighlightTag::TypeAlias => "type_alias", | 98 | HighlightTag::TypeAlias => "type_alias", |
88 | HighlightTag::TypeParam => "type_param", | 99 | HighlightTag::TypeParam => "type_param", |
89 | HighlightTag::Union => "union", | 100 | HighlightTag::Union => "union", |
101 | HighlightTag::ValueParam => "value_param", | ||
90 | HighlightTag::Local => "variable", | 102 | HighlightTag::Local => "variable", |
91 | HighlightTag::UnresolvedReference => "unresolved_reference", | 103 | HighlightTag::UnresolvedReference => "unresolved_reference", |
92 | HighlightTag::FormatSpecifier => "format_specifier", | ||
93 | HighlightTag::Operator => "operator", | ||
94 | } | 104 | } |
95 | } | 105 | } |
96 | } | 106 | } |
@@ -106,6 +116,8 @@ impl HighlightModifier { | |||
106 | HighlightModifier::Attribute, | 116 | HighlightModifier::Attribute, |
107 | HighlightModifier::ControlFlow, | 117 | HighlightModifier::ControlFlow, |
108 | HighlightModifier::Definition, | 118 | HighlightModifier::Definition, |
119 | HighlightModifier::Documentation, | ||
120 | HighlightModifier::Injected, | ||
109 | HighlightModifier::Mutable, | 121 | HighlightModifier::Mutable, |
110 | HighlightModifier::Unsafe, | 122 | HighlightModifier::Unsafe, |
111 | ]; | 123 | ]; |
@@ -115,6 +127,8 @@ impl HighlightModifier { | |||
115 | HighlightModifier::Attribute => "attribute", | 127 | HighlightModifier::Attribute => "attribute", |
116 | HighlightModifier::ControlFlow => "control", | 128 | HighlightModifier::ControlFlow => "control", |
117 | HighlightModifier::Definition => "declaration", | 129 | HighlightModifier::Definition => "declaration", |
130 | HighlightModifier::Documentation => "documentation", | ||
131 | HighlightModifier::Injected => "injected", | ||
118 | HighlightModifier::Mutable => "mutable", | 132 | HighlightModifier::Mutable => "mutable", |
119 | HighlightModifier::Unsafe => "unsafe", | 133 | HighlightModifier::Unsafe => "unsafe", |
120 | } | 134 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index eb43a23da..87a6e2523 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -1,15 +1,13 @@ | |||
1 | use std::fs; | 1 | use std::fs; |
2 | 2 | ||
3 | use test_utils::{assert_eq_text, project_dir, read_text}; | 3 | use expect::{expect_file, ExpectFile}; |
4 | use test_utils::project_dir; | ||
4 | 5 | ||
5 | use crate::{ | 6 | use crate::{mock_analysis::single_file, FileRange, TextRange}; |
6 | mock_analysis::{single_file, MockAnalysis}, | ||
7 | FileRange, TextRange, | ||
8 | }; | ||
9 | 7 | ||
10 | #[test] | 8 | #[test] |
11 | fn test_highlighting() { | 9 | fn test_highlighting() { |
12 | let (analysis, file_id) = single_file( | 10 | check_highlighting( |
13 | r#" | 11 | r#" |
14 | #[derive(Clone, Debug)] | 12 | #[derive(Clone, Debug)] |
15 | struct Foo { | 13 | struct Foo { |
@@ -27,6 +25,16 @@ impl Bar for Foo { | |||
27 | } | 25 | } |
28 | } | 26 | } |
29 | 27 | ||
28 | impl Foo { | ||
29 | fn baz(mut self) -> i32 { | ||
30 | self.x | ||
31 | } | ||
32 | |||
33 | fn qux(&mut self) { | ||
34 | self.x = 0; | ||
35 | } | ||
36 | } | ||
37 | |||
30 | static mut STATIC_MUT: i32 = 0; | 38 | static mut STATIC_MUT: i32 = 0; |
31 | 39 | ||
32 | fn foo<'a, T>() -> T { | 40 | fn foo<'a, T>() -> T { |
@@ -43,6 +51,12 @@ def_fn! { | |||
43 | } | 51 | } |
44 | } | 52 | } |
45 | 53 | ||
54 | macro_rules! noop { | ||
55 | ($expr:expr) => { | ||
56 | $expr | ||
57 | } | ||
58 | } | ||
59 | |||
46 | // comment | 60 | // comment |
47 | fn main() { | 61 | fn main() { |
48 | println!("Hello, {}!", 92); | 62 | println!("Hello, {}!", 92); |
@@ -61,10 +75,14 @@ fn main() { | |||
61 | // Do nothing | 75 | // Do nothing |
62 | } | 76 | } |
63 | 77 | ||
78 | noop!(noop!(1)); | ||
79 | |||
64 | let mut x = 42; | 80 | let mut x = 42; |
65 | let y = &mut x; | 81 | let y = &mut x; |
66 | let z = &y; | 82 | let z = &y; |
67 | 83 | ||
84 | let Foo { x: z, y } = Foo { x: z, y }; | ||
85 | |||
68 | y; | 86 | y; |
69 | } | 87 | } |
70 | 88 | ||
@@ -84,17 +102,14 @@ impl<T> Option<T> { | |||
84 | } | 102 | } |
85 | "# | 103 | "# |
86 | .trim(), | 104 | .trim(), |
105 | expect_file!["crates/ra_ide/test_data/highlighting.html"], | ||
106 | false, | ||
87 | ); | 107 | ); |
88 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html"); | ||
89 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
90 | let expected_html = &read_text(&dst_file); | ||
91 | fs::write(dst_file, &actual_html).unwrap(); | ||
92 | assert_eq_text!(expected_html, actual_html); | ||
93 | } | 108 | } |
94 | 109 | ||
95 | #[test] | 110 | #[test] |
96 | fn test_rainbow_highlighting() { | 111 | fn test_rainbow_highlighting() { |
97 | let (analysis, file_id) = single_file( | 112 | check_highlighting( |
98 | r#" | 113 | r#" |
99 | fn main() { | 114 | fn main() { |
100 | let hello = "hello"; | 115 | let hello = "hello"; |
@@ -110,12 +125,9 @@ fn bar() { | |||
110 | } | 125 | } |
111 | "# | 126 | "# |
112 | .trim(), | 127 | .trim(), |
128 | expect_file!["crates/ra_ide/test_data/rainbow_highlighting.html"], | ||
129 | true, | ||
113 | ); | 130 | ); |
114 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); | ||
115 | let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); | ||
116 | let expected_html = &read_text(&dst_file); | ||
117 | fs::write(dst_file, &actual_html).unwrap(); | ||
118 | assert_eq_text!(expected_html, actual_html); | ||
119 | } | 131 | } |
120 | 132 | ||
121 | #[test] | 133 | #[test] |
@@ -123,12 +135,10 @@ fn accidentally_quadratic() { | |||
123 | let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic"); | 135 | let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic"); |
124 | let src = fs::read_to_string(file).unwrap(); | 136 | let src = fs::read_to_string(file).unwrap(); |
125 | 137 | ||
126 | let mut mock = MockAnalysis::new(); | 138 | let (analysis, file_id) = single_file(&src); |
127 | let file_id = mock.add_file("/main.rs", &src); | ||
128 | let host = mock.analysis_host(); | ||
129 | 139 | ||
130 | // let t = std::time::Instant::now(); | 140 | // let t = std::time::Instant::now(); |
131 | let _ = host.analysis().highlight(file_id).unwrap(); | 141 | let _ = analysis.highlight(file_id).unwrap(); |
132 | // eprintln!("elapsed: {:?}", t.elapsed()); | 142 | // eprintln!("elapsed: {:?}", t.elapsed()); |
133 | } | 143 | } |
134 | 144 | ||
@@ -136,16 +146,17 @@ fn accidentally_quadratic() { | |||
136 | fn test_ranges() { | 146 | fn test_ranges() { |
137 | let (analysis, file_id) = single_file( | 147 | let (analysis, file_id) = single_file( |
138 | r#" | 148 | r#" |
139 | #[derive(Clone, Debug)] | 149 | #[derive(Clone, Debug)] |
140 | struct Foo { | 150 | struct Foo { |
141 | pub x: i32, | 151 | pub x: i32, |
142 | pub y: i32, | 152 | pub y: i32, |
143 | }"#, | 153 | } |
154 | "#, | ||
144 | ); | 155 | ); |
145 | 156 | ||
146 | // The "x" | 157 | // The "x" |
147 | let highlights = &analysis | 158 | let highlights = &analysis |
148 | .highlight_range(FileRange { file_id, range: TextRange::at(82.into(), 1.into()) }) | 159 | .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) }) |
149 | .unwrap(); | 160 | .unwrap(); |
150 | 161 | ||
151 | assert_eq!(&highlights[0].highlight.to_string(), "field.declaration"); | 162 | assert_eq!(&highlights[0].highlight.to_string(), "field.declaration"); |
@@ -153,7 +164,7 @@ fn test_ranges() { | |||
153 | 164 | ||
154 | #[test] | 165 | #[test] |
155 | fn test_flattening() { | 166 | fn test_flattening() { |
156 | let (analysis, file_id) = single_file( | 167 | check_highlighting( |
157 | r##" | 168 | r##" |
158 | fn fixture(ra_fixture: &str) {} | 169 | fn fixture(ra_fixture: &str) {} |
159 | 170 | ||
@@ -167,13 +178,9 @@ fn main() { | |||
167 | ); | 178 | ); |
168 | }"## | 179 | }"## |
169 | .trim(), | 180 | .trim(), |
181 | expect_file!["crates/ra_ide/test_data/highlight_injection.html"], | ||
182 | false, | ||
170 | ); | 183 | ); |
171 | |||
172 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html"); | ||
173 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
174 | let expected_html = &read_text(&dst_file); | ||
175 | fs::write(dst_file, &actual_html).unwrap(); | ||
176 | assert_eq_text!(expected_html, actual_html); | ||
177 | } | 184 | } |
178 | 185 | ||
179 | #[test] | 186 | #[test] |
@@ -192,7 +199,7 @@ macro_rules! test {} | |||
192 | fn test_string_highlighting() { | 199 | fn test_string_highlighting() { |
193 | // The format string detection is based on macro-expansion, | 200 | // The format string detection is based on macro-expansion, |
194 | // thus, we have to copy the macro definition from `std` | 201 | // thus, we have to copy the macro definition from `std` |
195 | let (analysis, file_id) = single_file( | 202 | check_highlighting( |
196 | r#" | 203 | r#" |
197 | macro_rules! println { | 204 | macro_rules! println { |
198 | ($($arg:tt)*) => ({ | 205 | ($($arg:tt)*) => ({ |
@@ -218,6 +225,7 @@ fn main() { | |||
218 | println!("{argument}", argument = "test"); // => "test" | 225 | println!("{argument}", argument = "test"); // => "test" |
219 | println!("{name} {}", 1, name = 2); // => "2 1" | 226 | println!("{name} {}", 1, name = 2); // => "2 1" |
220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | 227 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" |
228 | println!("{{{}}}", 2); // => "{2}" | ||
221 | println!("Hello {:5}!", "x"); | 229 | println!("Hello {:5}!", "x"); |
222 | println!("Hello {:1$}!", "x", 5); | 230 | println!("Hello {:1$}!", "x", 5); |
223 | println!("Hello {1:0$}!", 5, "x"); | 231 | println!("Hello {1:0$}!", 5, "x"); |
@@ -245,15 +253,128 @@ fn main() { | |||
245 | 253 | ||
246 | println!(r"Hello, {}!", "world"); | 254 | println!(r"Hello, {}!", "world"); |
247 | 255 | ||
256 | // escape sequences | ||
257 | println!("Hello\nWorld"); | ||
258 | println!("\u{48}\x65\x6C\x6C\x6F World"); | ||
259 | |||
248 | println!("{\x41}", A = 92); | 260 | println!("{\x41}", A = 92); |
249 | println!("{ничоси}", ничоси = 92); | 261 | println!("{ничоси}", ничоси = 92); |
250 | }"# | 262 | }"# |
251 | .trim(), | 263 | .trim(), |
264 | expect_file!["crates/ra_ide/test_data/highlight_strings.html"], | ||
265 | false, | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn test_unsafe_highlighting() { | ||
271 | check_highlighting( | ||
272 | r#" | ||
273 | unsafe fn unsafe_fn() {} | ||
274 | |||
275 | struct HasUnsafeFn; | ||
276 | |||
277 | impl HasUnsafeFn { | ||
278 | unsafe fn unsafe_method(&self) {} | ||
279 | } | ||
280 | |||
281 | fn main() { | ||
282 | let x = &5 as *const usize; | ||
283 | unsafe { | ||
284 | unsafe_fn(); | ||
285 | HasUnsafeFn.unsafe_method(); | ||
286 | let y = *(x); | ||
287 | let z = -x; | ||
288 | } | ||
289 | } | ||
290 | "# | ||
291 | .trim(), | ||
292 | expect_file!["crates/ra_ide/test_data/highlight_unsafe.html"], | ||
293 | false, | ||
294 | ); | ||
295 | } | ||
296 | |||
297 | #[test] | ||
298 | fn test_highlight_doctest() { | ||
299 | check_highlighting( | ||
300 | r#" | ||
301 | /// ``` | ||
302 | /// let _ = "early doctests should not go boom"; | ||
303 | /// ``` | ||
304 | struct Foo { | ||
305 | bar: bool, | ||
306 | } | ||
307 | |||
308 | impl Foo { | ||
309 | pub const bar: bool = true; | ||
310 | |||
311 | /// Constructs a new `Foo`. | ||
312 | /// | ||
313 | /// # Examples | ||
314 | /// | ||
315 | /// ``` | ||
316 | /// # #![allow(unused_mut)] | ||
317 | /// let mut foo: Foo = Foo::new(); | ||
318 | /// ``` | ||
319 | pub const fn new() -> Foo { | ||
320 | Foo { bar: true } | ||
321 | } | ||
322 | |||
323 | /// `bar` method on `Foo`. | ||
324 | /// | ||
325 | /// # Examples | ||
326 | /// | ||
327 | /// ``` | ||
328 | /// use x::y; | ||
329 | /// | ||
330 | /// let foo = Foo::new(); | ||
331 | /// | ||
332 | /// // calls bar on foo | ||
333 | /// assert!(foo.bar()); | ||
334 | /// | ||
335 | /// let bar = foo.bar || Foo::bar; | ||
336 | /// | ||
337 | /// /* multi-line | ||
338 | /// comment */ | ||
339 | /// | ||
340 | /// let multi_line_string = "Foo | ||
341 | /// bar | ||
342 | /// "; | ||
343 | /// | ||
344 | /// ``` | ||
345 | /// | ||
346 | /// ```rust,no_run | ||
347 | /// let foobar = Foo::new().bar(); | ||
348 | /// ``` | ||
349 | /// | ||
350 | /// ```sh | ||
351 | /// echo 1 | ||
352 | /// ``` | ||
353 | pub fn foo(&self) -> bool { | ||
354 | true | ||
355 | } | ||
356 | } | ||
357 | |||
358 | /// ``` | ||
359 | /// noop!(1); | ||
360 | /// ``` | ||
361 | macro_rules! noop { | ||
362 | ($expr:expr) => { | ||
363 | $expr | ||
364 | } | ||
365 | } | ||
366 | "# | ||
367 | .trim(), | ||
368 | expect_file!["crates/ra_ide/test_data/highlight_doctest.html"], | ||
369 | false, | ||
252 | ); | 370 | ); |
371 | } | ||
253 | 372 | ||
254 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); | 373 | /// Highlights the code given by the `ra_fixture` argument, renders the |
255 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | 374 | /// result as HTML, and compares it with the HTML file given as `snapshot`. |
256 | let expected_html = &read_text(&dst_file); | 375 | /// Note that the `snapshot` file is overwritten by the rendered HTML. |
257 | fs::write(dst_file, &actual_html).unwrap(); | 376 | fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) { |
258 | assert_eq_text!(expected_html, actual_html); | 377 | let (analysis, file_id) = single_file(ra_fixture); |
378 | let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); | ||
379 | expect.assert_eq(actual_html) | ||
259 | } | 380 | } |