aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/goto_definition.rs26
-rw-r--r--crates/ra_ide/src/hover.rs284
-rw-r--r--crates/ra_ide/src/inlay_hints.rs7
-rw-r--r--crates/ra_ide/src/lib.rs41
-rw-r--r--crates/ra_ide/src/runnables.rs92
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html71
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html3
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html5
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html5
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html3
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html3
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs135
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs3
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs168
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs111
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 @@
1use hir::Semantics; 1use hir::Semantics;
2use ra_ide_db::{ 2use 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};
6use ra_syntax::{ 6use 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::{
13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14 14
15use crate::{ 15use 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};
20use test_utils::mark;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct HoverConfig {
24 pub implementations: bool,
25 pub run: bool,
26 pub debug: bool,
27}
28
29impl Default for HoverConfig {
30 fn default() -> Self {
31 Self { implementations: true, run: true, debug: true }
32 }
33}
34
35impl 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)]
52pub 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)]
22pub struct HoverResult { 59pub struct HoverResult {
23 results: Vec<String>, 60 results: Vec<String>,
61 actions: Vec<HoverAction>,
24} 62}
25 63
26impl HoverResult { 64impl 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
171fn 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
191fn 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
115fn hover_text( 221fn 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)]
230mod tests { 336mod 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
459fn main() { 576fn 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
418fn main() { 417fn 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
79pub use hir::Documentation; 79pub use hir::Documentation;
80pub use ra_assists::{AssistConfig, AssistId}; 80pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
81pub use ra_db::{ 81pub 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)]
146pub struct Assist {
147 pub id: AssistId,
148 pub label: String,
149 pub group_label: Option<String>,
150 pub source_change: SourceChange,
151}
152
153impl AnalysisHost { 145impl 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
12use crate::{display::ToNav, FileId, NavigationTarget}; 12use crate::{display::ToNav, FileId, NavigationTarget};
13 13
14#[derive(Debug)] 14#[derive(Debug, Clone)]
15pub struct Runnable { 15pub 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)]
22pub enum TestId { 22pub 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)]
37pub enum RunnableKind { 37pub 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)]
46pub struct RunnableAction {
47 pub run_title: &'static str,
48 pub debugee: bool,
49}
50
51const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
52const DOCTEST: RunnableAction =
53 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
54const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
55const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
56
57impl 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
62fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { 98pub(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)]
139pub struct TestAttr { 179pub 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>
3body { margin: 0; }
4pre { 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>() -&gt; <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>) -&gt; <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">&gt;</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">&gt;</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 @@
1mod tags; 1mod tags;
2mod html; 2mod html;
3mod injection;
3#[cfg(test)] 4#[cfg(test)]
4mod tests; 5mod tests;
5 6
@@ -10,14 +11,14 @@ use ra_ide_db::{
10}; 11};
11use ra_prof::profile; 12use ra_prof::profile;
12use ra_syntax::{ 13use 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};
18use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
19 20
20use crate::{call_info::ActiveParameter, Analysis, FileId}; 21use crate::FileId;
21 22
22use ast::FormatSpecifier; 23use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html; 24pub(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
543fn 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
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy;
9
10use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase};
11
12use super::HighlightedRangeStack;
13
14pub(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
54type 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.
61pub(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.
131pub(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.
13fn 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]
11fn test_highlighting() { 23fn test_highlighting() {
12 let (analysis, file_id) = single_file( 24 check_highlighting(
13 r#" 25 r#"
14#[derive(Clone, Debug)] 26#[derive(Clone, Debug)]
15struct Foo { 27struct 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]
96fn test_rainbow_highlighting() { 105fn test_rainbow_highlighting() {
97 let (analysis, file_id) = single_file( 106 check_highlighting(
98 r#" 107 r#"
99fn main() { 108fn 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]
155fn test_flattening() { 161fn test_flattening() {
156 let (analysis, file_id) = single_file( 162 check_highlighting(
157 r##" 163 r##"
158fn fixture(ra_fixture: &str) {} 164fn 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 {}
192fn test_string_highlighting() { 194fn 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#"
197macro_rules! println { 199macro_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]
263fn test_unsafe_highlighting() { 261fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file( 262 check_highlighting(
265 r#" 263 r#"
266unsafe fn unsafe_fn() {} 264unsafe 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(); 289fn test_highlight_doctest() {
290 assert_eq_text!(expected_html, actual_html); 290 check_highlighting(
291 r#"
292impl 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}