diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/ra_ide/src/syntax_highlighting | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/html.rs | 97 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/injection.rs | 188 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tags.rs | 203 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 380 |
4 files changed, 0 insertions, 868 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs deleted file mode 100644 index a5e7d2867..000000000 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ /dev/null | |||
@@ -1,97 +0,0 @@ | |||
1 | //! Renders a bit of code as HTML. | ||
2 | |||
3 | use oorandom::Rand32; | ||
4 | use ra_db::SourceDatabase; | ||
5 | use ra_syntax::{AstNode, TextRange, TextSize}; | ||
6 | |||
7 | use crate::{syntax_highlighting::highlight, FileId, RootDatabase}; | ||
8 | |||
9 | pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { | ||
10 | let parse = db.parse(file_id); | ||
11 | |||
12 | fn rainbowify(seed: u64) -> String { | ||
13 | let mut rng = Rand32::new(seed); | ||
14 | format!( | ||
15 | "hsl({h},{s}%,{l}%)", | ||
16 | h = rng.rand_range(0..361), | ||
17 | s = rng.rand_range(42..99), | ||
18 | l = rng.rand_range(40..91), | ||
19 | ) | ||
20 | } | ||
21 | |||
22 | let ranges = highlight(db, file_id, None, false); | ||
23 | let text = parse.tree().syntax().to_string(); | ||
24 | let mut prev_pos = TextSize::from(0); | ||
25 | let mut buf = String::new(); | ||
26 | buf.push_str(&STYLE); | ||
27 | buf.push_str("<pre><code>"); | ||
28 | for range in &ranges { | ||
29 | if range.range.start() > prev_pos { | ||
30 | let curr = &text[TextRange::new(prev_pos, range.range.start())]; | ||
31 | let text = html_escape(curr); | ||
32 | buf.push_str(&text); | ||
33 | } | ||
34 | let curr = &text[TextRange::new(range.range.start(), range.range.end())]; | ||
35 | |||
36 | let class = range.highlight.to_string().replace('.', " "); | ||
37 | let color = match (rainbow, range.binding_hash) { | ||
38 | (true, Some(hash)) => { | ||
39 | format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash)) | ||
40 | } | ||
41 | _ => "".into(), | ||
42 | }; | ||
43 | buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr))); | ||
44 | |||
45 | prev_pos = range.range.end(); | ||
46 | } | ||
47 | // Add the remaining (non-highlighted) text | ||
48 | let curr = &text[TextRange::new(prev_pos, TextSize::of(&text))]; | ||
49 | let text = html_escape(curr); | ||
50 | buf.push_str(&text); | ||
51 | buf.push_str("</code></pre>"); | ||
52 | buf | ||
53 | } | ||
54 | |||
55 | //FIXME: like, real html escaping | ||
56 | fn html_escape(text: &str) -> String { | ||
57 | text.replace("<", "<").replace(">", ">") | ||
58 | } | ||
59 | |||
60 | const STYLE: &str = " | ||
61 | <style> | ||
62 | body { margin: 0; } | ||
63 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
64 | |||
65 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
66 | .comment { color: #7F9F7F; } | ||
67 | .documentation { color: #629755; } | ||
68 | .injected { opacity: 0.65 ; } | ||
69 | .struct, .enum { color: #7CB8BB; } | ||
70 | .enum_variant { color: #BDE0F3; } | ||
71 | .string_literal { color: #CC9393; } | ||
72 | .field { color: #94BFF3; } | ||
73 | .function { color: #93E0E3; } | ||
74 | .function.unsafe { color: #BC8383; } | ||
75 | .operator.unsafe { color: #BC8383; } | ||
76 | .parameter { color: #94BFF3; } | ||
77 | .text { color: #DCDCCC; } | ||
78 | .type { color: #7CB8BB; } | ||
79 | .builtin_type { color: #8CD0D3; } | ||
80 | .type_param { color: #DFAF8F; } | ||
81 | .attribute { color: #94BFF3; } | ||
82 | .numeric_literal { color: #BFEBBF; } | ||
83 | .bool_literal { color: #BFE6EB; } | ||
84 | .macro { color: #94BFF3; } | ||
85 | .module { color: #AFD8AF; } | ||
86 | .value_param { color: #DCDCCC; } | ||
87 | .variable { color: #DCDCCC; } | ||
88 | .format_specifier { color: #CC696B; } | ||
89 | .mutable { text-decoration: underline; } | ||
90 | .escape_sequence { color: #94BFF3; } | ||
91 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
92 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
93 | .control { font-style: italic; } | ||
94 | |||
95 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } | ||
96 | </style> | ||
97 | "; | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs deleted file mode 100644 index 8665b480f..000000000 --- a/crates/ra_ide/src/syntax_highlighting/injection.rs +++ /dev/null | |||
@@ -1,188 +0,0 @@ | |||
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 deleted file mode 100644 index 49ec94bdc..000000000 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ /dev/null | |||
@@ -1,203 +0,0 @@ | |||
1 | //! Defines token tags we use for syntax highlighting. | ||
2 | //! A tag is not unlike a CSS class. | ||
3 | |||
4 | use std::{fmt, ops}; | ||
5 | |||
6 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
7 | pub struct Highlight { | ||
8 | pub tag: HighlightTag, | ||
9 | pub modifiers: HighlightModifiers, | ||
10 | } | ||
11 | |||
12 | #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
13 | pub struct HighlightModifiers(u32); | ||
14 | |||
15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
16 | pub enum HighlightTag { | ||
17 | Attribute, | ||
18 | BoolLiteral, | ||
19 | BuiltinType, | ||
20 | ByteLiteral, | ||
21 | CharLiteral, | ||
22 | Comment, | ||
23 | Constant, | ||
24 | Enum, | ||
25 | EnumVariant, | ||
26 | EscapeSequence, | ||
27 | Field, | ||
28 | Function, | ||
29 | Generic, | ||
30 | Keyword, | ||
31 | Lifetime, | ||
32 | Macro, | ||
33 | Module, | ||
34 | NumericLiteral, | ||
35 | Punctuation, | ||
36 | SelfKeyword, | ||
37 | SelfType, | ||
38 | Static, | ||
39 | StringLiteral, | ||
40 | Struct, | ||
41 | Trait, | ||
42 | TypeAlias, | ||
43 | TypeParam, | ||
44 | Union, | ||
45 | ValueParam, | ||
46 | Local, | ||
47 | UnresolvedReference, | ||
48 | FormatSpecifier, | ||
49 | Operator, | ||
50 | } | ||
51 | |||
52 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
53 | #[repr(u8)] | ||
54 | pub enum HighlightModifier { | ||
55 | /// Used to differentiate individual elements within attributes. | ||
56 | Attribute = 0, | ||
57 | /// Used with keywords like `if` and `break`. | ||
58 | ControlFlow, | ||
59 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | ||
60 | /// not. | ||
61 | Definition, | ||
62 | Documentation, | ||
63 | Injected, | ||
64 | Mutable, | ||
65 | Unsafe, | ||
66 | } | ||
67 | |||
68 | impl HighlightTag { | ||
69 | fn as_str(self) -> &'static str { | ||
70 | match self { | ||
71 | HighlightTag::Attribute => "attribute", | ||
72 | HighlightTag::BoolLiteral => "bool_literal", | ||
73 | HighlightTag::BuiltinType => "builtin_type", | ||
74 | HighlightTag::ByteLiteral => "byte_literal", | ||
75 | HighlightTag::CharLiteral => "char_literal", | ||
76 | HighlightTag::Comment => "comment", | ||
77 | HighlightTag::Constant => "constant", | ||
78 | HighlightTag::Enum => "enum", | ||
79 | HighlightTag::EnumVariant => "enum_variant", | ||
80 | HighlightTag::EscapeSequence => "escape_sequence", | ||
81 | HighlightTag::Field => "field", | ||
82 | HighlightTag::FormatSpecifier => "format_specifier", | ||
83 | HighlightTag::Function => "function", | ||
84 | HighlightTag::Generic => "generic", | ||
85 | HighlightTag::Keyword => "keyword", | ||
86 | HighlightTag::Lifetime => "lifetime", | ||
87 | HighlightTag::Punctuation => "punctuation", | ||
88 | HighlightTag::Macro => "macro", | ||
89 | HighlightTag::Module => "module", | ||
90 | HighlightTag::NumericLiteral => "numeric_literal", | ||
91 | HighlightTag::Operator => "operator", | ||
92 | HighlightTag::SelfKeyword => "self_keyword", | ||
93 | HighlightTag::SelfType => "self_type", | ||
94 | HighlightTag::Static => "static", | ||
95 | HighlightTag::StringLiteral => "string_literal", | ||
96 | HighlightTag::Struct => "struct", | ||
97 | HighlightTag::Trait => "trait", | ||
98 | HighlightTag::TypeAlias => "type_alias", | ||
99 | HighlightTag::TypeParam => "type_param", | ||
100 | HighlightTag::Union => "union", | ||
101 | HighlightTag::ValueParam => "value_param", | ||
102 | HighlightTag::Local => "variable", | ||
103 | HighlightTag::UnresolvedReference => "unresolved_reference", | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | impl fmt::Display for HighlightTag { | ||
109 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
110 | fmt::Display::fmt(self.as_str(), f) | ||
111 | } | ||
112 | } | ||
113 | |||
114 | impl HighlightModifier { | ||
115 | const ALL: &'static [HighlightModifier] = &[ | ||
116 | HighlightModifier::Attribute, | ||
117 | HighlightModifier::ControlFlow, | ||
118 | HighlightModifier::Definition, | ||
119 | HighlightModifier::Documentation, | ||
120 | HighlightModifier::Injected, | ||
121 | HighlightModifier::Mutable, | ||
122 | HighlightModifier::Unsafe, | ||
123 | ]; | ||
124 | |||
125 | fn as_str(self) -> &'static str { | ||
126 | match self { | ||
127 | HighlightModifier::Attribute => "attribute", | ||
128 | HighlightModifier::ControlFlow => "control", | ||
129 | HighlightModifier::Definition => "declaration", | ||
130 | HighlightModifier::Documentation => "documentation", | ||
131 | HighlightModifier::Injected => "injected", | ||
132 | HighlightModifier::Mutable => "mutable", | ||
133 | HighlightModifier::Unsafe => "unsafe", | ||
134 | } | ||
135 | } | ||
136 | |||
137 | fn mask(self) -> u32 { | ||
138 | 1 << (self as u32) | ||
139 | } | ||
140 | } | ||
141 | |||
142 | impl fmt::Display for HighlightModifier { | ||
143 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
144 | fmt::Display::fmt(self.as_str(), f) | ||
145 | } | ||
146 | } | ||
147 | |||
148 | impl fmt::Display for Highlight { | ||
149 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
150 | write!(f, "{}", self.tag)?; | ||
151 | for modifier in self.modifiers.iter() { | ||
152 | write!(f, ".{}", modifier)? | ||
153 | } | ||
154 | Ok(()) | ||
155 | } | ||
156 | } | ||
157 | |||
158 | impl From<HighlightTag> for Highlight { | ||
159 | fn from(tag: HighlightTag) -> Highlight { | ||
160 | Highlight::new(tag) | ||
161 | } | ||
162 | } | ||
163 | |||
164 | impl Highlight { | ||
165 | pub(crate) fn new(tag: HighlightTag) -> Highlight { | ||
166 | Highlight { tag, modifiers: HighlightModifiers::default() } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | impl ops::BitOr<HighlightModifier> for HighlightTag { | ||
171 | type Output = Highlight; | ||
172 | |||
173 | fn bitor(self, rhs: HighlightModifier) -> Highlight { | ||
174 | Highlight::new(self) | rhs | ||
175 | } | ||
176 | } | ||
177 | |||
178 | impl ops::BitOrAssign<HighlightModifier> for HighlightModifiers { | ||
179 | fn bitor_assign(&mut self, rhs: HighlightModifier) { | ||
180 | self.0 |= rhs.mask(); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | impl ops::BitOrAssign<HighlightModifier> for Highlight { | ||
185 | fn bitor_assign(&mut self, rhs: HighlightModifier) { | ||
186 | self.modifiers |= rhs; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | impl ops::BitOr<HighlightModifier> for Highlight { | ||
191 | type Output = Highlight; | ||
192 | |||
193 | fn bitor(mut self, rhs: HighlightModifier) -> Highlight { | ||
194 | self |= rhs; | ||
195 | self | ||
196 | } | ||
197 | } | ||
198 | |||
199 | impl HighlightModifiers { | ||
200 | pub fn iter(self) -> impl Iterator<Item = HighlightModifier> { | ||
201 | HighlightModifier::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask()) | ||
202 | } | ||
203 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs deleted file mode 100644 index 87a6e2523..000000000 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ /dev/null | |||
@@ -1,380 +0,0 @@ | |||
1 | use std::fs; | ||
2 | |||
3 | use expect::{expect_file, ExpectFile}; | ||
4 | use test_utils::project_dir; | ||
5 | |||
6 | use crate::{mock_analysis::single_file, FileRange, TextRange}; | ||
7 | |||
8 | #[test] | ||
9 | fn test_highlighting() { | ||
10 | check_highlighting( | ||
11 | r#" | ||
12 | #[derive(Clone, Debug)] | ||
13 | struct Foo { | ||
14 | pub x: i32, | ||
15 | pub y: i32, | ||
16 | } | ||
17 | |||
18 | trait Bar { | ||
19 | fn bar(&self) -> i32; | ||
20 | } | ||
21 | |||
22 | impl Bar for Foo { | ||
23 | fn bar(&self) -> i32 { | ||
24 | self.x | ||
25 | } | ||
26 | } | ||
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 | |||
38 | static mut STATIC_MUT: i32 = 0; | ||
39 | |||
40 | fn foo<'a, T>() -> T { | ||
41 | foo::<'a, i32>() | ||
42 | } | ||
43 | |||
44 | macro_rules! def_fn { | ||
45 | ($($tt:tt)*) => {$($tt)*} | ||
46 | } | ||
47 | |||
48 | def_fn! { | ||
49 | fn bar() -> u32 { | ||
50 | 100 | ||
51 | } | ||
52 | } | ||
53 | |||
54 | macro_rules! noop { | ||
55 | ($expr:expr) => { | ||
56 | $expr | ||
57 | } | ||
58 | } | ||
59 | |||
60 | // comment | ||
61 | fn main() { | ||
62 | println!("Hello, {}!", 92); | ||
63 | |||
64 | let mut vec = Vec::new(); | ||
65 | if true { | ||
66 | let x = 92; | ||
67 | vec.push(Foo { x, y: 1 }); | ||
68 | } | ||
69 | unsafe { | ||
70 | vec.set_len(0); | ||
71 | STATIC_MUT = 1; | ||
72 | } | ||
73 | |||
74 | for e in vec { | ||
75 | // Do nothing | ||
76 | } | ||
77 | |||
78 | noop!(noop!(1)); | ||
79 | |||
80 | let mut x = 42; | ||
81 | let y = &mut x; | ||
82 | let z = &y; | ||
83 | |||
84 | let Foo { x: z, y } = Foo { x: z, y }; | ||
85 | |||
86 | y; | ||
87 | } | ||
88 | |||
89 | enum Option<T> { | ||
90 | Some(T), | ||
91 | None, | ||
92 | } | ||
93 | use Option::*; | ||
94 | |||
95 | impl<T> Option<T> { | ||
96 | fn and<U>(self, other: Option<U>) -> Option<(T, U)> { | ||
97 | match other { | ||
98 | None => unimplemented!(), | ||
99 | Nope => Nope, | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | "# | ||
104 | .trim(), | ||
105 | expect_file!["crates/ra_ide/test_data/highlighting.html"], | ||
106 | false, | ||
107 | ); | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn test_rainbow_highlighting() { | ||
112 | check_highlighting( | ||
113 | r#" | ||
114 | fn main() { | ||
115 | let hello = "hello"; | ||
116 | let x = hello.to_string(); | ||
117 | let y = hello.to_string(); | ||
118 | |||
119 | let x = "other color please!"; | ||
120 | let y = x.to_string(); | ||
121 | } | ||
122 | |||
123 | fn bar() { | ||
124 | let mut hello = "hello"; | ||
125 | } | ||
126 | "# | ||
127 | .trim(), | ||
128 | expect_file!["crates/ra_ide/test_data/rainbow_highlighting.html"], | ||
129 | true, | ||
130 | ); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn accidentally_quadratic() { | ||
135 | let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic"); | ||
136 | let src = fs::read_to_string(file).unwrap(); | ||
137 | |||
138 | let (analysis, file_id) = single_file(&src); | ||
139 | |||
140 | // let t = std::time::Instant::now(); | ||
141 | let _ = analysis.highlight(file_id).unwrap(); | ||
142 | // eprintln!("elapsed: {:?}", t.elapsed()); | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn test_ranges() { | ||
147 | let (analysis, file_id) = single_file( | ||
148 | r#" | ||
149 | #[derive(Clone, Debug)] | ||
150 | struct Foo { | ||
151 | pub x: i32, | ||
152 | pub y: i32, | ||
153 | } | ||
154 | "#, | ||
155 | ); | ||
156 | |||
157 | // The "x" | ||
158 | let highlights = &analysis | ||
159 | .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) }) | ||
160 | .unwrap(); | ||
161 | |||
162 | assert_eq!(&highlights[0].highlight.to_string(), "field.declaration"); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn test_flattening() { | ||
167 | check_highlighting( | ||
168 | r##" | ||
169 | fn fixture(ra_fixture: &str) {} | ||
170 | |||
171 | fn main() { | ||
172 | fixture(r#" | ||
173 | trait Foo { | ||
174 | fn foo() { | ||
175 | println!("2 + 2 = {}", 4); | ||
176 | } | ||
177 | }"# | ||
178 | ); | ||
179 | }"## | ||
180 | .trim(), | ||
181 | expect_file!["crates/ra_ide/test_data/highlight_injection.html"], | ||
182 | false, | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn ranges_sorted() { | ||
188 | let (analysis, file_id) = single_file( | ||
189 | r#" | ||
190 | #[foo(bar = "bar")] | ||
191 | macro_rules! test {} | ||
192 | }"# | ||
193 | .trim(), | ||
194 | ); | ||
195 | let _ = analysis.highlight(file_id).unwrap(); | ||
196 | } | ||
197 | |||
198 | #[test] | ||
199 | fn test_string_highlighting() { | ||
200 | // The format string detection is based on macro-expansion, | ||
201 | // thus, we have to copy the macro definition from `std` | ||
202 | check_highlighting( | ||
203 | r#" | ||
204 | macro_rules! println { | ||
205 | ($($arg:tt)*) => ({ | ||
206 | $crate::io::_print($crate::format_args_nl!($($arg)*)); | ||
207 | }) | ||
208 | } | ||
209 | #[rustc_builtin_macro] | ||
210 | macro_rules! format_args_nl { | ||
211 | ($fmt:expr) => {{ /* compiler built-in */ }}; | ||
212 | ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; | ||
213 | } | ||
214 | |||
215 | fn main() { | ||
216 | // from https://doc.rust-lang.org/std/fmt/index.html | ||
217 | println!("Hello"); // => "Hello" | ||
218 | println!("Hello, {}!", "world"); // => "Hello, world!" | ||
219 | println!("The number is {}", 1); // => "The number is 1" | ||
220 | println!("{:?}", (3, 4)); // => "(3, 4)" | ||
221 | println!("{value}", value=4); // => "4" | ||
222 | println!("{} {}", 1, 2); // => "1 2" | ||
223 | println!("{:04}", 42); // => "0042" with leading zerosV | ||
224 | println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2" | ||
225 | println!("{argument}", argument = "test"); // => "test" | ||
226 | println!("{name} {}", 1, name = 2); // => "2 1" | ||
227 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | ||
228 | println!("{{{}}}", 2); // => "{2}" | ||
229 | println!("Hello {:5}!", "x"); | ||
230 | println!("Hello {:1$}!", "x", 5); | ||
231 | println!("Hello {1:0$}!", 5, "x"); | ||
232 | println!("Hello {:width$}!", "x", width = 5); | ||
233 | println!("Hello {:<5}!", "x"); | ||
234 | println!("Hello {:-<5}!", "x"); | ||
235 | println!("Hello {:^5}!", "x"); | ||
236 | println!("Hello {:>5}!", "x"); | ||
237 | println!("Hello {:+}!", 5); | ||
238 | println!("{:#x}!", 27); | ||
239 | println!("Hello {:05}!", 5); | ||
240 | println!("Hello {:05}!", -5); | ||
241 | println!("{:#010x}!", 27); | ||
242 | println!("Hello {0} is {1:.5}", "x", 0.01); | ||
243 | println!("Hello {1} is {2:.0$}", 5, "x", 0.01); | ||
244 | println!("Hello {0} is {2:.1$}", "x", 5, 0.01); | ||
245 | println!("Hello {} is {:.*}", "x", 5, 0.01); | ||
246 | println!("Hello {} is {2:.*}", "x", 5, 0.01); | ||
247 | println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01); | ||
248 | println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56); | ||
249 | println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56"); | ||
250 | println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); | ||
251 | println!("Hello {{}}"); | ||
252 | println!("{{ Hello"); | ||
253 | |||
254 | println!(r"Hello, {}!", "world"); | ||
255 | |||
256 | // escape sequences | ||
257 | println!("Hello\nWorld"); | ||
258 | println!("\u{48}\x65\x6C\x6C\x6F World"); | ||
259 | |||
260 | println!("{\x41}", A = 92); | ||
261 | println!("{ничоси}", ничоси = 92); | ||
262 | }"# | ||
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, | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | /// Highlights the code given by the `ra_fixture` argument, renders the | ||
374 | /// result as HTML, and compares it with the HTML file given as `snapshot`. | ||
375 | /// Note that the `snapshot` file is overwritten by the rendered HTML. | ||
376 | fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) { | ||
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) | ||
380 | } | ||