aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/diagnostics.rs118
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs23
-rw-r--r--crates/ra_ide/src/hover.rs1142
-rw-r--r--crates/ra_ide/src/lib.rs12
-rw-r--r--crates/ra_ide/src/prime_caches.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs266
-rw-r--r--crates/ra_ide/src/runnables.rs18
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html91
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html8
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html2
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/ssr.rs601
-rw-r--r--crates/ra_ide/src/status.rs15
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs97
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs24
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs5
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs33
21 files changed, 1678 insertions, 790 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 05c940605..bbc6a5c9b 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -29,6 +29,7 @@ ra_fmt = { path = "../ra_fmt" }
29ra_prof = { path = "../ra_prof" } 29ra_prof = { path = "../ra_prof" }
30test_utils = { path = "../test_utils" } 30test_utils = { path = "../test_utils" }
31ra_assists = { path = "../ra_assists" } 31ra_assists = { path = "../ra_assists" }
32ra_ssr = { path = "../ra_ssr" }
32 33
33# ra_ide should depend only on the top-level `hir` package. if you need 34# ra_ide should depend only on the top-level `hir` package. if you need
34# something from some `hir_xxx` subpackage, reexport the API via `hir`. 35# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index e1bfd72f9..a88a978d7 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -8,15 +8,15 @@ use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, 10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink},
11 Semantics, 11 HasSource, HirDisplay, Semantics, VariantDef,
12}; 12};
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; 14use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 15use ra_ide_db::RootDatabase;
16use ra_prof::profile; 16use ra_prof::profile;
17use ra_syntax::{ 17use ra_syntax::{
18 algo, 18 algo,
19 ast::{self, make, AstNode}, 19 ast::{self, edit::IndentLevel, make, AstNode},
20 SyntaxNode, TextRange, T, 20 SyntaxNode, TextRange, T,
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
@@ -57,14 +57,10 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
57 }) 57 })
58 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 58 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
59 let original_file = d.source().file_id.original_file(db); 59 let original_file = d.source().file_id.original_file(db);
60 let source_root = db.file_source_root(original_file); 60 let fix = Fix::new(
61 let path = db 61 "Create module",
62 .file_relative_path(original_file) 62 FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(),
63 .parent() 63 );
64 .unwrap_or_else(|| RelativePath::new(""))
65 .join(&d.candidate);
66 let fix =
67 Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into());
68 res.borrow_mut().push(Diagnostic { 64 res.borrow_mut().push(Diagnostic {
69 range: sema.diagnostics_range(d).range, 65 range: sema.diagnostics_range(d).range,
70 message: d.message(), 66 message: d.message(),
@@ -123,7 +119,16 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
123 severity: Severity::Error, 119 severity: Severity::Error,
124 fix: Some(fix), 120 fix: Some(fix),
125 }) 121 })
122 })
123 .on::<hir::diagnostics::NoSuchField, _>(|d| {
124 res.borrow_mut().push(Diagnostic {
125 range: sema.diagnostics_range(d).range,
126 message: d.message(),
127 severity: Severity::Error,
128 fix: missing_struct_field_fix(&sema, file_id, d),
129 })
126 }); 130 });
131
127 if let Some(m) = sema.to_module_def(file_id) { 132 if let Some(m) = sema.to_module_def(file_id) {
128 m.diagnostics(db, &mut sink); 133 m.diagnostics(db, &mut sink);
129 }; 134 };
@@ -131,6 +136,68 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
131 res.into_inner() 136 res.into_inner()
132} 137}
133 138
139fn missing_struct_field_fix(
140 sema: &Semantics<RootDatabase>,
141 file_id: FileId,
142 d: &hir::diagnostics::NoSuchField,
143) -> Option<Fix> {
144 let record_expr = sema.ast(d);
145
146 let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?;
147 let def_id = sema.resolve_variant(record_lit)?;
148 let module;
149 let record_fields = match VariantDef::from(def_id) {
150 VariantDef::Struct(s) => {
151 module = s.module(sema.db);
152 let source = s.source(sema.db);
153 let fields = source.value.field_def_list()?;
154 record_field_def_list(fields)?
155 }
156 VariantDef::Union(u) => {
157 module = u.module(sema.db);
158 let source = u.source(sema.db);
159 source.value.record_field_def_list()?
160 }
161 VariantDef::EnumVariant(e) => {
162 module = e.module(sema.db);
163 let source = e.source(sema.db);
164 let fields = source.value.field_def_list()?;
165 record_field_def_list(fields)?
166 }
167 };
168
169 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
170 let new_field = make::record_field_def(
171 record_expr.field_name()?,
172 make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
173 );
174
175 let last_field = record_fields.fields().last()?;
176 let last_field_syntax = last_field.syntax();
177 let indent = IndentLevel::from_node(last_field_syntax);
178
179 let mut new_field = format!("\n{}{}", indent, new_field);
180
181 let needs_comma = !last_field_syntax.to_string().ends_with(",");
182 if needs_comma {
183 new_field = format!(",{}", new_field);
184 }
185
186 let source_change = SourceFileEdit {
187 file_id,
188 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
189 };
190 let fix = Fix::new("Create field", source_change.into());
191 return Some(fix);
192
193 fn record_field_def_list(field_def_list: ast::FieldDefList) -> Option<ast::RecordFieldDefList> {
194 match field_def_list {
195 ast::FieldDefList::RecordFieldDefList(it) => Some(it),
196 ast::FieldDefList::TupleFieldDefList(_) => None,
197 }
198 }
199}
200
134fn check_unnecessary_braces_in_use_statement( 201fn check_unnecessary_braces_in_use_statement(
135 acc: &mut Vec<Diagnostic>, 202 acc: &mut Vec<Diagnostic>,
136 file_id: FileId, 203 file_id: FileId,
@@ -612,10 +679,10 @@ mod tests {
612 source_file_edits: [], 679 source_file_edits: [],
613 file_system_edits: [ 680 file_system_edits: [
614 CreateFile { 681 CreateFile {
615 source_root: SourceRootId( 682 anchor: FileId(
616 0, 683 1,
617 ), 684 ),
618 path: "foo.rs", 685 dst: "foo.rs",
619 }, 686 },
620 ], 687 ],
621 is_snippet: false, 688 is_snippet: false,
@@ -795,4 +862,27 @@ fn main() {
795 check_struct_shorthand_initialization, 862 check_struct_shorthand_initialization,
796 ); 863 );
797 } 864 }
865
866 #[test]
867 fn test_add_field_from_usage() {
868 check_apply_diagnostic_fix(
869 r"
870 fn main() {
871 Foo { bar: 3, baz: false};
872 }
873 struct Foo {
874 bar: i32
875 }
876 ",
877 r"
878 fn main() {
879 Foo { bar: 3, baz: false};
880 }
881 struct Foo {
882 bar: i32,
883 baz: bool
884 }
885 ",
886 )
887 }
798} 888}
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index c7bb1e69f..0b52b01ab 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -135,8 +135,8 @@ impl NavigationTarget {
135 db: &RootDatabase, 135 db: &RootDatabase,
136 node: InFile<&dyn ast::NameOwner>, 136 node: InFile<&dyn ast::NameOwner>,
137 ) -> NavigationTarget { 137 ) -> NavigationTarget {
138 //FIXME: use `_` instead of empty string 138 let name =
139 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); 139 node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
140 let focus_range = 140 let focus_range =
141 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); 141 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
142 let frange = original_range(db, node.map(|it| it.syntax())); 142 let frange = original_range(db, node.map(|it| it.syntax()));
@@ -150,6 +150,25 @@ impl NavigationTarget {
150 ) 150 )
151 } 151 }
152 152
153 /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
154 pub(crate) fn from_doc_commented(
155 db: &RootDatabase,
156 named: InFile<&dyn ast::NameOwner>,
157 node: InFile<&dyn ast::DocCommentsOwner>,
158 ) -> NavigationTarget {
159 let name =
160 named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
161 let frange = original_range(db, node.map(|it| it.syntax()));
162
163 NavigationTarget::from_syntax(
164 frange.file_id,
165 name,
166 None,
167 frange.range,
168 node.value.syntax().kind(),
169 )
170 }
171
153 fn from_syntax( 172 fn from_syntax(
154 file_id: FileId, 173 file_id: FileId,
155 name: SmolStr, 174 name: SmolStr,
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index ad78b7671..d870e4cbc 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -2,7 +2,7 @@ use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
5 ModuleDef, ModuleSource, Semantics, 5 Module, ModuleDef, ModuleSource, Semantics,
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -13,7 +13,9 @@ 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, ToNav}, 16 display::{
17 macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav,
18 },
17 runnables::runnable, 19 runnables::runnable,
18 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
19}; 21};
@@ -24,19 +26,21 @@ pub struct HoverConfig {
24 pub implementations: bool, 26 pub implementations: bool,
25 pub run: bool, 27 pub run: bool,
26 pub debug: bool, 28 pub debug: bool,
29 pub goto_type_def: bool,
27} 30}
28 31
29impl Default for HoverConfig { 32impl Default for HoverConfig {
30 fn default() -> Self { 33 fn default() -> Self {
31 Self { implementations: true, run: true, debug: true } 34 Self { implementations: true, run: true, debug: true, goto_type_def: true }
32 } 35 }
33} 36}
34 37
35impl HoverConfig { 38impl HoverConfig {
36 pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; 39 pub const NO_ACTIONS: Self =
40 Self { implementations: false, run: false, debug: false, goto_type_def: false };
37 41
38 pub fn any(&self) -> bool { 42 pub fn any(&self) -> bool {
39 self.implementations || self.runnable() 43 self.implementations || self.runnable() || self.goto_type_def
40 } 44 }
41 45
42 pub fn none(&self) -> bool { 46 pub fn none(&self) -> bool {
@@ -52,6 +56,13 @@ impl HoverConfig {
52pub enum HoverAction { 56pub enum HoverAction {
53 Runnable(Runnable), 57 Runnable(Runnable),
54 Implementaion(FilePosition), 58 Implementaion(FilePosition),
59 GoToType(Vec<HoverGotoTypeData>),
60}
61
62#[derive(Debug, Clone, Eq, PartialEq)]
63pub struct HoverGotoTypeData {
64 pub mod_path: String,
65 pub nav: NavigationTarget,
55} 66}
56 67
57/// Contains the results when hovering over an item 68/// Contains the results when hovering over an item
@@ -138,6 +149,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
138 res.push_action(action); 149 res.push_action(action);
139 } 150 }
140 151
152 if let Some(action) = goto_type_action(db, name_kind) {
153 res.push_action(action);
154 }
155
141 return Some(RangeInfo::new(range, res)); 156 return Some(RangeInfo::new(range, res));
142 } 157 }
143 } 158 }
@@ -218,6 +233,44 @@ fn runnable_action(
218 } 233 }
219} 234}
220 235
236fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
237 match def {
238 Definition::Local(it) => {
239 let mut targets: Vec<ModuleDef> = Vec::new();
240 let mut push_new_def = |item: ModuleDef| {
241 if !targets.contains(&item) {
242 targets.push(item);
243 }
244 };
245
246 it.ty(db).walk(db, |t| {
247 if let Some(adt) = t.as_adt() {
248 push_new_def(adt.into());
249 } else if let Some(trait_) = t.as_dyn_trait() {
250 push_new_def(trait_.into());
251 } else if let Some(traits) = t.as_impl_traits(db) {
252 traits.into_iter().for_each(|it| push_new_def(it.into()));
253 } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
254 push_new_def(trait_.into());
255 }
256 });
257
258 let targets = targets
259 .into_iter()
260 .filter_map(|it| {
261 Some(HoverGotoTypeData {
262 mod_path: mod_path(db, &it)?,
263 nav: it.try_to_nav(db)?,
264 })
265 })
266 .collect();
267
268 Some(HoverAction::GoToType(targets))
269 }
270 _ => None,
271 }
272}
273
221fn hover_text( 274fn hover_text(
222 docs: Option<String>, 275 docs: Option<String>,
223 desc: Option<String>, 276 desc: Option<String>,
@@ -248,25 +301,31 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
248 .map(|name| name.to_string()) 301 .map(|name| name.to_string())
249} 302}
250 303
251fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { 304fn determine_mod_path(db: &RootDatabase, module: Module, name: Option<String>) -> String {
252 let mod_path = def.module(db).map(|module| { 305 once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string))
253 once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) 306 .chain(
254 .chain( 307 module
255 module 308 .path_to_root(db)
256 .path_to_root(db) 309 .into_iter()
257 .into_iter() 310 .rev()
258 .rev() 311 .map(|it| it.name(db).map(|name| name.to_string())),
259 .map(|it| it.name(db).map(|name| name.to_string())), 312 )
260 ) 313 .chain(once(name))
261 .chain(once(definition_owner_name(db, def))) 314 .flatten()
262 .flatten() 315 .join("::")
263 .join("::") 316}
264 }); 317
265 mod_path 318// returns None only for ModuleDef::BuiltinType
319fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option<String> {
320 Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string())))
321}
322
323fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
324 def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def)))
266} 325}
267 326
268fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { 327fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> {
269 let mod_path = determine_mod_path(db, &def); 328 let mod_path = definition_mod_path(db, &def);
270 return match def { 329 return match def {
271 Definition::Macro(it) => { 330 Definition::Macro(it) => {
272 let src = it.source(db); 331 let src = it.source(db);
@@ -1310,4 +1369,1045 @@ fn func(foo: i32) { if true { <|>foo; }; }
1310 ] 1369 ]
1311 "###); 1370 "###);
1312 } 1371 }
1372
1373 #[test]
1374 fn test_hover_struct_has_goto_type_action() {
1375 let (_, actions) = check_hover_result(
1376 "
1377 //- /main.rs
1378 struct S{ f1: u32 }
1379
1380 fn main() {
1381 let s<|>t = S{ f1:0 };
1382 }
1383 ",
1384 &["S"],
1385 );
1386 assert_debug_snapshot!(actions,
1387 @r###"
1388 [
1389 GoToType(
1390 [
1391 HoverGotoTypeData {
1392 mod_path: "S",
1393 nav: NavigationTarget {
1394 file_id: FileId(
1395 1,
1396 ),
1397 full_range: 0..19,
1398 name: "S",
1399 kind: STRUCT_DEF,
1400 focus_range: Some(
1401 7..8,
1402 ),
1403 container_name: None,
1404 description: Some(
1405 "struct S",
1406 ),
1407 docs: None,
1408 },
1409 },
1410 ],
1411 ),
1412 ]
1413 "###);
1414 }
1415
1416 #[test]
1417 fn test_hover_generic_struct_has_goto_type_actions() {
1418 let (_, actions) = check_hover_result(
1419 "
1420 //- /main.rs
1421 struct Arg(u32);
1422 struct S<T>{ f1: T }
1423
1424 fn main() {
1425 let s<|>t = S{ f1:Arg(0) };
1426 }
1427 ",
1428 &["S<Arg>"],
1429 );
1430 assert_debug_snapshot!(actions,
1431 @r###"
1432 [
1433 GoToType(
1434 [
1435 HoverGotoTypeData {
1436 mod_path: "S",
1437 nav: NavigationTarget {
1438 file_id: FileId(
1439 1,
1440 ),
1441 full_range: 17..37,
1442 name: "S",
1443 kind: STRUCT_DEF,
1444 focus_range: Some(
1445 24..25,
1446 ),
1447 container_name: None,
1448 description: Some(
1449 "struct S",
1450 ),
1451 docs: None,
1452 },
1453 },
1454 HoverGotoTypeData {
1455 mod_path: "Arg",
1456 nav: NavigationTarget {
1457 file_id: FileId(
1458 1,
1459 ),
1460 full_range: 0..16,
1461 name: "Arg",
1462 kind: STRUCT_DEF,
1463 focus_range: Some(
1464 7..10,
1465 ),
1466 container_name: None,
1467 description: Some(
1468 "struct Arg",
1469 ),
1470 docs: None,
1471 },
1472 },
1473 ],
1474 ),
1475 ]
1476 "###);
1477 }
1478
1479 #[test]
1480 fn test_hover_generic_struct_has_flattened_goto_type_actions() {
1481 let (_, actions) = check_hover_result(
1482 "
1483 //- /main.rs
1484 struct Arg(u32);
1485 struct S<T>{ f1: T }
1486
1487 fn main() {
1488 let s<|>t = S{ f1: S{ f1: Arg(0) } };
1489 }
1490 ",
1491 &["S<S<Arg>>"],
1492 );
1493 assert_debug_snapshot!(actions,
1494 @r###"
1495 [
1496 GoToType(
1497 [
1498 HoverGotoTypeData {
1499 mod_path: "S",
1500 nav: NavigationTarget {
1501 file_id: FileId(
1502 1,
1503 ),
1504 full_range: 17..37,
1505 name: "S",
1506 kind: STRUCT_DEF,
1507 focus_range: Some(
1508 24..25,
1509 ),
1510 container_name: None,
1511 description: Some(
1512 "struct S",
1513 ),
1514 docs: None,
1515 },
1516 },
1517 HoverGotoTypeData {
1518 mod_path: "Arg",
1519 nav: NavigationTarget {
1520 file_id: FileId(
1521 1,
1522 ),
1523 full_range: 0..16,
1524 name: "Arg",
1525 kind: STRUCT_DEF,
1526 focus_range: Some(
1527 7..10,
1528 ),
1529 container_name: None,
1530 description: Some(
1531 "struct Arg",
1532 ),
1533 docs: None,
1534 },
1535 },
1536 ],
1537 ),
1538 ]
1539 "###);
1540 }
1541
1542 #[test]
1543 fn test_hover_tuple_has_goto_type_actions() {
1544 let (_, actions) = check_hover_result(
1545 "
1546 //- /main.rs
1547 struct A(u32);
1548 struct B(u32);
1549 mod M {
1550 pub struct C(u32);
1551 }
1552
1553 fn main() {
1554 let s<|>t = (A(1), B(2), M::C(3) );
1555 }
1556 ",
1557 &["(A, B, C)"],
1558 );
1559 assert_debug_snapshot!(actions,
1560 @r###"
1561 [
1562 GoToType(
1563 [
1564 HoverGotoTypeData {
1565 mod_path: "A",
1566 nav: NavigationTarget {
1567 file_id: FileId(
1568 1,
1569 ),
1570 full_range: 0..14,
1571 name: "A",
1572 kind: STRUCT_DEF,
1573 focus_range: Some(
1574 7..8,
1575 ),
1576 container_name: None,
1577 description: Some(
1578 "struct A",
1579 ),
1580 docs: None,
1581 },
1582 },
1583 HoverGotoTypeData {
1584 mod_path: "B",
1585 nav: NavigationTarget {
1586 file_id: FileId(
1587 1,
1588 ),
1589 full_range: 15..29,
1590 name: "B",
1591 kind: STRUCT_DEF,
1592 focus_range: Some(
1593 22..23,
1594 ),
1595 container_name: None,
1596 description: Some(
1597 "struct B",
1598 ),
1599 docs: None,
1600 },
1601 },
1602 HoverGotoTypeData {
1603 mod_path: "M::C",
1604 nav: NavigationTarget {
1605 file_id: FileId(
1606 1,
1607 ),
1608 full_range: 42..60,
1609 name: "C",
1610 kind: STRUCT_DEF,
1611 focus_range: Some(
1612 53..54,
1613 ),
1614 container_name: None,
1615 description: Some(
1616 "pub struct C",
1617 ),
1618 docs: None,
1619 },
1620 },
1621 ],
1622 ),
1623 ]
1624 "###);
1625 }
1626
1627 #[test]
1628 fn test_hover_return_impl_trait_has_goto_type_action() {
1629 let (_, actions) = check_hover_result(
1630 "
1631 //- /main.rs
1632 trait Foo {}
1633
1634 fn foo() -> impl Foo {}
1635
1636 fn main() {
1637 let s<|>t = foo();
1638 }
1639 ",
1640 &["impl Foo"],
1641 );
1642 assert_debug_snapshot!(actions,
1643 @r###"
1644 [
1645 GoToType(
1646 [
1647 HoverGotoTypeData {
1648 mod_path: "Foo",
1649 nav: NavigationTarget {
1650 file_id: FileId(
1651 1,
1652 ),
1653 full_range: 0..12,
1654 name: "Foo",
1655 kind: TRAIT_DEF,
1656 focus_range: Some(
1657 6..9,
1658 ),
1659 container_name: None,
1660 description: Some(
1661 "trait Foo",
1662 ),
1663 docs: None,
1664 },
1665 },
1666 ],
1667 ),
1668 ]
1669 "###);
1670 }
1671
1672 #[test]
1673 fn test_hover_generic_return_impl_trait_has_goto_type_action() {
1674 let (_, actions) = check_hover_result(
1675 "
1676 //- /main.rs
1677 trait Foo<T> {}
1678 struct S;
1679
1680 fn foo() -> impl Foo<S> {}
1681
1682 fn main() {
1683 let s<|>t = foo();
1684 }
1685 ",
1686 &["impl Foo<S>"],
1687 );
1688 assert_debug_snapshot!(actions,
1689 @r###"
1690 [
1691 GoToType(
1692 [
1693 HoverGotoTypeData {
1694 mod_path: "Foo",
1695 nav: NavigationTarget {
1696 file_id: FileId(
1697 1,
1698 ),
1699 full_range: 0..15,
1700 name: "Foo",
1701 kind: TRAIT_DEF,
1702 focus_range: Some(
1703 6..9,
1704 ),
1705 container_name: None,
1706 description: Some(
1707 "trait Foo",
1708 ),
1709 docs: None,
1710 },
1711 },
1712 HoverGotoTypeData {
1713 mod_path: "S",
1714 nav: NavigationTarget {
1715 file_id: FileId(
1716 1,
1717 ),
1718 full_range: 16..25,
1719 name: "S",
1720 kind: STRUCT_DEF,
1721 focus_range: Some(
1722 23..24,
1723 ),
1724 container_name: None,
1725 description: Some(
1726 "struct S",
1727 ),
1728 docs: None,
1729 },
1730 },
1731 ],
1732 ),
1733 ]
1734 "###);
1735 }
1736
1737 #[test]
1738 fn test_hover_return_impl_traits_has_goto_type_action() {
1739 let (_, actions) = check_hover_result(
1740 "
1741 //- /main.rs
1742 trait Foo {}
1743 trait Bar {}
1744
1745 fn foo() -> impl Foo + Bar {}
1746
1747 fn main() {
1748 let s<|>t = foo();
1749 }
1750 ",
1751 &["impl Foo + Bar"],
1752 );
1753 assert_debug_snapshot!(actions,
1754 @r###"
1755 [
1756 GoToType(
1757 [
1758 HoverGotoTypeData {
1759 mod_path: "Foo",
1760 nav: NavigationTarget {
1761 file_id: FileId(
1762 1,
1763 ),
1764 full_range: 0..12,
1765 name: "Foo",
1766 kind: TRAIT_DEF,
1767 focus_range: Some(
1768 6..9,
1769 ),
1770 container_name: None,
1771 description: Some(
1772 "trait Foo",
1773 ),
1774 docs: None,
1775 },
1776 },
1777 HoverGotoTypeData {
1778 mod_path: "Bar",
1779 nav: NavigationTarget {
1780 file_id: FileId(
1781 1,
1782 ),
1783 full_range: 13..25,
1784 name: "Bar",
1785 kind: TRAIT_DEF,
1786 focus_range: Some(
1787 19..22,
1788 ),
1789 container_name: None,
1790 description: Some(
1791 "trait Bar",
1792 ),
1793 docs: None,
1794 },
1795 },
1796 ],
1797 ),
1798 ]
1799 "###);
1800 }
1801
1802 #[test]
1803 fn test_hover_generic_return_impl_traits_has_goto_type_action() {
1804 let (_, actions) = check_hover_result(
1805 "
1806 //- /main.rs
1807 trait Foo<T> {}
1808 trait Bar<T> {}
1809 struct S1 {}
1810 struct S2 {}
1811
1812 fn foo() -> impl Foo<S1> + Bar<S2> {}
1813
1814 fn main() {
1815 let s<|>t = foo();
1816 }
1817 ",
1818 &["impl Foo<S1> + Bar<S2>"],
1819 );
1820 assert_debug_snapshot!(actions,
1821 @r###"
1822 [
1823 GoToType(
1824 [
1825 HoverGotoTypeData {
1826 mod_path: "Foo",
1827 nav: NavigationTarget {
1828 file_id: FileId(
1829 1,
1830 ),
1831 full_range: 0..15,
1832 name: "Foo",
1833 kind: TRAIT_DEF,
1834 focus_range: Some(
1835 6..9,
1836 ),
1837 container_name: None,
1838 description: Some(
1839 "trait Foo",
1840 ),
1841 docs: None,
1842 },
1843 },
1844 HoverGotoTypeData {
1845 mod_path: "Bar",
1846 nav: NavigationTarget {
1847 file_id: FileId(
1848 1,
1849 ),
1850 full_range: 16..31,
1851 name: "Bar",
1852 kind: TRAIT_DEF,
1853 focus_range: Some(
1854 22..25,
1855 ),
1856 container_name: None,
1857 description: Some(
1858 "trait Bar",
1859 ),
1860 docs: None,
1861 },
1862 },
1863 HoverGotoTypeData {
1864 mod_path: "S1",
1865 nav: NavigationTarget {
1866 file_id: FileId(
1867 1,
1868 ),
1869 full_range: 32..44,
1870 name: "S1",
1871 kind: STRUCT_DEF,
1872 focus_range: Some(
1873 39..41,
1874 ),
1875 container_name: None,
1876 description: Some(
1877 "struct S1",
1878 ),
1879 docs: None,
1880 },
1881 },
1882 HoverGotoTypeData {
1883 mod_path: "S2",
1884 nav: NavigationTarget {
1885 file_id: FileId(
1886 1,
1887 ),
1888 full_range: 45..57,
1889 name: "S2",
1890 kind: STRUCT_DEF,
1891 focus_range: Some(
1892 52..54,
1893 ),
1894 container_name: None,
1895 description: Some(
1896 "struct S2",
1897 ),
1898 docs: None,
1899 },
1900 },
1901 ],
1902 ),
1903 ]
1904 "###);
1905 }
1906
1907 #[test]
1908 fn test_hover_arg_impl_trait_has_goto_type_action() {
1909 let (_, actions) = check_hover_result(
1910 "
1911 //- /lib.rs
1912 trait Foo {}
1913 fn foo(ar<|>g: &impl Foo) {}
1914 ",
1915 &["&impl Foo"],
1916 );
1917 assert_debug_snapshot!(actions,
1918 @r###"
1919 [
1920 GoToType(
1921 [
1922 HoverGotoTypeData {
1923 mod_path: "Foo",
1924 nav: NavigationTarget {
1925 file_id: FileId(
1926 1,
1927 ),
1928 full_range: 0..12,
1929 name: "Foo",
1930 kind: TRAIT_DEF,
1931 focus_range: Some(
1932 6..9,
1933 ),
1934 container_name: None,
1935 description: Some(
1936 "trait Foo",
1937 ),
1938 docs: None,
1939 },
1940 },
1941 ],
1942 ),
1943 ]
1944 "###);
1945 }
1946
1947 #[test]
1948 fn test_hover_arg_impl_traits_has_goto_type_action() {
1949 let (_, actions) = check_hover_result(
1950 "
1951 //- /lib.rs
1952 trait Foo {}
1953 trait Bar<T> {}
1954 struct S{}
1955
1956 fn foo(ar<|>g: &impl Foo + Bar<S>) {}
1957 ",
1958 &["&impl Foo + Bar<S>"],
1959 );
1960 assert_debug_snapshot!(actions,
1961 @r###"
1962 [
1963 GoToType(
1964 [
1965 HoverGotoTypeData {
1966 mod_path: "Foo",
1967 nav: NavigationTarget {
1968 file_id: FileId(
1969 1,
1970 ),
1971 full_range: 0..12,
1972 name: "Foo",
1973 kind: TRAIT_DEF,
1974 focus_range: Some(
1975 6..9,
1976 ),
1977 container_name: None,
1978 description: Some(
1979 "trait Foo",
1980 ),
1981 docs: None,
1982 },
1983 },
1984 HoverGotoTypeData {
1985 mod_path: "Bar",
1986 nav: NavigationTarget {
1987 file_id: FileId(
1988 1,
1989 ),
1990 full_range: 13..28,
1991 name: "Bar",
1992 kind: TRAIT_DEF,
1993 focus_range: Some(
1994 19..22,
1995 ),
1996 container_name: None,
1997 description: Some(
1998 "trait Bar",
1999 ),
2000 docs: None,
2001 },
2002 },
2003 HoverGotoTypeData {
2004 mod_path: "S",
2005 nav: NavigationTarget {
2006 file_id: FileId(
2007 1,
2008 ),
2009 full_range: 29..39,
2010 name: "S",
2011 kind: STRUCT_DEF,
2012 focus_range: Some(
2013 36..37,
2014 ),
2015 container_name: None,
2016 description: Some(
2017 "struct S",
2018 ),
2019 docs: None,
2020 },
2021 },
2022 ],
2023 ),
2024 ]
2025 "###);
2026 }
2027
2028 #[test]
2029 fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
2030 let (_, actions) = check_hover_result(
2031 "
2032 //- /lib.rs
2033 trait Foo<T> {}
2034 struct S {}
2035 fn foo(ar<|>g: &impl Foo<S>) {}
2036 ",
2037 &["&impl Foo<S>"],
2038 );
2039 assert_debug_snapshot!(actions,
2040 @r###"
2041 [
2042 GoToType(
2043 [
2044 HoverGotoTypeData {
2045 mod_path: "Foo",
2046 nav: NavigationTarget {
2047 file_id: FileId(
2048 1,
2049 ),
2050 full_range: 0..15,
2051 name: "Foo",
2052 kind: TRAIT_DEF,
2053 focus_range: Some(
2054 6..9,
2055 ),
2056 container_name: None,
2057 description: Some(
2058 "trait Foo",
2059 ),
2060 docs: None,
2061 },
2062 },
2063 HoverGotoTypeData {
2064 mod_path: "S",
2065 nav: NavigationTarget {
2066 file_id: FileId(
2067 1,
2068 ),
2069 full_range: 16..27,
2070 name: "S",
2071 kind: STRUCT_DEF,
2072 focus_range: Some(
2073 23..24,
2074 ),
2075 container_name: None,
2076 description: Some(
2077 "struct S",
2078 ),
2079 docs: None,
2080 },
2081 },
2082 ],
2083 ),
2084 ]
2085 "###);
2086 }
2087
2088 #[test]
2089 fn test_hover_dyn_return_has_goto_type_action() {
2090 let (_, actions) = check_hover_result(
2091 "
2092 //- /main.rs
2093 trait Foo {}
2094 struct S;
2095 impl Foo for S {}
2096
2097 struct B<T>{}
2098
2099 fn foo() -> B<dyn Foo> {}
2100
2101 fn main() {
2102 let s<|>t = foo();
2103 }
2104 ",
2105 &["B<dyn Foo>"],
2106 );
2107 assert_debug_snapshot!(actions,
2108 @r###"
2109 [
2110 GoToType(
2111 [
2112 HoverGotoTypeData {
2113 mod_path: "B",
2114 nav: NavigationTarget {
2115 file_id: FileId(
2116 1,
2117 ),
2118 full_range: 41..54,
2119 name: "B",
2120 kind: STRUCT_DEF,
2121 focus_range: Some(
2122 48..49,
2123 ),
2124 container_name: None,
2125 description: Some(
2126 "struct B",
2127 ),
2128 docs: None,
2129 },
2130 },
2131 HoverGotoTypeData {
2132 mod_path: "Foo",
2133 nav: NavigationTarget {
2134 file_id: FileId(
2135 1,
2136 ),
2137 full_range: 0..12,
2138 name: "Foo",
2139 kind: TRAIT_DEF,
2140 focus_range: Some(
2141 6..9,
2142 ),
2143 container_name: None,
2144 description: Some(
2145 "trait Foo",
2146 ),
2147 docs: None,
2148 },
2149 },
2150 ],
2151 ),
2152 ]
2153 "###);
2154 }
2155
2156 #[test]
2157 fn test_hover_dyn_arg_has_goto_type_action() {
2158 let (_, actions) = check_hover_result(
2159 "
2160 //- /lib.rs
2161 trait Foo {}
2162 fn foo(ar<|>g: &dyn Foo) {}
2163 ",
2164 &["&dyn Foo"],
2165 );
2166 assert_debug_snapshot!(actions,
2167 @r###"
2168 [
2169 GoToType(
2170 [
2171 HoverGotoTypeData {
2172 mod_path: "Foo",
2173 nav: NavigationTarget {
2174 file_id: FileId(
2175 1,
2176 ),
2177 full_range: 0..12,
2178 name: "Foo",
2179 kind: TRAIT_DEF,
2180 focus_range: Some(
2181 6..9,
2182 ),
2183 container_name: None,
2184 description: Some(
2185 "trait Foo",
2186 ),
2187 docs: None,
2188 },
2189 },
2190 ],
2191 ),
2192 ]
2193 "###);
2194 }
2195
2196 #[test]
2197 fn test_hover_generic_dyn_arg_has_goto_type_action() {
2198 let (_, actions) = check_hover_result(
2199 "
2200 //- /lib.rs
2201 trait Foo<T> {}
2202 struct S {}
2203 fn foo(ar<|>g: &dyn Foo<S>) {}
2204 ",
2205 &["&dyn Foo<S>"],
2206 );
2207 assert_debug_snapshot!(actions,
2208 @r###"
2209 [
2210 GoToType(
2211 [
2212 HoverGotoTypeData {
2213 mod_path: "Foo",
2214 nav: NavigationTarget {
2215 file_id: FileId(
2216 1,
2217 ),
2218 full_range: 0..15,
2219 name: "Foo",
2220 kind: TRAIT_DEF,
2221 focus_range: Some(
2222 6..9,
2223 ),
2224 container_name: None,
2225 description: Some(
2226 "trait Foo",
2227 ),
2228 docs: None,
2229 },
2230 },
2231 HoverGotoTypeData {
2232 mod_path: "S",
2233 nav: NavigationTarget {
2234 file_id: FileId(
2235 1,
2236 ),
2237 full_range: 16..27,
2238 name: "S",
2239 kind: STRUCT_DEF,
2240 focus_range: Some(
2241 23..24,
2242 ),
2243 container_name: None,
2244 description: Some(
2245 "struct S",
2246 ),
2247 docs: None,
2248 },
2249 },
2250 ],
2251 ),
2252 ]
2253 "###);
2254 }
2255
2256 #[test]
2257 fn test_hover_goto_type_action_links_order() {
2258 let (_, actions) = check_hover_result(
2259 "
2260 //- /lib.rs
2261 trait ImplTrait<T> {}
2262 trait DynTrait<T> {}
2263 struct B<T> {}
2264 struct S {}
2265
2266 fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2267 ",
2268 &["&impl ImplTrait<B<dyn DynTrait<B<S>>>>"],
2269 );
2270 assert_debug_snapshot!(actions,
2271 @r###"
2272 [
2273 GoToType(
2274 [
2275 HoverGotoTypeData {
2276 mod_path: "ImplTrait",
2277 nav: NavigationTarget {
2278 file_id: FileId(
2279 1,
2280 ),
2281 full_range: 0..21,
2282 name: "ImplTrait",
2283 kind: TRAIT_DEF,
2284 focus_range: Some(
2285 6..15,
2286 ),
2287 container_name: None,
2288 description: Some(
2289 "trait ImplTrait",
2290 ),
2291 docs: None,
2292 },
2293 },
2294 HoverGotoTypeData {
2295 mod_path: "B",
2296 nav: NavigationTarget {
2297 file_id: FileId(
2298 1,
2299 ),
2300 full_range: 43..57,
2301 name: "B",
2302 kind: STRUCT_DEF,
2303 focus_range: Some(
2304 50..51,
2305 ),
2306 container_name: None,
2307 description: Some(
2308 "struct B",
2309 ),
2310 docs: None,
2311 },
2312 },
2313 HoverGotoTypeData {
2314 mod_path: "DynTrait",
2315 nav: NavigationTarget {
2316 file_id: FileId(
2317 1,
2318 ),
2319 full_range: 22..42,
2320 name: "DynTrait",
2321 kind: TRAIT_DEF,
2322 focus_range: Some(
2323 28..36,
2324 ),
2325 container_name: None,
2326 description: Some(
2327 "trait DynTrait",
2328 ),
2329 docs: None,
2330 },
2331 },
2332 HoverGotoTypeData {
2333 mod_path: "S",
2334 nav: NavigationTarget {
2335 file_id: FileId(
2336 1,
2337 ),
2338 full_range: 58..69,
2339 name: "S",
2340 kind: STRUCT_DEF,
2341 focus_range: Some(
2342 65..66,
2343 ),
2344 container_name: None,
2345 description: Some(
2346 "struct S",
2347 ),
2348 docs: None,
2349 },
2350 },
2351 ],
2352 ),
2353 ]
2354 "###);
2355 }
2356
2357 #[test]
2358 fn test_hover_associated_type_has_goto_type_action() {
2359 let (_, actions) = check_hover_result(
2360 "
2361 //- /main.rs
2362 trait Foo {
2363 type Item;
2364 fn get(self) -> Self::Item {}
2365 }
2366
2367 struct Bar{}
2368 struct S{}
2369
2370 impl Foo for S{
2371 type Item = Bar;
2372 }
2373
2374 fn test() -> impl Foo {
2375 S{}
2376 }
2377
2378 fn main() {
2379 let s<|>t = test().get();
2380 }
2381 ",
2382 &["Foo::Item<impl Foo>"],
2383 );
2384 assert_debug_snapshot!(actions,
2385 @r###"
2386 [
2387 GoToType(
2388 [
2389 HoverGotoTypeData {
2390 mod_path: "Foo",
2391 nav: NavigationTarget {
2392 file_id: FileId(
2393 1,
2394 ),
2395 full_range: 0..62,
2396 name: "Foo",
2397 kind: TRAIT_DEF,
2398 focus_range: Some(
2399 6..9,
2400 ),
2401 container_name: None,
2402 description: Some(
2403 "trait Foo",
2404 ),
2405 docs: None,
2406 },
2407 },
2408 ],
2409 ),
2410 ]
2411 "###);
2412 }
1313} 2413}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 28f686767..47823718f 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -66,11 +66,10 @@ 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::{HoverAction, HoverConfig, HoverResult}, 69 hover::{HoverAction, HoverConfig, HoverGotoTypeData, 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},
73 ssr::SsrError,
74 syntax_highlighting::{ 73 syntax_highlighting::{
75 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 74 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
76 }, 75 },
@@ -82,13 +81,14 @@ pub use ra_db::{
82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 81 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
83}; 82};
84pub use ra_ide_db::{ 83pub use ra_ide_db::{
85 change::{AnalysisChange, LibraryData}, 84 change::AnalysisChange,
86 line_index::{LineCol, LineIndex}, 85 line_index::{LineCol, LineIndex},
87 search::SearchScope, 86 search::SearchScope,
88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 87 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
89 symbol_index::Query, 88 symbol_index::Query,
90 RootDatabase, 89 RootDatabase,
91}; 90};
91pub use ra_ssr::SsrError;
92pub use ra_text_edit::{Indel, TextEdit}; 92pub use ra_text_edit::{Indel, TextEdit};
93 93
94pub type Cancelable<T> = Result<T, Canceled>; 94pub type Cancelable<T> = Result<T, Canceled>;
@@ -440,12 +440,14 @@ impl Analysis {
440 440
441 /// Computes syntax highlighting for the given file 441 /// Computes syntax highlighting for the given file
442 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 442 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
443 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None)) 443 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
444 } 444 }
445 445
446 /// Computes syntax highlighting for the given file range. 446 /// Computes syntax highlighting for the given file range.
447 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { 447 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> {
448 self.with_db(|db| syntax_highlighting::highlight(db, frange.file_id, Some(frange.range))) 448 self.with_db(|db| {
449 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
450 })
449 } 451 }
450 452
451 /// Computes syntax highlighting for the given file. 453 /// Computes syntax highlighting for the given file.
diff --git a/crates/ra_ide/src/prime_caches.rs b/crates/ra_ide/src/prime_caches.rs
index 90bf7d25f..c5ab5a1d8 100644
--- a/crates/ra_ide/src/prime_caches.rs
+++ b/crates/ra_ide/src/prime_caches.rs
@@ -7,6 +7,6 @@ use crate::{FileId, RootDatabase};
7 7
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { 8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
9 for file in files { 9 for file in files {
10 let _ = crate::syntax_highlighting::highlight(db, file, None); 10 let _ = crate::syntax_highlighting::highlight(db, file, None, false);
11 } 11 }
12} 12}
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 915d4f4d3..99c2581b7 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -1,11 +1,14 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{ModuleSource, Semantics}; 3use hir::{Module, ModuleDef, ModuleSource, Semantics};
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase,
8};
6use ra_syntax::{ 9use ra_syntax::{
7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, 10 algo::find_node_at_offset, ast, ast::NameOwner, ast::TypeAscriptionOwner,
8 AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 11 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
9}; 12};
10use ra_text_edit::TextEdit; 13use ra_text_edit::TextEdit;
11use std::convert::TryInto; 14use std::convert::TryInto;
@@ -30,10 +33,8 @@ pub(crate) fn rename(
30 let sema = Semantics::new(db); 33 let sema = Semantics::new(db);
31 let source_file = sema.parse(position.file_id); 34 let source_file = sema.parse(position.file_id);
32 let syntax = source_file.syntax(); 35 let syntax = source_file.syntax();
33 if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { 36 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
34 let range = ast_name.syntax().text_range(); 37 rename_mod(db, position, module, new_name)
35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
36 .map(|info| RangeInfo::new(range, info))
37 } else if let Some(self_token) = 38 } else if let Some(self_token) =
38 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 39 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
39 { 40 {
@@ -43,13 +44,32 @@ pub(crate) fn rename(
43 } 44 }
44} 45}
45 46
46fn find_name_and_module_at_offset( 47fn find_module_at_offset(
47 syntax: &SyntaxNode, 48 sema: &Semantics<RootDatabase>,
48 position: FilePosition, 49 position: FilePosition,
49) -> Option<(ast::Name, ast::Module)> { 50 syntax: &SyntaxNode,
50 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; 51) -> Option<Module> {
51 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; 52 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
52 Some((ast_name, ast_module)) 53
54 let module = match_ast! {
55 match (ident.parent()) {
56 ast::NameRef(name_ref) => {
57 match classify_name_ref(sema, &name_ref)? {
58 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
59 _ => return None,
60 }
61 },
62 ast::Name(name) => {
63 match classify_name(&sema, &name)? {
64 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
65 _ => return None,
66 }
67 },
68 _ => return None,
69 }
70 };
71
72 Some(module)
53} 73}
54 74
55fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { 75fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
@@ -77,58 +97,50 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
77} 97}
78 98
79fn rename_mod( 99fn rename_mod(
80 sema: &Semantics<RootDatabase>, 100 db: &RootDatabase,
81 ast_name: &ast::Name,
82 ast_module: &ast::Module,
83 position: FilePosition, 101 position: FilePosition,
102 module: Module,
84 new_name: &str, 103 new_name: &str,
85) -> Option<SourceChange> { 104) -> Option<RangeInfo<SourceChange>> {
86 let mut source_file_edits = Vec::new(); 105 let mut source_file_edits = Vec::new();
87 let mut file_system_edits = Vec::new(); 106 let mut file_system_edits = Vec::new();
88 if let Some(module) = sema.to_def(ast_module) { 107
89 let src = module.definition_source(sema.db); 108 let src = module.definition_source(db);
90 let file_id = src.file_id.original_file(sema.db); 109 let file_id = src.file_id.original_file(db);
91 match src.value { 110 match src.value {
92 ModuleSource::SourceFile(..) => { 111 ModuleSource::SourceFile(..) => {
93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); 112 let mod_path: RelativePathBuf = db.file_relative_path(file_id);
94 // mod is defined in path/to/dir/mod.rs 113 // mod is defined in path/to/dir/mod.rs
95 let dst_path = if mod_path.file_stem() == Some("mod") { 114 let dst = if mod_path.file_stem() == Some("mod") {
96 mod_path 115 format!("../{}/mod.rs", new_name)
97 .parent() 116 } else {
98 .and_then(|p| p.parent()) 117 format!("{}.rs", new_name)
99 .or_else(|| Some(RelativePath::new(""))) 118 };
100 .map(|p| p.join(new_name).join("mod.rs")) 119 let move_file =
101 } else { 120 FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst };
102 Some(mod_path.with_file_name(new_name).with_extension("rs")) 121 file_system_edits.push(move_file);
103 };
104 if let Some(path) = dst_path {
105 let move_file = FileSystemEdit::MoveFile {
106 src: file_id,
107 dst_source_root: sema.db.file_source_root(position.file_id),
108 dst_path: path,
109 };
110 file_system_edits.push(move_file);
111 }
112 }
113 ModuleSource::Module(..) => {}
114 } 122 }
123 ModuleSource::Module(..) => {}
115 } 124 }
116 125
117 let edit = SourceFileEdit { 126 if let Some(src) = module.declaration_source(db) {
118 file_id: position.file_id, 127 let file_id = src.file_id.original_file(db);
119 edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), 128 let name = src.value.name()?;
120 }; 129 let edit = SourceFileEdit {
121 source_file_edits.push(edit); 130 file_id: file_id,
122 131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
123 if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { 132 };
124 let ref_edits = refs 133 source_file_edits.push(edit);
125 .references
126 .into_iter()
127 .map(|reference| source_edit_from_reference(reference, new_name));
128 source_file_edits.extend(ref_edits);
129 } 134 }
130 135
131 Some(SourceChange::from_edits(source_file_edits, file_system_edits)) 136 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
137 let ref_edits = refs
138 .references
139 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits);
142
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
132} 144}
133 145
134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { 146fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
@@ -623,16 +635,16 @@ mod tests {
623 #[test] 635 #[test]
624 fn test_rename_mod() { 636 fn test_rename_mod() {
625 let (analysis, position) = analysis_and_position( 637 let (analysis, position) = analysis_and_position(
626 " 638 r#"
627 //- /lib.rs 639//- /lib.rs
628 mod bar; 640mod bar;
629 641
630 //- /bar.rs 642//- /bar.rs
631 mod foo<|>; 643mod foo<|>;
632 644
633 //- /bar/foo.rs 645//- /bar/foo.rs
634 // emtpy 646// emtpy
635 ", 647 "#,
636 ); 648 );
637 let new_name = "foo2"; 649 let new_name = "foo2";
638 let source_change = analysis.rename(position, new_name).unwrap(); 650 let source_change = analysis.rename(position, new_name).unwrap();
@@ -662,10 +674,80 @@ mod tests {
662 src: FileId( 674 src: FileId(
663 3, 675 3,
664 ), 676 ),
665 dst_source_root: SourceRootId( 677 anchor: FileId(
666 0, 678 2,
667 ), 679 ),
668 dst_path: "bar/foo2.rs", 680 dst: "foo2.rs",
681 },
682 ],
683 is_snippet: false,
684 },
685 },
686 )
687 "###);
688 }
689
690 #[test]
691 fn test_rename_mod_in_use_tree() {
692 let (analysis, position) = analysis_and_position(
693 r#"
694//- /main.rs
695pub mod foo;
696pub mod bar;
697fn main() {}
698
699//- /foo.rs
700pub struct FooContent;
701
702//- /bar.rs
703use crate::foo<|>::FooContent;
704 "#,
705 );
706 let new_name = "qux";
707 let source_change = analysis.rename(position, new_name).unwrap();
708 assert_debug_snapshot!(&source_change,
709@r###"
710 Some(
711 RangeInfo {
712 range: 11..14,
713 info: SourceChange {
714 source_file_edits: [
715 SourceFileEdit {
716 file_id: FileId(
717 1,
718 ),
719 edit: TextEdit {
720 indels: [
721 Indel {
722 insert: "qux",
723 delete: 8..11,
724 },
725 ],
726 },
727 },
728 SourceFileEdit {
729 file_id: FileId(
730 3,
731 ),
732 edit: TextEdit {
733 indels: [
734 Indel {
735 insert: "qux",
736 delete: 11..14,
737 },
738 ],
739 },
740 },
741 ],
742 file_system_edits: [
743 MoveFile {
744 src: FileId(
745 2,
746 ),
747 anchor: FileId(
748 3,
749 ),
750 dst: "qux.rs",
669 }, 751 },
670 ], 752 ],
671 is_snippet: false, 753 is_snippet: false,
@@ -678,12 +760,12 @@ mod tests {
678 #[test] 760 #[test]
679 fn test_rename_mod_in_dir() { 761 fn test_rename_mod_in_dir() {
680 let (analysis, position) = analysis_and_position( 762 let (analysis, position) = analysis_and_position(
681 " 763 r#"
682 //- /lib.rs 764//- /lib.rs
683 mod fo<|>o; 765mod fo<|>o;
684 //- /foo/mod.rs 766//- /foo/mod.rs
685 // emtpy 767// emtpy
686 ", 768 "#,
687 ); 769 );
688 let new_name = "foo2"; 770 let new_name = "foo2";
689 let source_change = analysis.rename(position, new_name).unwrap(); 771 let source_change = analysis.rename(position, new_name).unwrap();
@@ -713,10 +795,10 @@ mod tests {
713 src: FileId( 795 src: FileId(
714 2, 796 2,
715 ), 797 ),
716 dst_source_root: SourceRootId( 798 anchor: FileId(
717 0, 799 1,
718 ), 800 ),
719 dst_path: "foo2/mod.rs", 801 dst: "../foo2/mod.rs",
720 }, 802 },
721 ], 803 ],
722 is_snippet: false, 804 is_snippet: false,
@@ -753,19 +835,19 @@ mod tests {
753 #[test] 835 #[test]
754 fn test_rename_mod_filename_and_path() { 836 fn test_rename_mod_filename_and_path() {
755 let (analysis, position) = analysis_and_position( 837 let (analysis, position) = analysis_and_position(
756 " 838 r#"
757 //- /lib.rs 839//- /lib.rs
758 mod bar; 840mod bar;
759 fn f() { 841fn f() {
760 bar::foo::fun() 842 bar::foo::fun()
761 } 843}
762 844
763 //- /bar.rs 845//- /bar.rs
764 pub mod foo<|>; 846pub mod foo<|>;
765 847
766 //- /bar/foo.rs 848//- /bar/foo.rs
767 // pub fn fun() {} 849// pub fn fun() {}
768 ", 850 "#,
769 ); 851 );
770 let new_name = "foo2"; 852 let new_name = "foo2";
771 let source_change = analysis.rename(position, new_name).unwrap(); 853 let source_change = analysis.rename(position, new_name).unwrap();
@@ -808,10 +890,10 @@ mod tests {
808 src: FileId( 890 src: FileId(
809 3, 891 3,
810 ), 892 ),
811 dst_source_root: SourceRootId( 893 anchor: FileId(
812 0, 894 2,
813 ), 895 ),
814 dst_path: "bar/foo2.rs", 896 dst: "foo2.rs",
815 }, 897 },
816 ], 898 ],
817 is_snippet: false, 899 is_snippet: false,
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index fc57dc33d..8105ef373 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -171,7 +171,15 @@ fn runnable_fn(
171 let cfg_exprs = 171 let cfg_exprs =
172 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 172 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
173 173
174 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); 174 let nav = if let RunnableKind::DocTest { .. } = kind {
175 NavigationTarget::from_doc_commented(
176 sema.db,
177 InFile::new(file_id.into(), &fn_def),
178 InFile::new(file_id.into(), &fn_def),
179 )
180 } else {
181 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
182 };
175 Some(Runnable { nav, kind, cfg_exprs }) 183 Some(Runnable { nav, kind, cfg_exprs })
176} 184}
177 185
@@ -419,9 +427,7 @@ mod tests {
419 full_range: 22..64, 427 full_range: 22..64,
420 name: "foo", 428 name: "foo",
421 kind: FN_DEF, 429 kind: FN_DEF,
422 focus_range: Some( 430 focus_range: None,
423 56..59,
424 ),
425 container_name: None, 431 container_name: None,
426 description: None, 432 description: None,
427 docs: None, 433 docs: None,
@@ -486,9 +492,7 @@ mod tests {
486 full_range: 51..105, 492 full_range: 51..105,
487 name: "foo", 493 name: "foo",
488 kind: FN_DEF, 494 kind: FN_DEF,
489 focus_range: Some( 495 focus_range: None,
490 97..100,
491 ),
492 container_name: None, 496 container_name: None,
493 description: None, 497 description: None,
494 docs: None, 498 docs: None,
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
index 0ae8c7efc..63199cdbe 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -25,47 +25,72 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
31.control { font-style: italic; } 33.control { font-style: italic; }
32</style> 34</style>
33<pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { 35<pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
34 <span class="comment">/// Constructs a new `Foo`.</span> 36 <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
35 <span class="comment">///</span> 37}
36 <span class="comment">/// # Examples</span> 38
37 <span class="comment">///</span> 39<span class="keyword">impl</span> <span class="struct">Foo</span> {
38 <span class="comment">/// ```</span> 40 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span>: <span class="builtin_type">bool</span> = <span class="bool_literal">true</span>;
39 <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> 41
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>(); 42 <span class="comment documentation">/// Constructs a new `Foo`.</span>
41 <span class="comment">/// ```</span> 43 <span class="comment documentation">///</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> { 44 <span class="comment documentation">/// # Examples</span>
43 <span class="unresolved_reference">Foo</span> { } 45 <span class="comment documentation">///</span>
46 <span class="comment documentation">/// ```</span>
47 <span class="comment documentation">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
48 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="struct">Foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
49 <span class="comment documentation">/// ```</span>
50 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="struct">Foo</span> {
51 <span class="struct">Foo</span> { <span class="field">bar</span>: <span class="bool_literal">true</span> }
44 } 52 }
45 53
46 <span class="comment">/// `bar` method on `Foo`.</span> 54 <span class="comment documentation">/// `bar` method on `Foo`.</span>
47 <span class="comment">///</span> 55 <span class="comment documentation">///</span>
48 <span class="comment">/// # Examples</span> 56 <span class="comment documentation">/// # Examples</span>
49 <span class="comment">///</span> 57 <span class="comment documentation">///</span>
50 <span class="comment">/// ```</span> 58 <span class="comment documentation">/// ```</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>(); 59 <span class="comment documentation">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>;
52 <span class="comment">///</span> 60 <span class="comment documentation">///</span>
53 <span class="comment">/// </span><span class="comment">// calls bar on foo</span> 61 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
54 <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); 62 <span class="comment documentation">///</span>
55 <span class="comment">///</span> 63 <span class="comment documentation">/// </span><span class="comment">// calls bar on foo</span>
56 <span class="comment">/// </span><span class="comment">/* multi-line 64 <span class="comment documentation">/// </span><span class="macro">assert!</span>(foo.bar());
57 </span><span class="comment">/// </span><span class="comment"> comment */</span> 65 <span class="comment documentation">///</span>
58 <span class="comment">///</span> 66 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">bar</span> = <span class="variable">foo</span>.<span class="field">bar</span> || <span class="struct">Foo</span>::<span class="constant">bar</span>;
59 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo 67 <span class="comment documentation">///</span>
60 </span><span class="comment">/// </span><span class="string_literal"> bar 68 <span class="comment documentation">/// </span><span class="comment">/* multi-line
61 </span><span class="comment">/// </span><span class="string_literal"> "</span>; 69 </span><span class="comment documentation">/// </span><span class="comment"> comment */</span>
62 <span class="comment">///</span> 70 <span class="comment documentation">///</span>
63 <span class="comment">/// ```</span> 71 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo
64 <span class="comment">///</span> 72 </span><span class="comment documentation">/// </span><span class="string_literal"> bar
65 <span class="comment">/// ```</span> 73 </span><span class="comment documentation">/// </span><span class="string_literal"> "</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>(); 74 <span class="comment documentation">///</span>
67 <span class="comment">/// ```</span> 75 <span class="comment documentation">/// ```</span>
76 <span class="comment documentation">///</span>
77 <span class="comment documentation">/// ```rust,no_run</span>
78 <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="struct">Foo</span>::<span class="function">new</span>().<span class="function">bar</span>();
79 <span class="comment documentation">/// ```</span>
80 <span class="comment documentation">///</span>
81 <span class="comment documentation">/// ```sh</span>
82 <span class="comment documentation">/// echo 1</span>
83 <span class="comment documentation">/// ```</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> { 84 <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> 85 <span class="bool_literal">true</span>
70 } 86 }
87}
88
89<span class="comment documentation">/// ```</span>
90<span class="comment documentation">/// </span><span class="macro">noop!</span>(<span class="numeric_literal">1</span>);
91<span class="comment documentation">/// ```</span>
92<span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> {
93 ($expr:expr) =&gt; {
94 $expr
95 }
71}</code></pre> \ No newline at end of file 96}</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 dec06eb51..47dbd7bc8 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index 849eb3b73..b46fa44c6 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
@@ -82,6 +84,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
82 84
83 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); 85 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
84 86
85 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); 87 <span class="comment">// escape sequences</span>
88 <span class="macro">println!</span>(<span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span>);
89 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span>);
90
91 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
86 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); 92 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
87}</code></pre> \ No newline at end of file 93}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html
index bd24e6e38..73438fbb4 100644
--- a/crates/ra_ide/src/snapshots/highlight_unsafe.html
+++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 5c2ff6ab5..0c4f0a018 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 1ab06182c..a74a70069 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 93e9aee1d..59c230f6c 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,37 +1,18 @@
1use std::{collections::HashMap, iter::once, str::FromStr}; 1use ra_db::SourceDatabaseExt;
2
3use ra_db::{SourceDatabase, SourceDatabaseExt};
4use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 2use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
5use ra_syntax::ast::{
6 make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr,
7 RecordField, RecordLit,
8};
9use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode};
10use ra_text_edit::{TextEdit, TextEditBuilder};
11use rustc_hash::FxHashMap;
12 3
13use crate::SourceFileEdit; 4use crate::SourceFileEdit;
14 5use ra_ssr::{MatchFinder, SsrError, SsrRule};
15#[derive(Debug, PartialEq)]
16pub struct SsrError(String);
17
18impl std::fmt::Display for SsrError {
19 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20 write!(f, "Parse error: {}", self.0)
21 }
22}
23
24impl std::error::Error for SsrError {}
25 6
26// Feature: Structural Seach and Replace 7// Feature: Structural Seach and Replace
27// 8//
28// Search and replace with named wildcards that will match any expression. 9// Search and replace with named wildcards that will match any expression, type, path, pattern or item.
29// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. 10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
30// A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. 11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
31// Available via the command `rust-analyzer.ssr`. 12// Available via the command `rust-analyzer.ssr`.
32// 13//
33// ```rust 14// ```rust
34// // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] 15// // Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)]
35// 16//
36// // BEFORE 17// // BEFORE
37// String::from(foo(y + 5, z)) 18// String::from(foo(y + 5, z))
@@ -46,584 +27,24 @@ impl std::error::Error for SsrError {}
46// | VS Code | **Rust Analyzer: Structural Search Replace** 27// | VS Code | **Rust Analyzer: Structural Search Replace**
47// |=== 28// |===
48pub fn parse_search_replace( 29pub fn parse_search_replace(
49 query: &str, 30 rule: &str,
50 parse_only: bool, 31 parse_only: bool,
51 db: &RootDatabase, 32 db: &RootDatabase,
52) -> Result<Vec<SourceFileEdit>, SsrError> { 33) -> Result<Vec<SourceFileEdit>, SsrError> {
53 let mut edits = vec![]; 34 let mut edits = vec![];
54 let query: SsrQuery = query.parse()?; 35 let rule: SsrRule = rule.parse()?;
55 if parse_only { 36 if parse_only {
56 return Ok(edits); 37 return Ok(edits);
57 } 38 }
39 let mut match_finder = MatchFinder::new(db);
40 match_finder.add_rule(rule);
58 for &root in db.local_roots().iter() { 41 for &root in db.local_roots().iter() {
59 let sr = db.source_root(root); 42 let sr = db.source_root(root);
60 for file_id in sr.walk() { 43 for file_id in sr.walk() {
61 let matches = find(&query.pattern, db.parse(file_id).tree().syntax()); 44 if let Some(edit) = match_finder.edits_for_file(file_id) {
62 if !matches.matches.is_empty() { 45 edits.push(SourceFileEdit { file_id, edit });
63 edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) });
64 } 46 }
65 } 47 }
66 } 48 }
67 Ok(edits) 49 Ok(edits)
68} 50}
69
70#[derive(Debug)]
71struct SsrQuery {
72 pattern: SsrPattern,
73 template: SsrTemplate,
74}
75
76#[derive(Debug)]
77struct SsrPattern {
78 pattern: SyntaxNode,
79 vars: Vec<Var>,
80}
81
82/// represents an `$var` in an SSR query
83#[derive(Debug, Clone, PartialEq, Eq, Hash)]
84struct Var(String);
85
86#[derive(Debug)]
87struct SsrTemplate {
88 template: SyntaxNode,
89 placeholders: FxHashMap<SyntaxNode, Var>,
90}
91
92type Binding = HashMap<Var, SyntaxNode>;
93
94#[derive(Debug)]
95struct Match {
96 place: SyntaxNode,
97 binding: Binding,
98 ignored_comments: Vec<Comment>,
99}
100
101#[derive(Debug)]
102struct SsrMatches {
103 matches: Vec<Match>,
104}
105
106impl FromStr for SsrQuery {
107 type Err = SsrError;
108
109 fn from_str(query: &str) -> Result<SsrQuery, SsrError> {
110 let mut it = query.split("==>>");
111 let pattern = it.next().expect("at least empty string").trim();
112 let mut template = it
113 .next()
114 .ok_or_else(|| SsrError("Cannot find delemiter `==>>`".into()))?
115 .trim()
116 .to_string();
117 if it.next().is_some() {
118 return Err(SsrError("More than one delimiter found".into()));
119 }
120 let mut vars = vec![];
121 let mut it = pattern.split('$');
122 let mut pattern = it.next().expect("something").to_string();
123
124 for part in it.map(split_by_var) {
125 let (var, var_type, remainder) = part?;
126 is_expr(var_type)?;
127 let new_var = create_name(var, &mut vars)?;
128 pattern.push_str(new_var);
129 pattern.push_str(remainder);
130 template = replace_in_template(template, var, new_var);
131 }
132
133 let template = try_expr_from_text(&template)
134 .ok_or(SsrError("Template is not an expression".into()))?
135 .syntax()
136 .clone();
137 let mut placeholders = FxHashMap::default();
138
139 traverse(&template, &mut |n| {
140 if let Some(v) = vars.iter().find(|v| v.0.as_str() == n.text()) {
141 placeholders.insert(n.clone(), v.clone());
142 false
143 } else {
144 true
145 }
146 });
147
148 let pattern = SsrPattern {
149 pattern: try_expr_from_text(&pattern)
150 .ok_or(SsrError("Pattern is not an expression".into()))?
151 .syntax()
152 .clone(),
153 vars,
154 };
155 let template = SsrTemplate { template, placeholders };
156 Ok(SsrQuery { pattern, template })
157 }
158}
159
160fn traverse(node: &SyntaxNode, go: &mut impl FnMut(&SyntaxNode) -> bool) {
161 if !go(node) {
162 return;
163 }
164 for ref child in node.children() {
165 traverse(child, go);
166 }
167}
168
169fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> {
170 let end_of_name = s.find(':').ok_or_else(|| SsrError("Use $<name>:expr".into()))?;
171 let name = &s[0..end_of_name];
172 is_name(name)?;
173 let type_begin = end_of_name + 1;
174 let type_length =
175 s[type_begin..].find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len());
176 let type_name = &s[type_begin..type_begin + type_length];
177 Ok((name, type_name, &s[type_begin + type_length..]))
178}
179
180fn is_name(s: &str) -> Result<(), SsrError> {
181 if s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
182 Ok(())
183 } else {
184 Err(SsrError("Name can contain only alphanumerics and _".into()))
185 }
186}
187
188fn is_expr(s: &str) -> Result<(), SsrError> {
189 if s == "expr" {
190 Ok(())
191 } else {
192 Err(SsrError("Only $<name>:expr is supported".into()))
193 }
194}
195
196fn replace_in_template(template: String, var: &str, new_var: &str) -> String {
197 let name = format!("${}", var);
198 template.replace(&name, new_var)
199}
200
201fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrError> {
202 let sanitized_name = format!("__search_pattern_{}", name);
203 if vars.iter().any(|a| a.0 == sanitized_name) {
204 return Err(SsrError(format!("Name `{}` repeats more than once", name)));
205 }
206 vars.push(Var(sanitized_name));
207 Ok(&vars.last().unwrap().0)
208}
209
210fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
211 fn check_record_lit(
212 pattern: RecordLit,
213 code: RecordLit,
214 placeholders: &[Var],
215 match_: Match,
216 ) -> Option<Match> {
217 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
218
219 let mut pattern_fields: Vec<RecordField> =
220 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
221 let mut code_fields: Vec<RecordField> =
222 code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
223
224 if pattern_fields.len() != code_fields.len() {
225 return None;
226 }
227
228 let by_name = |a: &RecordField, b: &RecordField| {
229 a.name_ref()
230 .map(|x| x.syntax().text().to_string())
231 .cmp(&b.name_ref().map(|x| x.syntax().text().to_string()))
232 };
233 pattern_fields.sort_by(by_name);
234 code_fields.sort_by(by_name);
235
236 pattern_fields.into_iter().zip(code_fields.into_iter()).fold(
237 Some(match_),
238 |accum, (a, b)| {
239 accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_))
240 },
241 )
242 }
243
244 fn check_call_and_method_call(
245 pattern: CallExpr,
246 code: MethodCallExpr,
247 placeholders: &[Var],
248 match_: Match,
249 ) -> Option<Match> {
250 let (pattern_name, pattern_type_args) = if let Some(Expr::PathExpr(path_exr)) =
251 pattern.expr()
252 {
253 let segment = path_exr.path().and_then(|p| p.segment());
254 (segment.as_ref().and_then(|s| s.name_ref()), segment.and_then(|s| s.type_arg_list()))
255 } else {
256 (None, None)
257 };
258 let match_ = check_opt_nodes(pattern_name, code.name_ref(), placeholders, match_)?;
259 let match_ =
260 check_opt_nodes(pattern_type_args, code.type_arg_list(), placeholders, match_)?;
261 let pattern_args = pattern.syntax().children().find_map(ArgList::cast)?.args();
262 let code_args = code.syntax().children().find_map(ArgList::cast)?.args();
263 let code_args = once(code.expr()?).chain(code_args);
264 check_iter(pattern_args, code_args, placeholders, match_)
265 }
266
267 fn check_method_call_and_call(
268 pattern: MethodCallExpr,
269 code: CallExpr,
270 placeholders: &[Var],
271 match_: Match,
272 ) -> Option<Match> {
273 let (code_name, code_type_args) = if let Some(Expr::PathExpr(path_exr)) = code.expr() {
274 let segment = path_exr.path().and_then(|p| p.segment());
275 (segment.as_ref().and_then(|s| s.name_ref()), segment.and_then(|s| s.type_arg_list()))
276 } else {
277 (None, None)
278 };
279 let match_ = check_opt_nodes(pattern.name_ref(), code_name, placeholders, match_)?;
280 let match_ =
281 check_opt_nodes(pattern.type_arg_list(), code_type_args, placeholders, match_)?;
282 let code_args = code.syntax().children().find_map(ArgList::cast)?.args();
283 let pattern_args = pattern.syntax().children().find_map(ArgList::cast)?.args();
284 let pattern_args = once(pattern.expr()?).chain(pattern_args);
285 check_iter(pattern_args, code_args, placeholders, match_)
286 }
287
288 fn check_opt_nodes(
289 pattern: Option<impl AstNode>,
290 code: Option<impl AstNode>,
291 placeholders: &[Var],
292 match_: Match,
293 ) -> Option<Match> {
294 match (pattern, code) {
295 (Some(pattern), Some(code)) => check(
296 &pattern.syntax().clone().into(),
297 &code.syntax().clone().into(),
298 placeholders,
299 match_,
300 ),
301 (None, None) => Some(match_),
302 _ => None,
303 }
304 }
305
306 fn check_iter<T, I1, I2>(
307 mut pattern: I1,
308 mut code: I2,
309 placeholders: &[Var],
310 match_: Match,
311 ) -> Option<Match>
312 where
313 T: AstNode,
314 I1: Iterator<Item = T>,
315 I2: Iterator<Item = T>,
316 {
317 pattern
318 .by_ref()
319 .zip(code.by_ref())
320 .fold(Some(match_), |accum, (a, b)| {
321 accum.and_then(|match_| {
322 check(
323 &a.syntax().clone().into(),
324 &b.syntax().clone().into(),
325 placeholders,
326 match_,
327 )
328 })
329 })
330 .filter(|_| pattern.next().is_none() && code.next().is_none())
331 }
332
333 fn check(
334 pattern: &SyntaxElement,
335 code: &SyntaxElement,
336 placeholders: &[Var],
337 mut match_: Match,
338 ) -> Option<Match> {
339 match (&pattern, &code) {
340 (SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => {
341 if pattern.text() == code.text() {
342 Some(match_)
343 } else {
344 None
345 }
346 }
347 (SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => {
348 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
349 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
350 Some(match_)
351 } else {
352 if let (Some(pattern), Some(code)) =
353 (RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone()))
354 {
355 check_record_lit(pattern, code, placeholders, match_)
356 } else if let (Some(pattern), Some(code)) =
357 (CallExpr::cast(pattern.clone()), MethodCallExpr::cast(code.clone()))
358 {
359 check_call_and_method_call(pattern, code, placeholders, match_)
360 } else if let (Some(pattern), Some(code)) =
361 (MethodCallExpr::cast(pattern.clone()), CallExpr::cast(code.clone()))
362 {
363 check_method_call_and_call(pattern, code, placeholders, match_)
364 } else {
365 let mut pattern_children = pattern
366 .children_with_tokens()
367 .filter(|element| !element.kind().is_trivia());
368 let mut code_children = code
369 .children_with_tokens()
370 .filter(|element| !element.kind().is_trivia());
371 let new_ignored_comments =
372 code.children_with_tokens().filter_map(|element| {
373 element.as_token().and_then(|token| Comment::cast(token.clone()))
374 });
375 match_.ignored_comments.extend(new_ignored_comments);
376 pattern_children
377 .by_ref()
378 .zip(code_children.by_ref())
379 .fold(Some(match_), |accum, (a, b)| {
380 accum.and_then(|match_| check(&a, &b, placeholders, match_))
381 })
382 .filter(|_| {
383 pattern_children.next().is_none() && code_children.next().is_none()
384 })
385 }
386 }
387 }
388 _ => None,
389 }
390 }
391 let kind = pattern.pattern.kind();
392 let matches = code
393 .descendants()
394 .filter(|n| {
395 n.kind() == kind
396 || (kind == SyntaxKind::CALL_EXPR && n.kind() == SyntaxKind::METHOD_CALL_EXPR)
397 || (kind == SyntaxKind::METHOD_CALL_EXPR && n.kind() == SyntaxKind::CALL_EXPR)
398 })
399 .filter_map(|code| {
400 let match_ =
401 Match { place: code.clone(), binding: HashMap::new(), ignored_comments: vec![] };
402 check(&pattern.pattern.clone().into(), &code.into(), &pattern.vars, match_)
403 })
404 .collect();
405 SsrMatches { matches }
406}
407
408fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit {
409 let mut builder = TextEditBuilder::default();
410 for match_ in &matches.matches {
411 builder.replace(
412 match_.place.text_range(),
413 render_replace(&match_.binding, &match_.ignored_comments, template),
414 );
415 }
416 builder.finish()
417}
418
419fn render_replace(
420 binding: &Binding,
421 ignored_comments: &Vec<Comment>,
422 template: &SsrTemplate,
423) -> String {
424 let edit = {
425 let mut builder = TextEditBuilder::default();
426 for element in template.template.descendants() {
427 if let Some(var) = template.placeholders.get(&element) {
428 builder.replace(element.text_range(), binding[var].to_string())
429 }
430 }
431 for comment in ignored_comments {
432 builder.insert(template.template.text_range().end(), comment.syntax().to_string())
433 }
434 builder.finish()
435 };
436
437 let mut text = template.template.text().to_string();
438 edit.apply(&mut text);
439 text
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use ra_syntax::SourceFile;
446
447 fn parse_error_text(query: &str) -> String {
448 format!("{}", query.parse::<SsrQuery>().unwrap_err())
449 }
450
451 #[test]
452 fn parser_happy_case() {
453 let result: SsrQuery = "foo($a:expr, $b:expr) ==>> bar($b, $a)".parse().unwrap();
454 assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)");
455 assert_eq!(result.pattern.vars.len(), 2);
456 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a");
457 assert_eq!(result.pattern.vars[1].0, "__search_pattern_b");
458 assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)");
459 }
460
461 #[test]
462 fn parser_empty_query() {
463 assert_eq!(parse_error_text(""), "Parse error: Cannot find delemiter `==>>`");
464 }
465
466 #[test]
467 fn parser_no_delimiter() {
468 assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delemiter `==>>`");
469 }
470
471 #[test]
472 fn parser_two_delimiters() {
473 assert_eq!(
474 parse_error_text("foo() ==>> a ==>> b "),
475 "Parse error: More than one delimiter found"
476 );
477 }
478
479 #[test]
480 fn parser_no_pattern_type() {
481 assert_eq!(parse_error_text("foo($a) ==>>"), "Parse error: Use $<name>:expr");
482 }
483
484 #[test]
485 fn parser_invalid_name() {
486 assert_eq!(
487 parse_error_text("foo($a+:expr) ==>>"),
488 "Parse error: Name can contain only alphanumerics and _"
489 );
490 }
491
492 #[test]
493 fn parser_invalid_type() {
494 assert_eq!(
495 parse_error_text("foo($a:ident) ==>>"),
496 "Parse error: Only $<name>:expr is supported"
497 );
498 }
499
500 #[test]
501 fn parser_repeated_name() {
502 assert_eq!(
503 parse_error_text("foo($a:expr, $a:expr) ==>>"),
504 "Parse error: Name `a` repeats more than once"
505 );
506 }
507
508 #[test]
509 fn parser_invlid_pattern() {
510 assert_eq!(parse_error_text(" ==>> ()"), "Parse error: Pattern is not an expression");
511 }
512
513 #[test]
514 fn parser_invlid_template() {
515 assert_eq!(parse_error_text("() ==>> )"), "Parse error: Template is not an expression");
516 }
517
518 #[test]
519 fn parse_match_replace() {
520 let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap();
521 let input = "fn main() { foo(1+2); }";
522
523 let code = SourceFile::parse(input).tree();
524 let matches = find(&query.pattern, code.syntax());
525 assert_eq!(matches.matches.len(), 1);
526 assert_eq!(matches.matches[0].place.text(), "foo(1+2)");
527 assert_eq!(matches.matches[0].binding.len(), 1);
528 assert_eq!(
529 matches.matches[0].binding[&Var("__search_pattern_x".to_string())].text(),
530 "1+2"
531 );
532
533 let edit = replace(&matches, &query.template);
534 let mut after = input.to_string();
535 edit.apply(&mut after);
536 assert_eq!(after, "fn main() { bar(1+2); }");
537 }
538
539 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
540 let query: SsrQuery = query.parse().unwrap();
541 let code = SourceFile::parse(input).tree();
542 let matches = find(&query.pattern, code.syntax());
543 let edit = replace(&matches, &query.template);
544 let mut after = input.to_string();
545 edit.apply(&mut after);
546 assert_eq!(after, result);
547 }
548
549 #[test]
550 fn ssr_function_to_method() {
551 assert_ssr_transform(
552 "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)",
553 "loop { my_function( other_func(x, y), z + w) }",
554 "loop { (other_func(x, y)).my_method(z + w) }",
555 )
556 }
557
558 #[test]
559 fn ssr_nested_function() {
560 assert_ssr_transform(
561 "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))",
562 "fn main { foo (x + value.method(b), x+y-z, true && false) }",
563 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
564 )
565 }
566
567 #[test]
568 fn ssr_expected_spacing() {
569 assert_ssr_transform(
570 "foo($x:expr) + bar() ==>> bar($x)",
571 "fn main() { foo(5) + bar() }",
572 "fn main() { bar(5) }",
573 );
574 }
575
576 #[test]
577 fn ssr_with_extra_space() {
578 assert_ssr_transform(
579 "foo($x:expr ) + bar() ==>> bar($x)",
580 "fn main() { foo( 5 ) +bar( ) }",
581 "fn main() { bar(5) }",
582 );
583 }
584
585 #[test]
586 fn ssr_keeps_nested_comment() {
587 assert_ssr_transform(
588 "foo($x:expr) ==>> bar($x)",
589 "fn main() { foo(other(5 /* using 5 */)) }",
590 "fn main() { bar(other(5 /* using 5 */)) }",
591 )
592 }
593
594 #[test]
595 fn ssr_keeps_comment() {
596 assert_ssr_transform(
597 "foo($x:expr) ==>> bar($x)",
598 "fn main() { foo(5 /* using 5 */) }",
599 "fn main() { bar(5)/* using 5 */ }",
600 )
601 }
602
603 #[test]
604 fn ssr_struct_lit() {
605 assert_ssr_transform(
606 "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)",
607 "fn main() { foo{b:2, a:1} }",
608 "fn main() { foo::new(1, 2) }",
609 )
610 }
611
612 #[test]
613 fn ssr_call_and_method_call() {
614 assert_ssr_transform(
615 "foo::<'a>($a:expr, $b:expr)) ==>> foo2($a, $b)",
616 "fn main() { get().bar.foo::<'a>(1); }",
617 "fn main() { foo2(get().bar, 1); }",
618 )
619 }
620
621 #[test]
622 fn ssr_method_call_and_call() {
623 assert_ssr_transform(
624 "$o:expr.foo::<i32>($a:expr)) ==>> $o.foo2($a)",
625 "fn main() { X::foo::<i32>(x, 1); }",
626 "fn main() { x.foo2(1); }",
627 )
628 }
629}
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs
index 5b7992920..45411b357 100644
--- a/crates/ra_ide/src/status.rs
+++ b/crates/ra_ide/src/status.rs
@@ -16,6 +16,7 @@ use ra_prof::{memory_usage, Bytes};
16use ra_syntax::{ast, Parse, SyntaxNode}; 16use ra_syntax::{ast, Parse, SyntaxNode};
17 17
18use crate::FileId; 18use crate::FileId;
19use rustc_hash::FxHashMap;
19 20
20fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 21fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
21 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>() 22 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>()
@@ -123,20 +124,24 @@ struct LibrarySymbolsStats {
123 124
124impl fmt::Display for LibrarySymbolsStats { 125impl fmt::Display for LibrarySymbolsStats {
125 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 126 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
126 write!(fmt, "{} ({}) symbols", self.total, self.size,) 127 write!(fmt, "{} ({}) symbols", self.total, self.size)
127 } 128 }
128} 129}
129 130
130impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { 131impl FromIterator<TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>
132 for LibrarySymbolsStats
133{
131 fn from_iter<T>(iter: T) -> LibrarySymbolsStats 134 fn from_iter<T>(iter: T) -> LibrarySymbolsStats
132 where 135 where
133 T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>, 136 T: IntoIterator<Item = TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>,
134 { 137 {
135 let mut res = LibrarySymbolsStats::default(); 138 let mut res = LibrarySymbolsStats::default();
136 for entry in iter { 139 for entry in iter {
137 let value = entry.value.unwrap(); 140 let value = entry.value.unwrap();
138 res.total += value.len(); 141 for symbols in value.values() {
139 res.size += value.memory_size(); 142 res.total += symbols.len();
143 res.size += symbols.memory_size();
144 }
140 } 145 }
141 res 146 res
142 } 147 }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index bbcd52a1c..448645bdc 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -44,6 +44,7 @@ pub(crate) fn highlight(
44 db: &RootDatabase, 44 db: &RootDatabase,
45 file_id: FileId, 45 file_id: FileId,
46 range_to_highlight: Option<TextRange>, 46 range_to_highlight: Option<TextRange>,
47 syntactic_name_ref_highlighting: bool,
47) -> Vec<HighlightedRange> { 48) -> Vec<HighlightedRange> {
48 let _p = profile("highlight"); 49 let _p = profile("highlight");
49 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
@@ -104,6 +105,7 @@ pub(crate) fn highlight(
104 if let Some((highlight, binding_hash)) = highlight_element( 105 if let Some((highlight, binding_hash)) = highlight_element(
105 &sema, 106 &sema,
106 &mut bindings_shadow_count, 107 &mut bindings_shadow_count,
108 syntactic_name_ref_highlighting,
107 name.syntax().clone().into(), 109 name.syntax().clone().into(),
108 ) { 110 ) {
109 stack.add(HighlightedRange { 111 stack.add(HighlightedRange {
@@ -119,7 +121,6 @@ pub(crate) fn highlight(
119 assert!(current_macro_call == Some(mc)); 121 assert!(current_macro_call == Some(mc));
120 current_macro_call = None; 122 current_macro_call = None;
121 format_string = None; 123 format_string = None;
122 continue;
123 } 124 }
124 _ => (), 125 _ => (),
125 } 126 }
@@ -148,7 +149,7 @@ pub(crate) fn highlight(
148 149
149 let range = element.text_range(); 150 let range = element.text_range();
150 151
151 let element_to_highlight = if current_macro_call.is_some() { 152 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
152 // Inside a macro -- expand it first 153 // Inside a macro -- expand it first
153 let token = match element.clone().into_token() { 154 let token = match element.clone().into_token() {
154 Some(it) if it.parent().kind() == TOKEN_TREE => it, 155 Some(it) if it.parent().kind() == TOKEN_TREE => it,
@@ -200,15 +201,18 @@ pub(crate) fn highlight(
200 201
201 let is_format_string = format_string.as_ref() == Some(&element_to_highlight); 202 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
202 203
203 if let Some((highlight, binding_hash)) = 204 if let Some((highlight, binding_hash)) = highlight_element(
204 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) 205 &sema,
205 { 206 &mut bindings_shadow_count,
207 syntactic_name_ref_highlighting,
208 element_to_highlight.clone(),
209 ) {
206 stack.add(HighlightedRange { range, highlight, binding_hash }); 210 stack.add(HighlightedRange { range, highlight, binding_hash });
207 if let Some(string) = 211 if let Some(string) =
208 element_to_highlight.as_token().cloned().and_then(ast::String::cast) 212 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
209 { 213 {
210 stack.push();
211 if is_format_string { 214 if is_format_string {
215 stack.push();
212 string.lex_format_specifier(|piece_range, kind| { 216 string.lex_format_specifier(|piece_range, kind| {
213 if let Some(highlight) = highlight_format_specifier(kind) { 217 if let Some(highlight) = highlight_format_specifier(kind) {
214 stack.add(HighlightedRange { 218 stack.add(HighlightedRange {
@@ -218,13 +222,27 @@ pub(crate) fn highlight(
218 }); 222 });
219 } 223 }
220 }); 224 });
225 stack.pop();
226 }
227 // Highlight escape sequences
228 if let Some(char_ranges) = string.char_ranges() {
229 stack.push();
230 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
231 if string.text()[piece_range.start().into()..].starts_with('\\') {
232 stack.add(HighlightedRange {
233 range: piece_range + range.start(),
234 highlight: HighlightTag::EscapeSequence.into(),
235 binding_hash: None,
236 });
237 }
238 }
239 stack.pop_and_inject(false);
221 } 240 }
222 stack.pop();
223 } else if let Some(string) = 241 } else if let Some(string) =
224 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) 242 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
225 { 243 {
226 stack.push();
227 if is_format_string { 244 if is_format_string {
245 stack.push();
228 string.lex_format_specifier(|piece_range, kind| { 246 string.lex_format_specifier(|piece_range, kind| {
229 if let Some(highlight) = highlight_format_specifier(kind) { 247 if let Some(highlight) = highlight_format_specifier(kind) {
230 stack.add(HighlightedRange { 248 stack.add(HighlightedRange {
@@ -234,8 +252,8 @@ pub(crate) fn highlight(
234 }); 252 });
235 } 253 }
236 }); 254 });
255 stack.pop();
237 } 256 }
238 stack.pop();
239 } 257 }
240 } 258 }
241 } 259 }
@@ -410,6 +428,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
410fn highlight_element( 428fn highlight_element(
411 sema: &Semantics<RootDatabase>, 429 sema: &Semantics<RootDatabase>,
412 bindings_shadow_count: &mut FxHashMap<Name, u32>, 430 bindings_shadow_count: &mut FxHashMap<Name, u32>,
431 syntactic_name_ref_highlighting: bool,
413 element: SyntaxElement, 432 element: SyntaxElement,
414) -> Option<(Highlight, Option<u64>)> { 433) -> Option<(Highlight, Option<u64>)> {
415 let db = sema.db; 434 let db = sema.db;
@@ -463,12 +482,20 @@ fn highlight_element(
463 } 482 }
464 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), 483 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
465 }, 484 },
485 None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
466 None => HighlightTag::UnresolvedReference.into(), 486 None => HighlightTag::UnresolvedReference.into(),
467 } 487 }
468 } 488 }
469 489
470 // Simple token-based highlighting 490 // Simple token-based highlighting
471 COMMENT => HighlightTag::Comment.into(), 491 COMMENT => {
492 let comment = element.into_token().and_then(ast::Comment::cast)?;
493 let h = HighlightTag::Comment;
494 match comment.kind().doc {
495 Some(_) => h | HighlightModifier::Documentation,
496 None => h.into(),
497 }
498 }
472 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), 499 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
473 ATTR => HighlightTag::Attribute.into(), 500 ATTR => HighlightTag::Attribute.into(),
474 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), 501 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
@@ -614,3 +641,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
614 641
615 tag.into() 642 tag.into()
616} 643}
644
645fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
646 let default = HighlightTag::UnresolvedReference;
647
648 let parent = match name.syntax().parent() {
649 Some(it) => it,
650 _ => return default.into(),
651 };
652
653 let tag = match parent.kind() {
654 METHOD_CALL_EXPR => HighlightTag::Function,
655 FIELD_EXPR => HighlightTag::Field,
656 PATH_SEGMENT => {
657 let path = match parent.parent().and_then(ast::Path::cast) {
658 Some(it) => it,
659 _ => return default.into(),
660 };
661 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
662 Some(it) => it,
663 _ => {
664 // within path, decide whether it is module or adt by checking for uppercase name
665 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
666 HighlightTag::Struct
667 } else {
668 HighlightTag::Module
669 }
670 .into();
671 }
672 };
673 let parent = match expr.syntax().parent() {
674 Some(it) => it,
675 None => return default.into(),
676 };
677
678 match parent.kind() {
679 CALL_EXPR => HighlightTag::Function,
680 _ => {
681 if name.text().chars().next().unwrap_or_default().is_uppercase() {
682 HighlightTag::Struct
683 } else {
684 HighlightTag::Constant
685 }
686 }
687 }
688 }
689 _ => default,
690 };
691
692 tag.into()
693}
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index 5bada6252..99b6b25ab 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
19 ) 19 )
20 } 20 }
21 21
22 let ranges = highlight(db, file_id, None); 22 let ranges = highlight(db, file_id, None, false);
23 let text = parse.tree().syntax().to_string(); 23 let text = parse.tree().syntax().to_string();
24 let mut prev_pos = TextSize::from(0); 24 let mut prev_pos = TextSize::from(0);
25 let mut buf = String::new(); 25 let mut buf = String::new();
@@ -84,6 +84,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
84.variable { color: #DCDCCC; } 84.variable { color: #DCDCCC; }
85.format_specifier { color: #CC696B; } 85.format_specifier { color: #CC696B; }
86.mutable { text-decoration: underline; } 86.mutable { text-decoration: underline; }
87.unresolved_reference { color: #FC5555; }
88.escape_sequence { color: #94BFF3; }
87 89
88.keyword { color: #F0DFAF; font-weight: bold; } 90.keyword { color: #F0DFAF; font-weight: bold; }
89.keyword.unsafe { color: #BC8383; font-weight: bold; } 91.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
index 3575a0fc6..415f24a6d 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -7,7 +7,10 @@ use hir::Semantics;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy; 8use stdx::SepBy;
9 9
10use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; 10use crate::{
11 call_info::ActiveParameter, Analysis, HighlightModifier, HighlightTag, HighlightedRange,
12 RootDatabase,
13};
11 14
12use super::HighlightedRangeStack; 15use super::HighlightedRangeStack;
13 16
@@ -53,6 +56,10 @@ pub(super) fn highlight_injection(
53/// Mapping from extracted documentation code to original code 56/// Mapping from extracted documentation code to original code
54type RangesMap = BTreeMap<TextSize, TextSize>; 57type RangesMap = BTreeMap<TextSize, TextSize>;
55 58
59const RUSTDOC_FENCE: &'static str = "```";
60const RUSTDOC_FENCE_TOKENS: &[&'static str] =
61 &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
62
56/// Extracts Rust code from documentation comments as well as a mapping from 63/// Extracts Rust code from documentation comments as well as a mapping from
57/// the extracted source code back to the original source ranges. 64/// the extracted source code back to the original source ranges.
58/// Lastly, a vector of new comment highlight ranges (spanning only the 65/// Lastly, a vector of new comment highlight ranges (spanning only the
@@ -67,6 +74,7 @@ pub(super) fn extract_doc_comments(
67 // Mapping from extracted documentation code to original code 74 // Mapping from extracted documentation code to original code
68 let mut range_mapping: RangesMap = BTreeMap::new(); 75 let mut range_mapping: RangesMap = BTreeMap::new();
69 let mut line_start = TextSize::try_from(prefix.len()).unwrap(); 76 let mut line_start = TextSize::try_from(prefix.len()).unwrap();
77 let mut is_codeblock = false;
70 let mut is_doctest = false; 78 let mut is_doctest = false;
71 // Replace the original, line-spanning comment ranges by new, only comment-prefix 79 // Replace the original, line-spanning comment ranges by new, only comment-prefix
72 // spanning comment ranges. 80 // spanning comment ranges.
@@ -76,8 +84,13 @@ pub(super) fn extract_doc_comments(
76 .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) 84 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
77 .filter(|comment| comment.kind().doc.is_some()) 85 .filter(|comment| comment.kind().doc.is_some())
78 .filter(|comment| { 86 .filter(|comment| {
79 if comment.text().contains("```") { 87 if let Some(idx) = comment.text().find(RUSTDOC_FENCE) {
80 is_doctest = !is_doctest; 88 is_codeblock = !is_codeblock;
89 // Check whether code is rust by inspecting fence guards
90 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..];
91 let is_rust =
92 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
93 is_doctest = is_codeblock && is_rust;
81 false 94 false
82 } else { 95 } else {
83 is_doctest 96 is_doctest
@@ -108,7 +121,7 @@ pub(super) fn extract_doc_comments(
108 range.start(), 121 range.start(),
109 range.start() + TextSize::try_from(pos).unwrap(), 122 range.start() + TextSize::try_from(pos).unwrap(),
110 ), 123 ),
111 highlight: HighlightTag::Comment.into(), 124 highlight: HighlightTag::Comment | HighlightModifier::Documentation,
112 binding_hash: None, 125 binding_hash: None,
113 }); 126 });
114 line_start += range.len() - TextSize::try_from(pos).unwrap(); 127 line_start += range.len() - TextSize::try_from(pos).unwrap();
@@ -137,7 +150,7 @@ pub(super) fn highlight_doc_comment(
137 let (analysis, tmp_file_id) = Analysis::from_single_file(text); 150 let (analysis, tmp_file_id) = Analysis::from_single_file(text);
138 151
139 stack.push(); 152 stack.push();
140 for mut h in analysis.highlight(tmp_file_id).unwrap() { 153 for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
141 // Determine start offset and end offset in case of multi-line ranges 154 // Determine start offset and end offset in case of multi-line ranges
142 let mut start_offset = None; 155 let mut start_offset = None;
143 let mut end_offset = None; 156 let mut end_offset = None;
@@ -154,6 +167,7 @@ pub(super) fn highlight_doc_comment(
154 h.range.start() + start_offset, 167 h.range.start() + start_offset,
155 h.range.end() + end_offset.unwrap_or(start_offset), 168 h.range.end() + end_offset.unwrap_or(start_offset),
156 ); 169 );
170
157 stack.add(h); 171 stack.add(h);
158 } 172 }
159 } 173 }
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 94f466966..93bbb4b4d 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -23,6 +23,7 @@ pub enum HighlightTag {
23 Constant, 23 Constant,
24 Enum, 24 Enum,
25 EnumVariant, 25 EnumVariant,
26 EscapeSequence,
26 Field, 27 Field,
27 FormatSpecifier, 28 FormatSpecifier,
28 Function, 29 Function,
@@ -55,6 +56,7 @@ pub enum HighlightModifier {
55 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 56 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
56 /// not. 57 /// not.
57 Definition, 58 Definition,
59 Documentation,
58 Mutable, 60 Mutable,
59 Unsafe, 61 Unsafe,
60} 62}
@@ -71,6 +73,7 @@ impl HighlightTag {
71 HighlightTag::Constant => "constant", 73 HighlightTag::Constant => "constant",
72 HighlightTag::Enum => "enum", 74 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 75 HighlightTag::EnumVariant => "enum_variant",
76 HighlightTag::EscapeSequence => "escape_sequence",
74 HighlightTag::Field => "field", 77 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier", 78 HighlightTag::FormatSpecifier => "format_specifier",
76 HighlightTag::Function => "function", 79 HighlightTag::Function => "function",
@@ -106,6 +109,7 @@ impl HighlightModifier {
106 HighlightModifier::Attribute, 109 HighlightModifier::Attribute,
107 HighlightModifier::ControlFlow, 110 HighlightModifier::ControlFlow,
108 HighlightModifier::Definition, 111 HighlightModifier::Definition,
112 HighlightModifier::Documentation,
109 HighlightModifier::Mutable, 113 HighlightModifier::Mutable,
110 HighlightModifier::Unsafe, 114 HighlightModifier::Unsafe,
111 ]; 115 ];
@@ -115,6 +119,7 @@ impl HighlightModifier {
115 HighlightModifier::Attribute => "attribute", 119 HighlightModifier::Attribute => "attribute",
116 HighlightModifier::ControlFlow => "control", 120 HighlightModifier::ControlFlow => "control",
117 HighlightModifier::Definition => "declaration", 121 HighlightModifier::Definition => "declaration",
122 HighlightModifier::Documentation => "documentation",
118 HighlightModifier::Mutable => "mutable", 123 HighlightModifier::Mutable => "mutable",
119 HighlightModifier::Unsafe => "unsafe", 124 HighlightModifier::Unsafe => "unsafe",
120 } 125 }
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 070b24f45..93a276ffe 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -246,6 +246,10 @@ fn main() {
246 246
247 println!(r"Hello, {}!", "world"); 247 println!(r"Hello, {}!", "world");
248 248
249 // escape sequences
250 println!("Hello\nWorld");
251 println!("\u{48}\x65\x6C\x6C\x6F World");
252
249 println!("{\x41}", A = 92); 253 println!("{\x41}", A = 92);
250 println!("{ничоси}", ничоси = 92); 254 println!("{ничоси}", ничоси = 92);
251}"# 255}"#
@@ -287,7 +291,13 @@ fn main() {
287fn test_highlight_doctest() { 291fn test_highlight_doctest() {
288 check_highlighting( 292 check_highlighting(
289 r#" 293 r#"
294struct Foo {
295 bar: bool,
296}
297
290impl Foo { 298impl Foo {
299 pub const bar: bool = true;
300
291 /// Constructs a new `Foo`. 301 /// Constructs a new `Foo`.
292 /// 302 ///
293 /// # Examples 303 /// # Examples
@@ -297,7 +307,7 @@ impl Foo {
297 /// let mut foo: Foo = Foo::new(); 307 /// let mut foo: Foo = Foo::new();
298 /// ``` 308 /// ```
299 pub const fn new() -> Foo { 309 pub const fn new() -> Foo {
300 Foo { } 310 Foo { bar: true }
301 } 311 }
302 312
303 /// `bar` method on `Foo`. 313 /// `bar` method on `Foo`.
@@ -305,11 +315,15 @@ impl Foo {
305 /// # Examples 315 /// # Examples
306 /// 316 ///
307 /// ``` 317 /// ```
318 /// use x::y;
319 ///
308 /// let foo = Foo::new(); 320 /// let foo = Foo::new();
309 /// 321 ///
310 /// // calls bar on foo 322 /// // calls bar on foo
311 /// assert!(foo.bar()); 323 /// assert!(foo.bar());
312 /// 324 ///
325 /// let bar = foo.bar || Foo::bar;
326 ///
313 /// /* multi-line 327 /// /* multi-line
314 /// comment */ 328 /// comment */
315 /// 329 ///
@@ -319,18 +333,31 @@ impl Foo {
319 /// 333 ///
320 /// ``` 334 /// ```
321 /// 335 ///
322 /// ``` 336 /// ```rust,no_run
323 /// let foobar = Foo::new().bar(); 337 /// let foobar = Foo::new().bar();
324 /// ``` 338 /// ```
339 ///
340 /// ```sh
341 /// echo 1
342 /// ```
325 pub fn foo(&self) -> bool { 343 pub fn foo(&self) -> bool {
326 true 344 true
327 } 345 }
328} 346}
347
348/// ```
349/// noop!(1);
350/// ```
351macro_rules! noop {
352 ($expr:expr) => {
353 $expr
354 }
355}
329"# 356"#
330 .trim(), 357 .trim(),
331 "crates/ra_ide/src/snapshots/highlight_doctest.html", 358 "crates/ra_ide/src/snapshots/highlight_doctest.html",
332 false, 359 false,
333 ) 360 );
334} 361}
335 362
336/// Highlights the code given by the `ra_fixture` argument, renders the 363/// Highlights the code given by the `ra_fixture` argument, renders the