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 | 284 | ||||
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 7 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 41 | ||||
-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 |
15 files changed, 817 insertions, 140 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 9636cd0d6..ad78b7671 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -13,14 +13,52 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, |
17 | FilePosition, RangeInfo, | 17 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | ||
18 | }; | 19 | }; |
20 | use test_utils::mark; | ||
21 | |||
22 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
23 | pub struct HoverConfig { | ||
24 | pub implementations: bool, | ||
25 | pub run: bool, | ||
26 | pub debug: bool, | ||
27 | } | ||
28 | |||
29 | impl Default for HoverConfig { | ||
30 | fn default() -> Self { | ||
31 | Self { implementations: true, run: true, debug: true } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl HoverConfig { | ||
36 | pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; | ||
37 | |||
38 | pub fn any(&self) -> bool { | ||
39 | self.implementations || self.runnable() | ||
40 | } | ||
41 | |||
42 | pub fn none(&self) -> bool { | ||
43 | !self.any() | ||
44 | } | ||
45 | |||
46 | pub fn runnable(&self) -> bool { | ||
47 | self.run || self.debug | ||
48 | } | ||
49 | } | ||
50 | |||
51 | #[derive(Debug, Clone)] | ||
52 | pub enum HoverAction { | ||
53 | Runnable(Runnable), | ||
54 | Implementaion(FilePosition), | ||
55 | } | ||
19 | 56 | ||
20 | /// Contains the results when hovering over an item | 57 | /// Contains the results when hovering over an item |
21 | #[derive(Debug, Default)] | 58 | #[derive(Debug, Default)] |
22 | pub struct HoverResult { | 59 | pub struct HoverResult { |
23 | results: Vec<String>, | 60 | results: Vec<String>, |
61 | actions: Vec<HoverAction>, | ||
24 | } | 62 | } |
25 | 63 | ||
26 | impl HoverResult { | 64 | impl HoverResult { |
@@ -48,10 +86,20 @@ impl HoverResult { | |||
48 | &self.results | 86 | &self.results |
49 | } | 87 | } |
50 | 88 | ||
89 | pub fn actions(&self) -> &[HoverAction] { | ||
90 | &self.actions | ||
91 | } | ||
92 | |||
93 | pub fn push_action(&mut self, action: HoverAction) { | ||
94 | self.actions.push(action); | ||
95 | } | ||
96 | |||
51 | /// Returns the results converted into markup | 97 | /// Returns the results converted into markup |
52 | /// for displaying in a UI | 98 | /// for displaying in a UI |
99 | /// | ||
100 | /// Does not process actions! | ||
53 | pub fn to_markup(&self) -> String { | 101 | pub fn to_markup(&self) -> String { |
54 | self.results.join("\n\n---\n") | 102 | self.results.join("\n\n___\n") |
55 | } | 103 | } |
56 | } | 104 | } |
57 | 105 | ||
@@ -82,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
82 | res.extend(hover_text_from_name_kind(db, name_kind)); | 130 | res.extend(hover_text_from_name_kind(db, name_kind)); |
83 | 131 | ||
84 | if !res.is_empty() { | 132 | if !res.is_empty() { |
133 | if let Some(action) = show_implementations_action(db, name_kind) { | ||
134 | res.push_action(action); | ||
135 | } | ||
136 | |||
137 | if let Some(action) = runnable_action(&sema, name_kind, position.file_id) { | ||
138 | res.push_action(action); | ||
139 | } | ||
140 | |||
85 | return Some(RangeInfo::new(range, res)); | 141 | return Some(RangeInfo::new(range, res)); |
86 | } | 142 | } |
87 | } | 143 | } |
@@ -112,6 +168,56 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
112 | Some(RangeInfo::new(range, res)) | 168 | Some(RangeInfo::new(range, res)) |
113 | } | 169 | } |
114 | 170 | ||
171 | fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { | ||
172 | fn to_action(nav_target: NavigationTarget) -> HoverAction { | ||
173 | HoverAction::Implementaion(FilePosition { | ||
174 | file_id: nav_target.file_id(), | ||
175 | offset: nav_target.range().start(), | ||
176 | }) | ||
177 | } | ||
178 | |||
179 | match def { | ||
180 | Definition::ModuleDef(it) => match it { | ||
181 | ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))), | ||
182 | ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))), | ||
183 | ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))), | ||
184 | ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))), | ||
185 | _ => None, | ||
186 | }, | ||
187 | _ => None, | ||
188 | } | ||
189 | } | ||
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 | |||
115 | fn hover_text( | 221 | fn hover_text( |
116 | docs: Option<String>, | 222 | docs: Option<String>, |
117 | desc: Option<String>, | 223 | desc: Option<String>, |
@@ -228,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
228 | 334 | ||
229 | #[cfg(test)] | 335 | #[cfg(test)] |
230 | mod tests { | 336 | mod tests { |
337 | use super::*; | ||
338 | use insta::assert_debug_snapshot; | ||
339 | |||
231 | use ra_db::FileLoader; | 340 | use ra_db::FileLoader; |
232 | use ra_syntax::TextRange; | 341 | use ra_syntax::TextRange; |
233 | 342 | ||
@@ -241,7 +350,15 @@ mod tests { | |||
241 | s.map(trim_markup) | 350 | s.map(trim_markup) |
242 | } | 351 | } |
243 | 352 | ||
244 | fn check_hover_result(fixture: &str, expected: &[&str]) -> String { | 353 | fn assert_impl_action(action: &HoverAction, position: u32) { |
354 | let offset = match action { | ||
355 | HoverAction::Implementaion(pos) => pos.offset, | ||
356 | it => panic!("Unexpected hover action: {:#?}", it), | ||
357 | }; | ||
358 | assert_eq!(offset, position.into()); | ||
359 | } | ||
360 | |||
361 | fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) { | ||
245 | let (analysis, position) = analysis_and_position(fixture); | 362 | let (analysis, position) = analysis_and_position(fixture); |
246 | let hover = analysis.hover(position).unwrap().unwrap(); | 363 | let hover = analysis.hover(position).unwrap().unwrap(); |
247 | let mut results = Vec::from(hover.info.results()); | 364 | let mut results = Vec::from(hover.info.results()); |
@@ -256,7 +373,7 @@ mod tests { | |||
256 | assert_eq!(hover.info.len(), expected.len()); | 373 | assert_eq!(hover.info.len(), expected.len()); |
257 | 374 | ||
258 | let content = analysis.db.file_text(position.file_id); | 375 | let content = analysis.db.file_text(position.file_id); |
259 | content[hover.range].to_string() | 376 | (content[hover.range].to_string(), hover.info.actions().to_vec()) |
260 | } | 377 | } |
261 | 378 | ||
262 | fn check_hover_no_result(fixture: &str) { | 379 | fn check_hover_no_result(fixture: &str) { |
@@ -457,7 +574,7 @@ struct Test<K, T = u8> { | |||
457 | } | 574 | } |
458 | 575 | ||
459 | fn main() { | 576 | fn main() { |
460 | let zz<|> = Test { t: 23, k: 33 }; | 577 | let zz<|> = Test { t: 23u8, k: 33 }; |
461 | }"#, | 578 | }"#, |
462 | &["Test<i32, u8>"], | 579 | &["Test<i32, u8>"], |
463 | ); | 580 | ); |
@@ -746,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
746 | 863 | ||
747 | #[test] | 864 | #[test] |
748 | fn test_hover_through_macro() { | 865 | fn test_hover_through_macro() { |
749 | let hover_on = check_hover_result( | 866 | let (hover_on, _) = check_hover_result( |
750 | " | 867 | " |
751 | //- /lib.rs | 868 | //- /lib.rs |
752 | macro_rules! id { | 869 | macro_rules! id { |
@@ -767,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
767 | 884 | ||
768 | #[test] | 885 | #[test] |
769 | fn test_hover_through_expr_in_macro() { | 886 | fn test_hover_through_expr_in_macro() { |
770 | let hover_on = check_hover_result( | 887 | let (hover_on, _) = check_hover_result( |
771 | " | 888 | " |
772 | //- /lib.rs | 889 | //- /lib.rs |
773 | macro_rules! id { | 890 | macro_rules! id { |
@@ -785,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
785 | 902 | ||
786 | #[test] | 903 | #[test] |
787 | fn test_hover_through_expr_in_macro_recursive() { | 904 | fn test_hover_through_expr_in_macro_recursive() { |
788 | let hover_on = check_hover_result( | 905 | let (hover_on, _) = check_hover_result( |
789 | " | 906 | " |
790 | //- /lib.rs | 907 | //- /lib.rs |
791 | macro_rules! id_deep { | 908 | macro_rules! id_deep { |
@@ -806,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
806 | 923 | ||
807 | #[test] | 924 | #[test] |
808 | fn test_hover_through_func_in_macro_recursive() { | 925 | fn test_hover_through_func_in_macro_recursive() { |
809 | let hover_on = check_hover_result( | 926 | let (hover_on, _) = check_hover_result( |
810 | " | 927 | " |
811 | //- /lib.rs | 928 | //- /lib.rs |
812 | macro_rules! id_deep { | 929 | macro_rules! id_deep { |
@@ -830,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
830 | 947 | ||
831 | #[test] | 948 | #[test] |
832 | fn test_hover_through_literal_string_in_macro() { | 949 | fn test_hover_through_literal_string_in_macro() { |
833 | let hover_on = check_hover_result( | 950 | let (hover_on, _) = check_hover_result( |
834 | r#" | 951 | r#" |
835 | //- /lib.rs | 952 | //- /lib.rs |
836 | macro_rules! arr { | 953 | macro_rules! arr { |
@@ -849,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
849 | 966 | ||
850 | #[test] | 967 | #[test] |
851 | fn test_hover_through_assert_macro() { | 968 | fn test_hover_through_assert_macro() { |
852 | let hover_on = check_hover_result( | 969 | let (hover_on, _) = check_hover_result( |
853 | r#" | 970 | r#" |
854 | //- /lib.rs | 971 | //- /lib.rs |
855 | #[rustc_builtin_macro] | 972 | #[rustc_builtin_macro] |
@@ -925,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
925 | 1042 | ||
926 | #[test] | 1043 | #[test] |
927 | fn test_hover_trait_show_qualifiers() { | 1044 | fn test_hover_trait_show_qualifiers() { |
928 | check_hover_result( | 1045 | let (_, actions) = check_hover_result( |
929 | " | 1046 | " |
930 | //- /lib.rs | 1047 | //- /lib.rs |
931 | unsafe trait foo<|>() {} | 1048 | unsafe trait foo<|>() {} |
932 | ", | 1049 | ", |
933 | &["unsafe trait foo"], | 1050 | &["unsafe trait foo"], |
934 | ); | 1051 | ); |
1052 | assert_impl_action(&actions[0], 13); | ||
935 | } | 1053 | } |
936 | 1054 | ||
937 | #[test] | 1055 | #[test] |
@@ -1003,6 +1121,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1003 | 1121 | ||
1004 | #[test] | 1122 | #[test] |
1005 | 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 | |||
1006 | check_hover_result( | 1126 | check_hover_result( |
1007 | r#" | 1127 | r#" |
1008 | //- /lib.rs | 1128 | //- /lib.rs |
@@ -1029,6 +1149,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1029 | 1149 | ||
1030 | #[test] | 1150 | #[test] |
1031 | 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 | |||
1032 | check_hover_result( | 1154 | check_hover_result( |
1033 | r#" | 1155 | r#" |
1034 | //- /lib.rs | 1156 | //- /lib.rs |
@@ -1052,4 +1174,140 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1052 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], | 1174 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], |
1053 | ); | 1175 | ); |
1054 | } | 1176 | } |
1177 | |||
1178 | #[test] | ||
1179 | fn test_hover_trait_has_impl_action() { | ||
1180 | let (_, actions) = check_hover_result( | ||
1181 | " | ||
1182 | //- /lib.rs | ||
1183 | trait foo<|>() {} | ||
1184 | ", | ||
1185 | &["trait foo"], | ||
1186 | ); | ||
1187 | assert_impl_action(&actions[0], 6); | ||
1188 | } | ||
1189 | |||
1190 | #[test] | ||
1191 | fn test_hover_struct_has_impl_action() { | ||
1192 | let (_, actions) = check_hover_result( | ||
1193 | " | ||
1194 | //- /lib.rs | ||
1195 | struct foo<|>() {} | ||
1196 | ", | ||
1197 | &["struct foo"], | ||
1198 | ); | ||
1199 | assert_impl_action(&actions[0], 7); | ||
1200 | } | ||
1201 | |||
1202 | #[test] | ||
1203 | fn test_hover_union_has_impl_action() { | ||
1204 | let (_, actions) = check_hover_result( | ||
1205 | " | ||
1206 | //- /lib.rs | ||
1207 | union foo<|>() {} | ||
1208 | ", | ||
1209 | &["union foo"], | ||
1210 | ); | ||
1211 | assert_impl_action(&actions[0], 6); | ||
1212 | } | ||
1213 | |||
1214 | #[test] | ||
1215 | fn test_hover_enum_has_impl_action() { | ||
1216 | let (_, actions) = check_hover_result( | ||
1217 | " | ||
1218 | //- /lib.rs | ||
1219 | enum foo<|>() { | ||
1220 | A, | ||
1221 | B | ||
1222 | } | ||
1223 | ", | ||
1224 | &["enum foo"], | ||
1225 | ); | ||
1226 | assert_impl_action(&actions[0], 5); | ||
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 | } | ||
1055 | } | 1313 | } |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 75bd3c96b..7eb2cef73 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -149,11 +149,10 @@ fn get_param_name_hints( | |||
149 | ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), | 149 | ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), |
150 | _ => return None, | 150 | _ => return None, |
151 | }; | 151 | }; |
152 | let args_count = args.clone().count(); | ||
153 | 152 | ||
154 | let fn_signature = get_fn_signature(sema, &expr)?; | 153 | let fn_signature = get_fn_signature(sema, &expr)?; |
155 | let n_params_to_skip = | 154 | let n_params_to_skip = |
156 | if fn_signature.has_self_param && fn_signature.parameter_names.len() > args_count { | 155 | if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) { |
157 | 1 | 156 | 1 |
158 | } else { | 157 | } else { |
159 | 0 | 158 | 0 |
@@ -416,7 +415,7 @@ struct Test<K, T = u8> { | |||
416 | } | 415 | } |
417 | 416 | ||
418 | fn main() { | 417 | fn main() { |
419 | let zz = Test { t: 23, k: 33 }; | 418 | let zz = Test { t: 23u8, k: 33 }; |
420 | let zz_ref = &zz; | 419 | let zz_ref = &zz; |
421 | }"#, | 420 | }"#, |
422 | ); | 421 | ); |
@@ -429,7 +428,7 @@ fn main() { | |||
429 | label: "Test<i32>", | 428 | label: "Test<i32>", |
430 | }, | 429 | }, |
431 | InlayHint { | 430 | InlayHint { |
432 | range: 105..111, | 431 | range: 107..113, |
433 | kind: TypeHint, | 432 | kind: TypeHint, |
434 | label: "&Test<i32>", | 433 | label: "&Test<i32>", |
435 | }, | 434 | }, |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 12d5716e8..a56718d3f 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -66,7 +66,7 @@ pub use crate::{ | |||
66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, | 66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, |
67 | expand_macro::ExpandedMacro, | 67 | expand_macro::ExpandedMacro, |
68 | folding_ranges::{Fold, FoldKind}, | 68 | folding_ranges::{Fold, FoldKind}, |
69 | hover::HoverResult, | 69 | hover::{HoverAction, HoverConfig, HoverResult}, |
70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
72 | runnables::{Runnable, RunnableKind, TestId}, | 72 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -77,7 +77,7 @@ pub use crate::{ | |||
77 | }; | 77 | }; |
78 | 78 | ||
79 | pub use hir::Documentation; | 79 | pub use hir::Documentation; |
80 | pub use ra_assists::{AssistConfig, AssistId}; | 80 | pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; |
81 | pub use ra_db::{ | 81 | pub use ra_db::{ |
82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
83 | }; | 83 | }; |
@@ -142,14 +142,6 @@ pub struct AnalysisHost { | |||
142 | db: RootDatabase, | 142 | db: RootDatabase, |
143 | } | 143 | } |
144 | 144 | ||
145 | #[derive(Debug)] | ||
146 | pub struct Assist { | ||
147 | pub id: AssistId, | ||
148 | pub label: String, | ||
149 | pub group_label: Option<String>, | ||
150 | pub source_change: SourceChange, | ||
151 | } | ||
152 | |||
153 | impl AnalysisHost { | 145 | impl AnalysisHost { |
154 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { | 146 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { |
155 | AnalysisHost { db: RootDatabase::new(lru_capacity) } | 147 | AnalysisHost { db: RootDatabase::new(lru_capacity) } |
@@ -470,20 +462,23 @@ impl Analysis { | |||
470 | self.with_db(|db| completion::completions(db, config, position).map(Into::into)) | 462 | self.with_db(|db| completion::completions(db, config, position).map(Into::into)) |
471 | } | 463 | } |
472 | 464 | ||
473 | /// Computes assists (aka code actions aka intentions) for the given | 465 | /// Computes resolved assists with source changes for the given position. |
466 | pub fn resolved_assists( | ||
467 | &self, | ||
468 | config: &AssistConfig, | ||
469 | frange: FileRange, | ||
470 | ) -> Cancelable<Vec<ResolvedAssist>> { | ||
471 | self.with_db(|db| ra_assists::Assist::resolved(db, config, frange)) | ||
472 | } | ||
473 | |||
474 | /// Computes unresolved assists (aka code actions aka intentions) for the given | ||
474 | /// position. | 475 | /// position. |
475 | pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> { | 476 | pub fn unresolved_assists( |
476 | self.with_db(|db| { | 477 | &self, |
477 | ra_assists::Assist::resolved(db, config, frange) | 478 | config: &AssistConfig, |
478 | .into_iter() | 479 | frange: FileRange, |
479 | .map(|assist| Assist { | 480 | ) -> Cancelable<Vec<Assist>> { |
480 | id: assist.assist.id, | 481 | self.with_db(|db| Assist::unresolved(db, config, frange)) |
481 | label: assist.assist.label, | ||
482 | group_label: assist.assist.group.map(|it| it.0), | ||
483 | source_change: assist.source_change, | ||
484 | }) | ||
485 | .collect() | ||
486 | }) | ||
487 | } | 482 | } |
488 | 483 | ||
489 | /// Computes the set of diagnostics for the given file. | 484 | /// Computes the set of diagnostics for the given file. |
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 | } |