diff options
Diffstat (limited to 'crates/ra_ide')
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" } | |||
29 | ra_prof = { path = "../ra_prof" } | 29 | ra_prof = { path = "../ra_prof" } |
30 | test_utils = { path = "../test_utils" } | 30 | test_utils = { path = "../test_utils" } |
31 | ra_assists = { path = "../ra_assists" } | 31 | ra_assists = { path = "../ra_assists" } |
32 | ra_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 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, | 10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, |
11 | Semantics, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 15 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use 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 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use 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 | ||
139 | fn 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 | |||
134 | fn check_unnecessary_braces_in_use_statement( | 201 | fn 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 | ||
3 | use hir::{ | 3 | use 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 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
@@ -13,7 +13,9 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, 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 | ||
29 | impl Default for HoverConfig { | 32 | impl 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 | ||
35 | impl HoverConfig { | 38 | impl 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 { | |||
52 | pub enum HoverAction { | 56 | pub 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)] | ||
63 | pub 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 | ||
236 | fn 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 | |||
221 | fn hover_text( | 274 | fn 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 | ||
251 | fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | 304 | fn 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 |
319 | fn 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 | |||
323 | fn 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 | ||
268 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { | 327 | fn 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 | }; |
84 | pub use ra_ide_db::{ | 83 | pub 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 | }; |
91 | pub use ra_ssr::SsrError; | ||
92 | pub use ra_text_edit::{Indel, TextEdit}; | 92 | pub use ra_text_edit::{Indel, TextEdit}; |
93 | 93 | ||
94 | pub type Cancelable<T> = Result<T, Canceled>; | 94 | pub 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 | ||
8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { | 8 | pub(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 | ||
3 | use hir::{ModuleSource, Semantics}; | 3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | ||
7 | RootDatabase, | ||
8 | }; | ||
6 | use ra_syntax::{ | 9 | use 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 | }; |
10 | use ra_text_edit::TextEdit; | 13 | use ra_text_edit::TextEdit; |
11 | use std::convert::TryInto; | 14 | use 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 | ||
46 | fn find_name_and_module_at_offset( | 47 | fn 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 | ||
55 | fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { | 75 | fn 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 | ||
79 | fn rename_mod( | 99 | fn 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 | ||
134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 146 | fn 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; | 640 | mod bar; |
629 | 641 | ||
630 | //- /bar.rs | 642 | //- /bar.rs |
631 | mod foo<|>; | 643 | mod 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 | ||
695 | pub mod foo; | ||
696 | pub mod bar; | ||
697 | fn main() {} | ||
698 | |||
699 | //- /foo.rs | ||
700 | pub struct FooContent; | ||
701 | |||
702 | //- /bar.rs | ||
703 | use 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; | 765 | mod 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; | 840 | mod bar; |
759 | fn f() { | 841 | fn f() { |
760 | bar::foo::fun() | 842 | bar::foo::fun() |
761 | } | 843 | } |
762 | 844 | ||
763 | //- /bar.rs | 845 | //- /bar.rs |
764 | pub mod foo<|>; | 846 | pub 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>() -> <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>() -> <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>) -> <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>) -> <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) => { | ||
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 @@ | |||
1 | use std::{collections::HashMap, iter::once, str::FromStr}; | 1 | use ra_db::SourceDatabaseExt; |
2 | |||
3 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | ||
4 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 2 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
5 | use ra_syntax::ast::{ | ||
6 | make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, | ||
7 | RecordField, RecordLit, | ||
8 | }; | ||
9 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; | ||
10 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
11 | use rustc_hash::FxHashMap; | ||
12 | 3 | ||
13 | use crate::SourceFileEdit; | 4 | use crate::SourceFileEdit; |
14 | 5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; | |
15 | #[derive(Debug, PartialEq)] | ||
16 | pub struct SsrError(String); | ||
17 | |||
18 | impl 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 | |||
24 | impl 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 | // |=== |
48 | pub fn parse_search_replace( | 29 | pub 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)] | ||
71 | struct SsrQuery { | ||
72 | pattern: SsrPattern, | ||
73 | template: SsrTemplate, | ||
74 | } | ||
75 | |||
76 | #[derive(Debug)] | ||
77 | struct 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)] | ||
84 | struct Var(String); | ||
85 | |||
86 | #[derive(Debug)] | ||
87 | struct SsrTemplate { | ||
88 | template: SyntaxNode, | ||
89 | placeholders: FxHashMap<SyntaxNode, Var>, | ||
90 | } | ||
91 | |||
92 | type Binding = HashMap<Var, SyntaxNode>; | ||
93 | |||
94 | #[derive(Debug)] | ||
95 | struct Match { | ||
96 | place: SyntaxNode, | ||
97 | binding: Binding, | ||
98 | ignored_comments: Vec<Comment>, | ||
99 | } | ||
100 | |||
101 | #[derive(Debug)] | ||
102 | struct SsrMatches { | ||
103 | matches: Vec<Match>, | ||
104 | } | ||
105 | |||
106 | impl 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 | |||
160 | fn 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 | |||
169 | fn 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 | |||
180 | fn 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 | |||
188 | fn 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 | |||
196 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { | ||
197 | let name = format!("${}", var); | ||
198 | template.replace(&name, new_var) | ||
199 | } | ||
200 | |||
201 | fn 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 | |||
210 | fn 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 | |||
408 | fn 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 | |||
419 | fn 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)] | ||
443 | mod 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}; | |||
16 | use ra_syntax::{ast, Parse, SyntaxNode}; | 16 | use ra_syntax::{ast, Parse, SyntaxNode}; |
17 | 17 | ||
18 | use crate::FileId; | 18 | use crate::FileId; |
19 | use rustc_hash::FxHashMap; | ||
19 | 20 | ||
20 | fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | 21 | fn 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 | ||
124 | impl fmt::Display for LibrarySymbolsStats { | 125 | impl 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 | ||
130 | impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { | 131 | impl 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> { | |||
410 | fn highlight_element( | 428 | fn 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 | |||
645 | fn 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; | |||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
8 | use stdx::SepBy; | 8 | use stdx::SepBy; |
9 | 9 | ||
10 | use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | 10 | use crate::{ |
11 | call_info::ActiveParameter, Analysis, HighlightModifier, HighlightTag, HighlightedRange, | ||
12 | RootDatabase, | ||
13 | }; | ||
11 | 14 | ||
12 | use super::HighlightedRangeStack; | 15 | use 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 |
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | 57 | type RangesMap = BTreeMap<TextSize, TextSize>; |
55 | 58 | ||
59 | const RUSTDOC_FENCE: &'static str = "```"; | ||
60 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = | ||
61 | &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"]; | ||
62 | |||
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() { | |||
287 | fn test_highlight_doctest() { | 291 | fn test_highlight_doctest() { |
288 | check_highlighting( | 292 | check_highlighting( |
289 | r#" | 293 | r#" |
294 | struct Foo { | ||
295 | bar: bool, | ||
296 | } | ||
297 | |||
290 | impl Foo { | 298 | impl 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 | /// ``` | ||
351 | macro_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 |