diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 26 | ||||
-rw-r--r-- | crates/ra_ide/src/hover.rs | 142 | ||||
-rw-r--r-- | crates/ra_ide/src/runnables.rs | 92 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_doctest.html | 71 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_injection.html | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_strings.html | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_unsafe.html | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlighting.html | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/rainbow_highlighting.html | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 135 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/html.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/injection.rs | 168 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 111 |
13 files changed, 663 insertions, 104 deletions
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index a6c86e99c..0798d2c36 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
3 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref, NameClass}, |
4 | symbol_index, RootDatabase, | 4 | symbol_index, RootDatabase, |
5 | }; | 5 | }; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
@@ -39,7 +39,10 @@ pub(crate) fn goto_definition( | |||
39 | reference_definition(&sema, &name_ref).to_vec() | 39 | reference_definition(&sema, &name_ref).to_vec() |
40 | }, | 40 | }, |
41 | ast::Name(name) => { | 41 | ast::Name(name) => { |
42 | let def = classify_name(&sema, &name)?.definition(); | 42 | let def = match classify_name(&sema, &name)? { |
43 | NameClass::Definition(def) | NameClass::ConstReference(def) => def, | ||
44 | NameClass::FieldShorthand { local: _, field } => field, | ||
45 | }; | ||
43 | let nav = def.try_to_nav(sema.db)?; | 46 | let nav = def.try_to_nav(sema.db)?; |
44 | vec![nav] | 47 | vec![nav] |
45 | }, | 48 | }, |
@@ -886,4 +889,23 @@ mod tests { | |||
886 | "x", | 889 | "x", |
887 | ) | 890 | ) |
888 | } | 891 | } |
892 | |||
893 | #[test] | ||
894 | fn goto_def_for_enum_variant_field() { | ||
895 | check_goto( | ||
896 | " | ||
897 | //- /lib.rs | ||
898 | enum Foo { | ||
899 | Bar { x: i32 } | ||
900 | } | ||
901 | fn baz(foo: Foo) { | ||
902 | match foo { | ||
903 | Foo::Bar { x<|> } => x | ||
904 | }; | ||
905 | } | ||
906 | ", | ||
907 | "x RECORD_FIELD_DEF FileId(1) 21..27 21..22", | ||
908 | "x: i32|x", | ||
909 | ); | ||
910 | } | ||
889 | } | 911 | } |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 846d8c69b..ad78b7671 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -14,34 +14,43 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs | |||
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, |
17 | FilePosition, NavigationTarget, RangeInfo, | 17 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | ||
18 | }; | 19 | }; |
20 | use test_utils::mark; | ||
19 | 21 | ||
20 | #[derive(Clone, Debug, PartialEq, Eq)] | 22 | #[derive(Clone, Debug, PartialEq, Eq)] |
21 | pub struct HoverConfig { | 23 | pub struct HoverConfig { |
22 | pub implementations: bool, | 24 | pub implementations: bool, |
25 | pub run: bool, | ||
26 | pub debug: bool, | ||
23 | } | 27 | } |
24 | 28 | ||
25 | impl Default for HoverConfig { | 29 | impl Default for HoverConfig { |
26 | fn default() -> Self { | 30 | fn default() -> Self { |
27 | Self { implementations: true } | 31 | Self { implementations: true, run: true, debug: true } |
28 | } | 32 | } |
29 | } | 33 | } |
30 | 34 | ||
31 | impl HoverConfig { | 35 | impl HoverConfig { |
32 | pub const NO_ACTIONS: Self = Self { implementations: false }; | 36 | pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; |
33 | 37 | ||
34 | pub fn any(&self) -> bool { | 38 | pub fn any(&self) -> bool { |
35 | self.implementations | 39 | self.implementations || self.runnable() |
36 | } | 40 | } |
37 | 41 | ||
38 | pub fn none(&self) -> bool { | 42 | pub fn none(&self) -> bool { |
39 | !self.any() | 43 | !self.any() |
40 | } | 44 | } |
45 | |||
46 | pub fn runnable(&self) -> bool { | ||
47 | self.run || self.debug | ||
48 | } | ||
41 | } | 49 | } |
42 | 50 | ||
43 | #[derive(Debug, Clone)] | 51 | #[derive(Debug, Clone)] |
44 | pub enum HoverAction { | 52 | pub enum HoverAction { |
53 | Runnable(Runnable), | ||
45 | Implementaion(FilePosition), | 54 | Implementaion(FilePosition), |
46 | } | 55 | } |
47 | 56 | ||
@@ -125,6 +134,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
125 | res.push_action(action); | 134 | res.push_action(action); |
126 | } | 135 | } |
127 | 136 | ||
137 | if let Some(action) = runnable_action(&sema, name_kind, position.file_id) { | ||
138 | res.push_action(action); | ||
139 | } | ||
140 | |||
128 | return Some(RangeInfo::new(range, res)); | 141 | return Some(RangeInfo::new(range, res)); |
129 | } | 142 | } |
130 | } | 143 | } |
@@ -175,6 +188,36 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov | |||
175 | } | 188 | } |
176 | } | 189 | } |
177 | 190 | ||
191 | fn runnable_action( | ||
192 | sema: &Semantics<RootDatabase>, | ||
193 | def: Definition, | ||
194 | file_id: FileId, | ||
195 | ) -> Option<HoverAction> { | ||
196 | match def { | ||
197 | Definition::ModuleDef(it) => match it { | ||
198 | ModuleDef::Module(it) => match it.definition_source(sema.db).value { | ||
199 | ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id) | ||
200 | .map(|it| HoverAction::Runnable(it)), | ||
201 | _ => None, | ||
202 | }, | ||
203 | ModuleDef::Function(it) => { | ||
204 | let src = it.source(sema.db); | ||
205 | if src.file_id != file_id.into() { | ||
206 | mark::hit!(hover_macro_generated_struct_fn_doc_comment); | ||
207 | mark::hit!(hover_macro_generated_struct_fn_doc_attr); | ||
208 | |||
209 | return None; | ||
210 | } | ||
211 | |||
212 | runnable(&sema, src.value.syntax().clone(), file_id) | ||
213 | .map(|it| HoverAction::Runnable(it)) | ||
214 | } | ||
215 | _ => None, | ||
216 | }, | ||
217 | _ => None, | ||
218 | } | ||
219 | } | ||
220 | |||
178 | fn hover_text( | 221 | fn hover_text( |
179 | docs: Option<String>, | 222 | docs: Option<String>, |
180 | desc: Option<String>, | 223 | desc: Option<String>, |
@@ -292,6 +335,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
292 | #[cfg(test)] | 335 | #[cfg(test)] |
293 | mod tests { | 336 | mod tests { |
294 | use super::*; | 337 | use super::*; |
338 | use insta::assert_debug_snapshot; | ||
295 | 339 | ||
296 | use ra_db::FileLoader; | 340 | use ra_db::FileLoader; |
297 | use ra_syntax::TextRange; | 341 | use ra_syntax::TextRange; |
@@ -309,6 +353,7 @@ mod tests { | |||
309 | fn assert_impl_action(action: &HoverAction, position: u32) { | 353 | fn assert_impl_action(action: &HoverAction, position: u32) { |
310 | let offset = match action { | 354 | let offset = match action { |
311 | HoverAction::Implementaion(pos) => pos.offset, | 355 | HoverAction::Implementaion(pos) => pos.offset, |
356 | it => panic!("Unexpected hover action: {:#?}", it), | ||
312 | }; | 357 | }; |
313 | assert_eq!(offset, position.into()); | 358 | assert_eq!(offset, position.into()); |
314 | } | 359 | } |
@@ -1076,6 +1121,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1076 | 1121 | ||
1077 | #[test] | 1122 | #[test] |
1078 | fn test_hover_macro_generated_struct_fn_doc_comment() { | 1123 | fn test_hover_macro_generated_struct_fn_doc_comment() { |
1124 | mark::check!(hover_macro_generated_struct_fn_doc_comment); | ||
1125 | |||
1079 | check_hover_result( | 1126 | check_hover_result( |
1080 | r#" | 1127 | r#" |
1081 | //- /lib.rs | 1128 | //- /lib.rs |
@@ -1102,6 +1149,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1102 | 1149 | ||
1103 | #[test] | 1150 | #[test] |
1104 | fn test_hover_macro_generated_struct_fn_doc_attr() { | 1151 | fn test_hover_macro_generated_struct_fn_doc_attr() { |
1152 | mark::check!(hover_macro_generated_struct_fn_doc_attr); | ||
1153 | |||
1105 | check_hover_result( | 1154 | check_hover_result( |
1106 | r#" | 1155 | r#" |
1107 | //- /lib.rs | 1156 | //- /lib.rs |
@@ -1176,4 +1225,89 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1176 | ); | 1225 | ); |
1177 | assert_impl_action(&actions[0], 5); | 1226 | assert_impl_action(&actions[0], 5); |
1178 | } | 1227 | } |
1228 | |||
1229 | #[test] | ||
1230 | fn test_hover_test_has_action() { | ||
1231 | let (_, actions) = check_hover_result( | ||
1232 | " | ||
1233 | //- /lib.rs | ||
1234 | #[test] | ||
1235 | fn foo_<|>test() {} | ||
1236 | ", | ||
1237 | &["fn foo_test()"], | ||
1238 | ); | ||
1239 | assert_debug_snapshot!(actions, | ||
1240 | @r###" | ||
1241 | [ | ||
1242 | Runnable( | ||
1243 | Runnable { | ||
1244 | nav: NavigationTarget { | ||
1245 | file_id: FileId( | ||
1246 | 1, | ||
1247 | ), | ||
1248 | full_range: 0..24, | ||
1249 | name: "foo_test", | ||
1250 | kind: FN_DEF, | ||
1251 | focus_range: Some( | ||
1252 | 11..19, | ||
1253 | ), | ||
1254 | container_name: None, | ||
1255 | description: None, | ||
1256 | docs: None, | ||
1257 | }, | ||
1258 | kind: Test { | ||
1259 | test_id: Path( | ||
1260 | "foo_test", | ||
1261 | ), | ||
1262 | attr: TestAttr { | ||
1263 | ignore: false, | ||
1264 | }, | ||
1265 | }, | ||
1266 | cfg_exprs: [], | ||
1267 | }, | ||
1268 | ), | ||
1269 | ] | ||
1270 | "###); | ||
1271 | } | ||
1272 | |||
1273 | #[test] | ||
1274 | fn test_hover_test_mod_has_action() { | ||
1275 | let (_, actions) = check_hover_result( | ||
1276 | " | ||
1277 | //- /lib.rs | ||
1278 | mod tests<|> { | ||
1279 | #[test] | ||
1280 | fn foo_test() {} | ||
1281 | } | ||
1282 | ", | ||
1283 | &["mod tests"], | ||
1284 | ); | ||
1285 | assert_debug_snapshot!(actions, | ||
1286 | @r###" | ||
1287 | [ | ||
1288 | Runnable( | ||
1289 | Runnable { | ||
1290 | nav: NavigationTarget { | ||
1291 | file_id: FileId( | ||
1292 | 1, | ||
1293 | ), | ||
1294 | full_range: 0..46, | ||
1295 | name: "tests", | ||
1296 | kind: MODULE, | ||
1297 | focus_range: Some( | ||
1298 | 4..9, | ||
1299 | ), | ||
1300 | container_name: None, | ||
1301 | description: None, | ||
1302 | docs: None, | ||
1303 | }, | ||
1304 | kind: TestMod { | ||
1305 | path: "tests", | ||
1306 | }, | ||
1307 | cfg_exprs: [], | ||
1308 | }, | ||
1309 | ), | ||
1310 | ] | ||
1311 | "###); | ||
1312 | } | ||
1179 | } | 1313 | } |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index f32ce0d22..fc57dc33d 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -11,14 +11,14 @@ use ra_syntax::{ | |||
11 | 11 | ||
12 | use crate::{display::ToNav, FileId, NavigationTarget}; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
13 | 13 | ||
14 | #[derive(Debug)] | 14 | #[derive(Debug, Clone)] |
15 | pub struct Runnable { | 15 | pub struct Runnable { |
16 | pub nav: NavigationTarget, | 16 | pub nav: NavigationTarget, |
17 | pub kind: RunnableKind, | 17 | pub kind: RunnableKind, |
18 | pub cfg_exprs: Vec<CfgExpr>, | 18 | pub cfg_exprs: Vec<CfgExpr>, |
19 | } | 19 | } |
20 | 20 | ||
21 | #[derive(Debug)] | 21 | #[derive(Debug, Clone)] |
22 | pub enum TestId { | 22 | pub enum TestId { |
23 | Name(String), | 23 | Name(String), |
24 | Path(String), | 24 | Path(String), |
@@ -33,7 +33,7 @@ impl fmt::Display for TestId { | |||
33 | } | 33 | } |
34 | } | 34 | } |
35 | 35 | ||
36 | #[derive(Debug)] | 36 | #[derive(Debug, Clone)] |
37 | pub enum RunnableKind { | 37 | pub enum RunnableKind { |
38 | Test { test_id: TestId, attr: TestAttr }, | 38 | Test { test_id: TestId, attr: TestAttr }, |
39 | TestMod { path: String }, | 39 | TestMod { path: String }, |
@@ -42,6 +42,42 @@ pub enum RunnableKind { | |||
42 | Bin, | 42 | Bin, |
43 | } | 43 | } |
44 | 44 | ||
45 | #[derive(Debug, Eq, PartialEq)] | ||
46 | pub struct RunnableAction { | ||
47 | pub run_title: &'static str, | ||
48 | pub debugee: bool, | ||
49 | } | ||
50 | |||
51 | const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true }; | ||
52 | const DOCTEST: RunnableAction = | ||
53 | RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false }; | ||
54 | const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true }; | ||
55 | const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true }; | ||
56 | |||
57 | impl Runnable { | ||
58 | // test package::module::testname | ||
59 | pub fn label(&self, target: Option<String>) -> String { | ||
60 | match &self.kind { | ||
61 | RunnableKind::Test { test_id, .. } => format!("test {}", test_id), | ||
62 | RunnableKind::TestMod { path } => format!("test-mod {}", path), | ||
63 | RunnableKind::Bench { test_id } => format!("bench {}", test_id), | ||
64 | RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id), | ||
65 | RunnableKind::Bin => { | ||
66 | target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | pub fn action(&self) -> &'static RunnableAction { | ||
72 | match &self.kind { | ||
73 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST, | ||
74 | RunnableKind::DocTest { .. } => &DOCTEST, | ||
75 | RunnableKind::Bench { .. } => &BENCH, | ||
76 | RunnableKind::Bin => &BIN, | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
45 | // Feature: Run | 81 | // Feature: Run |
46 | // | 82 | // |
47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | 83 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor |
@@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | |||
59 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() | 95 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() |
60 | } | 96 | } |
61 | 97 | ||
62 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { | 98 | pub(crate) fn runnable( |
99 | sema: &Semantics<RootDatabase>, | ||
100 | item: SyntaxNode, | ||
101 | file_id: FileId, | ||
102 | ) -> Option<Runnable> { | ||
63 | match_ast! { | 103 | match_ast! { |
64 | match item { | 104 | match item { |
65 | ast::FnDef(it) => runnable_fn(sema, it, file_id), | 105 | ast::FnDef(it) => runnable_fn(sema, it, file_id), |
@@ -135,7 +175,7 @@ fn runnable_fn( | |||
135 | Some(Runnable { nav, kind, cfg_exprs }) | 175 | Some(Runnable { nav, kind, cfg_exprs }) |
136 | } | 176 | } |
137 | 177 | ||
138 | #[derive(Debug)] | 178 | #[derive(Debug, Copy, Clone)] |
139 | pub struct TestAttr { | 179 | pub struct TestAttr { |
140 | pub ignore: bool, | 180 | pub ignore: bool, |
141 | } | 181 | } |
@@ -207,6 +247,15 @@ mod tests { | |||
207 | 247 | ||
208 | use crate::mock_analysis::analysis_and_position; | 248 | use crate::mock_analysis::analysis_and_position; |
209 | 249 | ||
250 | use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST}; | ||
251 | |||
252 | fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) { | ||
253 | assert_eq!( | ||
254 | actions, | ||
255 | runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice() | ||
256 | ); | ||
257 | } | ||
258 | |||
210 | #[test] | 259 | #[test] |
211 | fn test_runnables() { | 260 | fn test_runnables() { |
212 | let (analysis, pos) = analysis_and_position( | 261 | let (analysis, pos) = analysis_and_position( |
@@ -221,6 +270,9 @@ mod tests { | |||
221 | #[test] | 270 | #[test] |
222 | #[ignore] | 271 | #[ignore] |
223 | fn test_foo() {} | 272 | fn test_foo() {} |
273 | |||
274 | #[bench] | ||
275 | fn bench() {} | ||
224 | "#, | 276 | "#, |
225 | ); | 277 | ); |
226 | let runnables = analysis.runnables(pos.file_id).unwrap(); | 278 | let runnables = analysis.runnables(pos.file_id).unwrap(); |
@@ -295,9 +347,32 @@ mod tests { | |||
295 | }, | 347 | }, |
296 | cfg_exprs: [], | 348 | cfg_exprs: [], |
297 | }, | 349 | }, |
350 | Runnable { | ||
351 | nav: NavigationTarget { | ||
352 | file_id: FileId( | ||
353 | 1, | ||
354 | ), | ||
355 | full_range: 82..104, | ||
356 | name: "bench", | ||
357 | kind: FN_DEF, | ||
358 | focus_range: Some( | ||
359 | 94..99, | ||
360 | ), | ||
361 | container_name: None, | ||
362 | description: None, | ||
363 | docs: None, | ||
364 | }, | ||
365 | kind: Bench { | ||
366 | test_id: Path( | ||
367 | "bench", | ||
368 | ), | ||
369 | }, | ||
370 | cfg_exprs: [], | ||
371 | }, | ||
298 | ] | 372 | ] |
299 | "### | 373 | "### |
300 | ); | 374 | ); |
375 | assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]); | ||
301 | } | 376 | } |
302 | 377 | ||
303 | #[test] | 378 | #[test] |
@@ -361,6 +436,7 @@ mod tests { | |||
361 | ] | 436 | ] |
362 | "### | 437 | "### |
363 | ); | 438 | ); |
439 | assert_actions(&runnables, &[&BIN, &DOCTEST]); | ||
364 | } | 440 | } |
365 | 441 | ||
366 | #[test] | 442 | #[test] |
@@ -427,6 +503,7 @@ mod tests { | |||
427 | ] | 503 | ] |
428 | "### | 504 | "### |
429 | ); | 505 | ); |
506 | assert_actions(&runnables, &[&BIN, &DOCTEST]); | ||
430 | } | 507 | } |
431 | 508 | ||
432 | #[test] | 509 | #[test] |
@@ -493,6 +570,7 @@ mod tests { | |||
493 | ] | 570 | ] |
494 | "### | 571 | "### |
495 | ); | 572 | ); |
573 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
496 | } | 574 | } |
497 | 575 | ||
498 | #[test] | 576 | #[test] |
@@ -561,6 +639,7 @@ mod tests { | |||
561 | ] | 639 | ] |
562 | "### | 640 | "### |
563 | ); | 641 | ); |
642 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
564 | } | 643 | } |
565 | 644 | ||
566 | #[test] | 645 | #[test] |
@@ -631,6 +710,7 @@ mod tests { | |||
631 | ] | 710 | ] |
632 | "### | 711 | "### |
633 | ); | 712 | ); |
713 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
634 | } | 714 | } |
635 | 715 | ||
636 | #[test] | 716 | #[test] |
@@ -681,6 +761,7 @@ mod tests { | |||
681 | ] | 761 | ] |
682 | "### | 762 | "### |
683 | ); | 763 | ); |
764 | assert_actions(&runnables, &[&TEST]); | ||
684 | } | 765 | } |
685 | 766 | ||
686 | #[test] | 767 | #[test] |
@@ -739,6 +820,7 @@ mod tests { | |||
739 | ] | 820 | ] |
740 | "### | 821 | "### |
741 | ); | 822 | ); |
823 | assert_actions(&runnables, &[&TEST]); | ||
742 | } | 824 | } |
743 | 825 | ||
744 | #[test] | 826 | #[test] |
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html new file mode 100644 index 000000000..0ae8c7efc --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html | |||
@@ -0,0 +1,71 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .function.unsafe { color: #BC8383; } | ||
14 | .operator.unsafe { color: #BC8383; } | ||
15 | .parameter { color: #94BFF3; } | ||
16 | .text { color: #DCDCCC; } | ||
17 | .type { color: #7CB8BB; } | ||
18 | .builtin_type { color: #8CD0D3; } | ||
19 | .type_param { color: #DFAF8F; } | ||
20 | .attribute { color: #94BFF3; } | ||
21 | .numeric_literal { color: #BFEBBF; } | ||
22 | .bool_literal { color: #BFE6EB; } | ||
23 | .macro { color: #94BFF3; } | ||
24 | .module { color: #AFD8AF; } | ||
25 | .variable { color: #DCDCCC; } | ||
26 | .format_specifier { color: #CC696B; } | ||
27 | .mutable { text-decoration: underline; } | ||
28 | |||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
31 | .control { font-style: italic; } | ||
32 | </style> | ||
33 | <pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { | ||
34 | <span class="comment">/// Constructs a new `Foo`.</span> | ||
35 | <span class="comment">///</span> | ||
36 | <span class="comment">/// # Examples</span> | ||
37 | <span class="comment">///</span> | ||
38 | <span class="comment">/// ```</span> | ||
39 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | ||
40 | <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | ||
41 | <span class="comment">/// ```</span> | ||
42 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> { | ||
43 | <span class="unresolved_reference">Foo</span> { } | ||
44 | } | ||
45 | |||
46 | <span class="comment">/// `bar` method on `Foo`.</span> | ||
47 | <span class="comment">///</span> | ||
48 | <span class="comment">/// # Examples</span> | ||
49 | <span class="comment">///</span> | ||
50 | <span class="comment">/// ```</span> | ||
51 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | ||
52 | <span class="comment">///</span> | ||
53 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> | ||
54 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); | ||
55 | <span class="comment">///</span> | ||
56 | <span class="comment">/// </span><span class="comment">/* multi-line | ||
57 | </span><span class="comment">/// </span><span class="comment"> comment */</span> | ||
58 | <span class="comment">///</span> | ||
59 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo | ||
60 | </span><span class="comment">/// </span><span class="string_literal"> bar | ||
61 | </span><span class="comment">/// </span><span class="string_literal"> "</span>; | ||
62 | <span class="comment">///</span> | ||
63 | <span class="comment">/// ```</span> | ||
64 | <span class="comment">///</span> | ||
65 | <span class="comment">/// ```</span> | ||
66 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>(); | ||
67 | <span class="comment">/// ```</span> | ||
68 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { | ||
69 | <span class="bool_literal">true</span> | ||
70 | } | ||
71 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index fcdc98201..dec06eb51 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -10,7 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | 13 | .function.unsafe { color: #BC8383; } |
14 | .operator.unsafe { color: #BC8383; } | ||
14 | .parameter { color: #94BFF3; } | 15 | .parameter { color: #94BFF3; } |
15 | .text { color: #DCDCCC; } | 16 | .text { color: #DCDCCC; } |
16 | .type { color: #7CB8BB; } | 17 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index e97192b61..849eb3b73 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -10,7 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | 13 | .function.unsafe { color: #BC8383; } |
14 | .operator.unsafe { color: #BC8383; } | ||
14 | .parameter { color: #94BFF3; } | 15 | .parameter { color: #94BFF3; } |
15 | .text { color: #DCDCCC; } | 16 | .text { color: #DCDCCC; } |
16 | .type { color: #7CB8BB; } | 17 | .type { color: #7CB8BB; } |
@@ -63,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
63 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 64 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
64 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 65 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
65 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); | 66 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); |
66 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>); | 67 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); |
67 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); | 68 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); |
68 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>); | 69 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>); |
69 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); | 70 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); |
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html index 17ffc727c..bd24e6e38 100644 --- a/crates/ra_ide/src/snapshots/highlight_unsafe.html +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
@@ -10,7 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | 13 | .function.unsafe { color: #BC8383; } |
14 | .operator.unsafe { color: #BC8383; } | ||
14 | .parameter { color: #94BFF3; } | 15 | .parameter { color: #94BFF3; } |
15 | .text { color: #DCDCCC; } | 16 | .text { color: #DCDCCC; } |
16 | .type { color: #7CB8BB; } | 17 | .type { color: #7CB8BB; } |
@@ -42,7 +43,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
42 | <span class="keyword unsafe">unsafe</span> { | 43 | <span class="keyword unsafe">unsafe</span> { |
43 | <span class="function unsafe">unsafe_fn</span>(); | 44 | <span class="function unsafe">unsafe_fn</span>(); |
44 | <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>(); | 45 | <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>(); |
45 | <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>; | 46 | <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span>(<span class="variable">x</span>); |
46 | <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>; | 47 | <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>; |
47 | } | 48 | } |
48 | }</code></pre> \ No newline at end of file | 49 | }</code></pre> \ No newline at end of file |
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 42c5f3e55..e34ff5a7d 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -10,7 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | 13 | .function.unsafe { color: #BC8383; } |
14 | .operator.unsafe { color: #BC8383; } | ||
14 | .parameter { color: #94BFF3; } | 15 | .parameter { color: #94BFF3; } |
15 | .text { color: #DCDCCC; } | 16 | .text { color: #DCDCCC; } |
16 | .type { color: #7CB8BB; } | 17 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 2dd61d20d..1ab06182c 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
@@ -10,7 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | 13 | .function.unsafe { color: #BC8383; } |
14 | .operator.unsafe { color: #BC8383; } | ||
14 | .parameter { color: #94BFF3; } | 15 | .parameter { color: #94BFF3; } |
15 | .text { color: #DCDCCC; } | 16 | .text { color: #DCDCCC; } |
16 | .type { color: #7CB8BB; } | 17 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 19ecd54d6..ab45c364a 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | mod tags; | 1 | mod tags; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | ||
3 | #[cfg(test)] | 4 | #[cfg(test)] |
4 | mod tests; | 5 | mod tests; |
5 | 6 | ||
@@ -10,14 +11,14 @@ use ra_ide_db::{ | |||
10 | }; | 11 | }; |
11 | use ra_prof::profile; | 12 | use ra_prof::profile; |
12 | use ra_syntax::{ | 13 | use ra_syntax::{ |
13 | ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, | 14 | ast::{self, HasFormatSpecifier}, |
14 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 15 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, |
15 | SyntaxKind::*, | 16 | SyntaxKind::*, |
16 | SyntaxToken, TextRange, WalkEvent, T, | 17 | TextRange, WalkEvent, T, |
17 | }; | 18 | }; |
18 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::FxHashMap; |
19 | 20 | ||
20 | use crate::{call_info::ActiveParameter, Analysis, FileId}; | 21 | use crate::FileId; |
21 | 22 | ||
22 | use ast::FormatSpecifier; | 23 | use ast::FormatSpecifier; |
23 | pub(crate) use html::highlight_as_html; | 24 | pub(crate) use html::highlight_as_html; |
@@ -123,6 +124,23 @@ pub(crate) fn highlight( | |||
123 | _ => (), | 124 | _ => (), |
124 | } | 125 | } |
125 | 126 | ||
127 | // Check for Rust code in documentation | ||
128 | match &event { | ||
129 | WalkEvent::Leave(NodeOrToken::Node(node)) => { | ||
130 | if let Some((doctest, range_mapping, new_comments)) = | ||
131 | injection::extract_doc_comments(node) | ||
132 | { | ||
133 | injection::highlight_doc_comment( | ||
134 | doctest, | ||
135 | range_mapping, | ||
136 | new_comments, | ||
137 | &mut stack, | ||
138 | ); | ||
139 | } | ||
140 | } | ||
141 | _ => (), | ||
142 | } | ||
143 | |||
126 | let element = match event { | 144 | let element = match event { |
127 | WalkEvent::Enter(it) => it, | 145 | WalkEvent::Enter(it) => it, |
128 | WalkEvent::Leave(_) => continue, | 146 | WalkEvent::Leave(_) => continue, |
@@ -173,7 +191,7 @@ pub(crate) fn highlight( | |||
173 | 191 | ||
174 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | 192 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { |
175 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 193 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
176 | if highlight_injection(&mut stack, &sema, token, expanded).is_some() { | 194 | if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { |
177 | continue; | 195 | continue; |
178 | } | 196 | } |
179 | } | 197 | } |
@@ -259,9 +277,8 @@ impl HighlightedRangeStack { | |||
259 | let mut parent = prev.pop().unwrap(); | 277 | let mut parent = prev.pop().unwrap(); |
260 | for ele in children { | 278 | for ele in children { |
261 | assert!(parent.range.contains_range(ele.range)); | 279 | assert!(parent.range.contains_range(ele.range)); |
262 | let mut cloned = parent.clone(); | 280 | |
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | 281 | let cloned = Self::intersect(&mut parent, &ele); |
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | 282 | if !parent.range.is_empty() { |
266 | prev.push(parent); | 283 | prev.push(parent); |
267 | } | 284 | } |
@@ -274,6 +291,62 @@ impl HighlightedRangeStack { | |||
274 | } | 291 | } |
275 | } | 292 | } |
276 | 293 | ||
294 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
295 | /// `parent` is mutated in place, becoming the range before `child`. | ||
296 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
297 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
298 | assert!(parent.range.contains_range(child.range)); | ||
299 | |||
300 | let mut cloned = parent.clone(); | ||
301 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
302 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
303 | |||
304 | cloned | ||
305 | } | ||
306 | |||
307 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
308 | /// can only modify the last range currently on the stack. | ||
309 | /// Can be used to do injections that span multiple ranges, like the | ||
310 | /// doctest injection below. | ||
311 | /// If `delete` is set to true, the parent range is deleted instead of | ||
312 | /// intersected. | ||
313 | /// | ||
314 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
315 | /// latter is computationally more expensive. | ||
316 | fn pop_and_inject(&mut self, delete: bool) { | ||
317 | let mut children = self.stack.pop().unwrap(); | ||
318 | let prev = self.stack.last_mut().unwrap(); | ||
319 | children.sort_by_key(|range| range.range.start()); | ||
320 | prev.sort_by_key(|range| range.range.start()); | ||
321 | |||
322 | for child in children { | ||
323 | if let Some(idx) = | ||
324 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
325 | { | ||
326 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
327 | let insert_idx = if delete || prev[idx].range.is_empty() { | ||
328 | prev.remove(idx); | ||
329 | idx | ||
330 | } else { | ||
331 | idx + 1 | ||
332 | }; | ||
333 | prev.insert(insert_idx, child); | ||
334 | if !delete && !cloned.range.is_empty() { | ||
335 | prev.insert(insert_idx + 1, cloned); | ||
336 | } | ||
337 | } else if let Some(_idx) = | ||
338 | prev.iter().position(|parent| parent.range.contains(child.range.start())) | ||
339 | { | ||
340 | unreachable!("child range should be completely contained in parent range"); | ||
341 | } else { | ||
342 | let idx = prev | ||
343 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
344 | .unwrap_or_else(|x| x); | ||
345 | prev.insert(idx, child); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
277 | fn add(&mut self, range: HighlightedRange) { | 350 | fn add(&mut self, range: HighlightedRange) { |
278 | self.stack | 351 | self.stack |
279 | .last_mut() | 352 | .last_mut() |
@@ -363,6 +436,7 @@ fn highlight_element( | |||
363 | highlight_name(db, def) | HighlightModifier::Definition | 436 | highlight_name(db, def) | HighlightModifier::Definition |
364 | } | 437 | } |
365 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), | 438 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), |
439 | Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(), | ||
366 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | 440 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, |
367 | } | 441 | } |
368 | } | 442 | } |
@@ -406,12 +480,8 @@ fn highlight_element( | |||
406 | _ => h, | 480 | _ => h, |
407 | } | 481 | } |
408 | } | 482 | } |
409 | PREFIX_EXPR => { | 483 | T![*] => { |
410 | let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; | 484 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; |
411 | match prefix_expr.op_kind() { | ||
412 | Some(ast::PrefixOp::Deref) => {} | ||
413 | _ => return None, | ||
414 | } | ||
415 | 485 | ||
416 | let expr = prefix_expr.expr()?; | 486 | let expr = prefix_expr.expr()?; |
417 | let ty = sema.type_of_expr(&expr)?; | 487 | let ty = sema.type_of_expr(&expr)?; |
@@ -539,42 +609,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
539 | 609 | ||
540 | tag.into() | 610 | tag.into() |
541 | } | 611 | } |
542 | |||
543 | fn highlight_injection( | ||
544 | acc: &mut HighlightedRangeStack, | ||
545 | sema: &Semantics<RootDatabase>, | ||
546 | literal: ast::RawString, | ||
547 | expanded: SyntaxToken, | ||
548 | ) -> Option<()> { | ||
549 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | ||
550 | if !active_parameter.name.starts_with("ra_fixture") { | ||
551 | return None; | ||
552 | } | ||
553 | let value = literal.value()?; | ||
554 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | ||
555 | |||
556 | if let Some(range) = literal.open_quote_text_range() { | ||
557 | acc.add(HighlightedRange { | ||
558 | range, | ||
559 | highlight: HighlightTag::StringLiteral.into(), | ||
560 | binding_hash: None, | ||
561 | }) | ||
562 | } | ||
563 | |||
564 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
565 | if let Some(r) = literal.map_range_up(h.range) { | ||
566 | h.range = r; | ||
567 | acc.add(h) | ||
568 | } | ||
569 | } | ||
570 | |||
571 | if let Some(range) = literal.close_quote_text_range() { | ||
572 | acc.add(HighlightedRange { | ||
573 | range, | ||
574 | highlight: HighlightTag::StringLiteral.into(), | ||
575 | binding_hash: None, | ||
576 | }) | ||
577 | } | ||
578 | |||
579 | Some(()) | ||
580 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index 7d946c98d..5bada6252 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -69,7 +69,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
69 | .string_literal { color: #CC9393; } | 69 | .string_literal { color: #CC9393; } |
70 | .field { color: #94BFF3; } | 70 | .field { color: #94BFF3; } |
71 | .function { color: #93E0E3; } | 71 | .function { color: #93E0E3; } |
72 | .operator.unsafe { color: #E28C14; } | 72 | .function.unsafe { color: #BC8383; } |
73 | .operator.unsafe { color: #BC8383; } | ||
73 | .parameter { color: #94BFF3; } | 74 | .parameter { color: #94BFF3; } |
74 | .text { color: #DCDCCC; } | 75 | .text { color: #DCDCCC; } |
75 | .type { color: #7CB8BB; } | 76 | .type { color: #7CB8BB; } |
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..3575a0fc6 --- /dev/null +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -0,0 +1,168 @@ | |||
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::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | ||
11 | |||
12 | use super::HighlightedRangeStack; | ||
13 | |||
14 | pub(super) fn highlight_injection( | ||
15 | acc: &mut HighlightedRangeStack, | ||
16 | sema: &Semantics<RootDatabase>, | ||
17 | literal: ast::RawString, | ||
18 | expanded: SyntaxToken, | ||
19 | ) -> Option<()> { | ||
20 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | ||
21 | if !active_parameter.name.starts_with("ra_fixture") { | ||
22 | return None; | ||
23 | } | ||
24 | let value = literal.value()?; | ||
25 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | ||
26 | |||
27 | if let Some(range) = literal.open_quote_text_range() { | ||
28 | acc.add(HighlightedRange { | ||
29 | range, | ||
30 | highlight: HighlightTag::StringLiteral.into(), | ||
31 | binding_hash: None, | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
36 | if let Some(r) = literal.map_range_up(h.range) { | ||
37 | h.range = r; | ||
38 | acc.add(h) | ||
39 | } | ||
40 | } | ||
41 | |||
42 | if let Some(range) = literal.close_quote_text_range() { | ||
43 | acc.add(HighlightedRange { | ||
44 | range, | ||
45 | highlight: HighlightTag::StringLiteral.into(), | ||
46 | binding_hash: None, | ||
47 | }) | ||
48 | } | ||
49 | |||
50 | Some(()) | ||
51 | } | ||
52 | |||
53 | /// Mapping from extracted documentation code to original code | ||
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | ||
55 | |||
56 | /// Extracts Rust code from documentation comments as well as a mapping from | ||
57 | /// the extracted source code back to the original source ranges. | ||
58 | /// Lastly, a vector of new comment highlight ranges (spanning only the | ||
59 | /// comment prefix) is returned which is used in the syntax highlighting | ||
60 | /// injection to replace the previous (line-spanning) comment ranges. | ||
61 | pub(super) fn extract_doc_comments( | ||
62 | node: &SyntaxNode, | ||
63 | ) -> Option<(String, RangesMap, Vec<HighlightedRange>)> { | ||
64 | // wrap the doctest into function body to get correct syntax highlighting | ||
65 | let prefix = "fn doctest() {\n"; | ||
66 | let suffix = "}\n"; | ||
67 | // Mapping from extracted documentation code to original code | ||
68 | let mut range_mapping: RangesMap = BTreeMap::new(); | ||
69 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | ||
70 | let mut is_doctest = false; | ||
71 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | ||
72 | // spanning comment ranges. | ||
73 | let mut new_comments = Vec::new(); | ||
74 | let doctest = node | ||
75 | .children_with_tokens() | ||
76 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | ||
77 | .filter(|comment| comment.kind().doc.is_some()) | ||
78 | .filter(|comment| { | ||
79 | if comment.text().contains("```") { | ||
80 | is_doctest = !is_doctest; | ||
81 | false | ||
82 | } else { | ||
83 | is_doctest | ||
84 | } | ||
85 | }) | ||
86 | .map(|comment| { | ||
87 | let prefix_len = comment.prefix().len(); | ||
88 | let line: &str = comment.text().as_str(); | ||
89 | let range = comment.syntax().text_range(); | ||
90 | |||
91 | // whitespace after comment is ignored | ||
92 | let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) { | ||
93 | prefix_len + ws.len_utf8() | ||
94 | } else { | ||
95 | prefix_len | ||
96 | }; | ||
97 | |||
98 | // lines marked with `#` should be ignored in output, we skip the `#` char | ||
99 | let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') { | ||
100 | pos + ws.len_utf8() | ||
101 | } else { | ||
102 | pos | ||
103 | }; | ||
104 | |||
105 | range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap()); | ||
106 | new_comments.push(HighlightedRange { | ||
107 | range: TextRange::new( | ||
108 | range.start(), | ||
109 | range.start() + TextSize::try_from(pos).unwrap(), | ||
110 | ), | ||
111 | highlight: HighlightTag::Comment.into(), | ||
112 | binding_hash: None, | ||
113 | }); | ||
114 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | ||
115 | line_start += TextSize::try_from('\n'.len_utf8()).unwrap(); | ||
116 | |||
117 | line[pos..].to_owned() | ||
118 | }) | ||
119 | .sep_by("\n") | ||
120 | .to_string(); | ||
121 | |||
122 | if doctest.is_empty() { | ||
123 | return None; | ||
124 | } | ||
125 | |||
126 | let doctest = format!("{}{}{}", prefix, doctest, suffix); | ||
127 | Some((doctest, range_mapping, new_comments)) | ||
128 | } | ||
129 | |||
130 | /// Injection of syntax highlighting of doctests. | ||
131 | pub(super) fn highlight_doc_comment( | ||
132 | text: String, | ||
133 | range_mapping: RangesMap, | ||
134 | new_comments: Vec<HighlightedRange>, | ||
135 | stack: &mut HighlightedRangeStack, | ||
136 | ) { | ||
137 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | ||
138 | |||
139 | stack.push(); | ||
140 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
141 | // Determine start offset and end offset in case of multi-line ranges | ||
142 | let mut start_offset = None; | ||
143 | let mut end_offset = None; | ||
144 | for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() { | ||
145 | if line_start <= &h.range.start() { | ||
146 | start_offset.get_or_insert(orig_line_start - line_start); | ||
147 | break; | ||
148 | } else { | ||
149 | end_offset.get_or_insert(orig_line_start - line_start); | ||
150 | } | ||
151 | } | ||
152 | if let Some(start_offset) = start_offset { | ||
153 | h.range = TextRange::new( | ||
154 | h.range.start() + start_offset, | ||
155 | h.range.end() + end_offset.unwrap_or(start_offset), | ||
156 | ); | ||
157 | stack.add(h); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | // Inject the comment prefix highlight ranges | ||
162 | stack.push(); | ||
163 | for comment in new_comments { | ||
164 | stack.add(comment); | ||
165 | } | ||
166 | stack.pop_and_inject(false); | ||
167 | stack.pop_and_inject(true); | ||
168 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 36a1aa419..021f8e7e2 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -7,9 +7,21 @@ use crate::{ | |||
7 | FileRange, TextRange, | 7 | FileRange, TextRange, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | /// Highlights the code given by the `ra_fixture` argument, renders the | ||
11 | /// result as HTML, and compares it with the HTML file given as `snapshot`. | ||
12 | /// Note that the `snapshot` file is overwritten by the rendered HTML. | ||
13 | fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) { | ||
14 | let (analysis, file_id) = single_file(ra_fixture); | ||
15 | let dst_file = project_dir().join(snapshot); | ||
16 | let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); | ||
17 | let expected_html = &read_text(&dst_file); | ||
18 | fs::write(dst_file, &actual_html).unwrap(); | ||
19 | assert_eq_text!(expected_html, actual_html); | ||
20 | } | ||
21 | |||
10 | #[test] | 22 | #[test] |
11 | fn test_highlighting() { | 23 | fn test_highlighting() { |
12 | let (analysis, file_id) = single_file( | 24 | check_highlighting( |
13 | r#" | 25 | r#" |
14 | #[derive(Clone, Debug)] | 26 | #[derive(Clone, Debug)] |
15 | struct Foo { | 27 | struct Foo { |
@@ -84,17 +96,14 @@ impl<T> Option<T> { | |||
84 | } | 96 | } |
85 | "# | 97 | "# |
86 | .trim(), | 98 | .trim(), |
99 | "crates/ra_ide/src/snapshots/highlighting.html", | ||
100 | false, | ||
87 | ); | 101 | ); |
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 | } | 102 | } |
94 | 103 | ||
95 | #[test] | 104 | #[test] |
96 | fn test_rainbow_highlighting() { | 105 | fn test_rainbow_highlighting() { |
97 | let (analysis, file_id) = single_file( | 106 | check_highlighting( |
98 | r#" | 107 | r#" |
99 | fn main() { | 108 | fn main() { |
100 | let hello = "hello"; | 109 | let hello = "hello"; |
@@ -110,12 +119,9 @@ fn bar() { | |||
110 | } | 119 | } |
111 | "# | 120 | "# |
112 | .trim(), | 121 | .trim(), |
122 | "crates/ra_ide/src/snapshots/rainbow_highlighting.html", | ||
123 | true, | ||
113 | ); | 124 | ); |
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 | } | 125 | } |
120 | 126 | ||
121 | #[test] | 127 | #[test] |
@@ -153,7 +159,7 @@ fn test_ranges() { | |||
153 | 159 | ||
154 | #[test] | 160 | #[test] |
155 | fn test_flattening() { | 161 | fn test_flattening() { |
156 | let (analysis, file_id) = single_file( | 162 | check_highlighting( |
157 | r##" | 163 | r##" |
158 | fn fixture(ra_fixture: &str) {} | 164 | fn fixture(ra_fixture: &str) {} |
159 | 165 | ||
@@ -167,13 +173,9 @@ fn main() { | |||
167 | ); | 173 | ); |
168 | }"## | 174 | }"## |
169 | .trim(), | 175 | .trim(), |
176 | "crates/ra_ide/src/snapshots/highlight_injection.html", | ||
177 | false, | ||
170 | ); | 178 | ); |
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 | } | 179 | } |
178 | 180 | ||
179 | #[test] | 181 | #[test] |
@@ -192,7 +194,7 @@ macro_rules! test {} | |||
192 | fn test_string_highlighting() { | 194 | fn test_string_highlighting() { |
193 | // The format string detection is based on macro-expansion, | 195 | // The format string detection is based on macro-expansion, |
194 | // thus, we have to copy the macro definition from `std` | 196 | // thus, we have to copy the macro definition from `std` |
195 | let (analysis, file_id) = single_file( | 197 | check_highlighting( |
196 | r#" | 198 | r#" |
197 | macro_rules! println { | 199 | macro_rules! println { |
198 | ($($arg:tt)*) => ({ | 200 | ($($arg:tt)*) => ({ |
@@ -250,18 +252,14 @@ fn main() { | |||
250 | println!("{ничоси}", ничоси = 92); | 252 | println!("{ничоси}", ничоси = 92); |
251 | }"# | 253 | }"# |
252 | .trim(), | 254 | .trim(), |
255 | "crates/ra_ide/src/snapshots/highlight_strings.html", | ||
256 | false, | ||
253 | ); | 257 | ); |
254 | |||
255 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); | ||
256 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
257 | let expected_html = &read_text(&dst_file); | ||
258 | fs::write(dst_file, &actual_html).unwrap(); | ||
259 | assert_eq_text!(expected_html, actual_html); | ||
260 | } | 258 | } |
261 | 259 | ||
262 | #[test] | 260 | #[test] |
263 | fn test_unsafe_highlighting() { | 261 | fn test_unsafe_highlighting() { |
264 | let (analysis, file_id) = single_file( | 262 | check_highlighting( |
265 | r#" | 263 | r#" |
266 | unsafe fn unsafe_fn() {} | 264 | unsafe fn unsafe_fn() {} |
267 | 265 | ||
@@ -276,16 +274,63 @@ fn main() { | |||
276 | unsafe { | 274 | unsafe { |
277 | unsafe_fn(); | 275 | unsafe_fn(); |
278 | HasUnsafeFn.unsafe_method(); | 276 | HasUnsafeFn.unsafe_method(); |
279 | let y = *x; | 277 | let y = *(x); |
280 | let z = -x; | 278 | let z = -x; |
281 | } | 279 | } |
282 | } | 280 | } |
283 | "# | 281 | "# |
284 | .trim(), | 282 | .trim(), |
283 | "crates/ra_ide/src/snapshots/highlight_unsafe.html", | ||
284 | false, | ||
285 | ); | 285 | ); |
286 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); | 286 | } |
287 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | 287 | |
288 | let expected_html = &read_text(&dst_file); | 288 | #[test] |
289 | fs::write(dst_file, &actual_html).unwrap(); | 289 | fn test_highlight_doctest() { |
290 | assert_eq_text!(expected_html, actual_html); | 290 | check_highlighting( |
291 | r#" | ||
292 | impl Foo { | ||
293 | /// Constructs a new `Foo`. | ||
294 | /// | ||
295 | /// # Examples | ||
296 | /// | ||
297 | /// ``` | ||
298 | /// # #![allow(unused_mut)] | ||
299 | /// let mut foo: Foo = Foo::new(); | ||
300 | /// ``` | ||
301 | pub const fn new() -> Foo { | ||
302 | Foo { } | ||
303 | } | ||
304 | |||
305 | /// `bar` method on `Foo`. | ||
306 | /// | ||
307 | /// # Examples | ||
308 | /// | ||
309 | /// ``` | ||
310 | /// let foo = Foo::new(); | ||
311 | /// | ||
312 | /// // calls bar on foo | ||
313 | /// assert!(foo.bar()); | ||
314 | /// | ||
315 | /// /* multi-line | ||
316 | /// comment */ | ||
317 | /// | ||
318 | /// let multi_line_string = "Foo | ||
319 | /// bar | ||
320 | /// "; | ||
321 | /// | ||
322 | /// ``` | ||
323 | /// | ||
324 | /// ``` | ||
325 | /// let foobar = Foo::new().bar(); | ||
326 | /// ``` | ||
327 | pub fn foo(&self) -> bool { | ||
328 | true | ||
329 | } | ||
330 | } | ||
331 | "# | ||
332 | .trim(), | ||
333 | "crates/ra_ide/src/snapshots/highlight_doctest.html", | ||
334 | false, | ||
335 | ) | ||
291 | } | 336 | } |