diff options
Diffstat (limited to 'crates/ra_ide/src/display/navigation_target.rs')
-rw-r--r-- | crates/ra_ide/src/display/navigation_target.rs | 207 |
1 files changed, 125 insertions, 82 deletions
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 0b52b01ab..45fbc86ef 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | TextRange, | 11 | TextRange, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use crate::{FileRange, FileSymbol}; | 14 | use crate::FileSymbol; |
15 | 15 | ||
16 | use super::short_label::ShortLabel; | 16 | use super::short_label::ShortLabel; |
17 | 17 | ||
@@ -22,15 +22,28 @@ use super::short_label::ShortLabel; | |||
22 | /// code, like a function or a struct, but this is not strictly required. | 22 | /// code, like a function or a struct, but this is not strictly required. |
23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
24 | pub struct NavigationTarget { | 24 | pub struct NavigationTarget { |
25 | // FIXME: use FileRange? | 25 | pub file_id: FileId, |
26 | file_id: FileId, | 26 | /// Range which encompasses the whole element. |
27 | full_range: TextRange, | 27 | /// |
28 | name: SmolStr, | 28 | /// Should include body, doc comments, attributes, etc. |
29 | kind: SyntaxKind, | 29 | /// |
30 | focus_range: Option<TextRange>, | 30 | /// Clients should use this range to answer "is the cursor inside the |
31 | container_name: Option<SmolStr>, | 31 | /// element?" question. |
32 | description: Option<String>, | 32 | pub full_range: TextRange, |
33 | docs: Option<String>, | 33 | /// A "most interesting" range withing the `full_range`. |
34 | /// | ||
35 | /// Typically, `full_range` is the whole syntax node, including doc | ||
36 | /// comments, and `focus_range` is the range of the identifier. "Most | ||
37 | /// interesting" range within the full range, typically the range of | ||
38 | /// identifier. | ||
39 | /// | ||
40 | /// Clients should place the cursor on this range when navigating to this target. | ||
41 | pub focus_range: Option<TextRange>, | ||
42 | pub name: SmolStr, | ||
43 | pub kind: SyntaxKind, | ||
44 | pub container_name: Option<SmolStr>, | ||
45 | pub description: Option<String>, | ||
46 | pub docs: Option<String>, | ||
34 | } | 47 | } |
35 | 48 | ||
36 | pub(crate) trait ToNav { | 49 | pub(crate) trait ToNav { |
@@ -42,52 +55,10 @@ pub(crate) trait TryToNav { | |||
42 | } | 55 | } |
43 | 56 | ||
44 | impl NavigationTarget { | 57 | impl NavigationTarget { |
45 | /// When `focus_range` is specified, returns it. otherwise | 58 | pub fn focus_or_full_range(&self) -> TextRange { |
46 | /// returns `full_range` | ||
47 | pub fn range(&self) -> TextRange { | ||
48 | self.focus_range.unwrap_or(self.full_range) | 59 | self.focus_range.unwrap_or(self.full_range) |
49 | } | 60 | } |
50 | 61 | ||
51 | pub fn name(&self) -> &SmolStr { | ||
52 | &self.name | ||
53 | } | ||
54 | |||
55 | pub fn container_name(&self) -> Option<&SmolStr> { | ||
56 | self.container_name.as_ref() | ||
57 | } | ||
58 | |||
59 | pub fn kind(&self) -> SyntaxKind { | ||
60 | self.kind | ||
61 | } | ||
62 | |||
63 | pub fn file_id(&self) -> FileId { | ||
64 | self.file_id | ||
65 | } | ||
66 | |||
67 | pub fn file_range(&self) -> FileRange { | ||
68 | FileRange { file_id: self.file_id, range: self.full_range } | ||
69 | } | ||
70 | |||
71 | pub fn full_range(&self) -> TextRange { | ||
72 | self.full_range | ||
73 | } | ||
74 | |||
75 | pub fn docs(&self) -> Option<&str> { | ||
76 | self.docs.as_deref() | ||
77 | } | ||
78 | |||
79 | pub fn description(&self) -> Option<&str> { | ||
80 | self.description.as_deref() | ||
81 | } | ||
82 | |||
83 | /// A "most interesting" range withing the `full_range`. | ||
84 | /// | ||
85 | /// Typically, `full_range` is the whole syntax node, | ||
86 | /// including doc comments, and `focus_range` is the range of the identifier. | ||
87 | pub fn focus_range(&self) -> Option<TextRange> { | ||
88 | self.focus_range | ||
89 | } | ||
90 | |||
91 | pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | 62 | pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { |
92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | 63 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); |
93 | if let Some(src) = module.declaration_source(db) { | 64 | if let Some(src) = module.declaration_source(db) { |
@@ -114,17 +85,12 @@ impl NavigationTarget { | |||
114 | 85 | ||
115 | #[cfg(test)] | 86 | #[cfg(test)] |
116 | pub(crate) fn debug_render(&self) -> String { | 87 | pub(crate) fn debug_render(&self) -> String { |
117 | let mut buf = format!( | 88 | let mut buf = |
118 | "{} {:?} {:?} {:?}", | 89 | format!("{} {:?} {:?} {:?}", self.name, self.kind, self.file_id, self.full_range); |
119 | self.name(), | 90 | if let Some(focus_range) = self.focus_range { |
120 | self.kind(), | ||
121 | self.file_id(), | ||
122 | self.full_range() | ||
123 | ); | ||
124 | if let Some(focus_range) = self.focus_range() { | ||
125 | buf.push_str(&format!(" {:?}", focus_range)) | 91 | buf.push_str(&format!(" {:?}", focus_range)) |
126 | } | 92 | } |
127 | if let Some(container_name) = self.container_name() { | 93 | if let Some(container_name) = &self.container_name { |
128 | buf.push_str(&format!(" {}", container_name)) | 94 | buf.push_str(&format!(" {}", container_name)) |
129 | } | 95 | } |
130 | buf | 96 | buf |
@@ -278,16 +244,22 @@ impl ToNav for hir::Module { | |||
278 | impl ToNav for hir::ImplDef { | 244 | impl ToNav for hir::ImplDef { |
279 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 245 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
280 | let src = self.source(db); | 246 | let src = self.source(db); |
281 | let frange = if let Some(item) = self.is_builtin_derive(db) { | 247 | let derive_attr = self.is_builtin_derive(db); |
248 | let frange = if let Some(item) = &derive_attr { | ||
282 | original_range(db, item.syntax()) | 249 | original_range(db, item.syntax()) |
283 | } else { | 250 | } else { |
284 | original_range(db, src.as_ref().map(|it| it.syntax())) | 251 | original_range(db, src.as_ref().map(|it| it.syntax())) |
285 | }; | 252 | }; |
253 | let focus_range = if derive_attr.is_some() { | ||
254 | None | ||
255 | } else { | ||
256 | src.value.target_type().map(|ty| original_range(db, src.with_value(ty.syntax())).range) | ||
257 | }; | ||
286 | 258 | ||
287 | NavigationTarget::from_syntax( | 259 | NavigationTarget::from_syntax( |
288 | frange.file_id, | 260 | frange.file_id, |
289 | "impl".into(), | 261 | "impl".into(), |
290 | None, | 262 | focus_range, |
291 | frange.range, | 263 | frange.range, |
292 | src.value.syntax().kind(), | 264 | src.value.syntax().kind(), |
293 | ) | 265 | ) |
@@ -407,16 +379,16 @@ pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option | |||
407 | 379 | ||
408 | match_ast! { | 380 | match_ast! { |
409 | match node { | 381 | match node { |
410 | ast::FnDef(it) => it.doc_comment_text(), | 382 | ast::Fn(it) => it.doc_comment_text(), |
411 | ast::StructDef(it) => it.doc_comment_text(), | 383 | ast::Struct(it) => it.doc_comment_text(), |
412 | ast::EnumDef(it) => it.doc_comment_text(), | 384 | ast::Enum(it) => it.doc_comment_text(), |
413 | ast::TraitDef(it) => it.doc_comment_text(), | 385 | ast::Trait(it) => it.doc_comment_text(), |
414 | ast::Module(it) => it.doc_comment_text(), | 386 | ast::Module(it) => it.doc_comment_text(), |
415 | ast::TypeAliasDef(it) => it.doc_comment_text(), | 387 | ast::TypeAlias(it) => it.doc_comment_text(), |
416 | ast::ConstDef(it) => it.doc_comment_text(), | 388 | ast::Const(it) => it.doc_comment_text(), |
417 | ast::StaticDef(it) => it.doc_comment_text(), | 389 | ast::Static(it) => it.doc_comment_text(), |
418 | ast::RecordFieldDef(it) => it.doc_comment_text(), | 390 | ast::RecordField(it) => it.doc_comment_text(), |
419 | ast::EnumVariant(it) => it.doc_comment_text(), | 391 | ast::Variant(it) => it.doc_comment_text(), |
420 | ast::MacroCall(it) => it.doc_comment_text(), | 392 | ast::MacroCall(it) => it.doc_comment_text(), |
421 | _ => None, | 393 | _ => None, |
422 | } | 394 | } |
@@ -432,17 +404,88 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> | |||
432 | 404 | ||
433 | match_ast! { | 405 | match_ast! { |
434 | match node { | 406 | match node { |
435 | ast::FnDef(it) => it.short_label(), | 407 | ast::Fn(it) => it.short_label(), |
436 | ast::StructDef(it) => it.short_label(), | 408 | ast::Struct(it) => it.short_label(), |
437 | ast::EnumDef(it) => it.short_label(), | 409 | ast::Enum(it) => it.short_label(), |
438 | ast::TraitDef(it) => it.short_label(), | 410 | ast::Trait(it) => it.short_label(), |
439 | ast::Module(it) => it.short_label(), | 411 | ast::Module(it) => it.short_label(), |
440 | ast::TypeAliasDef(it) => it.short_label(), | 412 | ast::TypeAlias(it) => it.short_label(), |
441 | ast::ConstDef(it) => it.short_label(), | 413 | ast::Const(it) => it.short_label(), |
442 | ast::StaticDef(it) => it.short_label(), | 414 | ast::Static(it) => it.short_label(), |
443 | ast::RecordFieldDef(it) => it.short_label(), | 415 | ast::RecordField(it) => it.short_label(), |
444 | ast::EnumVariant(it) => it.short_label(), | 416 | ast::Variant(it) => it.short_label(), |
445 | _ => None, | 417 | _ => None, |
446 | } | 418 | } |
447 | } | 419 | } |
448 | } | 420 | } |
421 | |||
422 | #[cfg(test)] | ||
423 | mod tests { | ||
424 | use expect::expect; | ||
425 | |||
426 | use crate::{mock_analysis::single_file, Query}; | ||
427 | |||
428 | #[test] | ||
429 | fn test_nav_for_symbol() { | ||
430 | let (analysis, _) = single_file( | ||
431 | r#" | ||
432 | enum FooInner { } | ||
433 | fn foo() { enum FooInner { } } | ||
434 | "#, | ||
435 | ); | ||
436 | |||
437 | let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap(); | ||
438 | expect![[r#" | ||
439 | [ | ||
440 | NavigationTarget { | ||
441 | file_id: FileId( | ||
442 | 1, | ||
443 | ), | ||
444 | full_range: 0..17, | ||
445 | focus_range: Some( | ||
446 | 5..13, | ||
447 | ), | ||
448 | name: "FooInner", | ||
449 | kind: ENUM, | ||
450 | container_name: None, | ||
451 | description: Some( | ||
452 | "enum FooInner", | ||
453 | ), | ||
454 | docs: None, | ||
455 | }, | ||
456 | NavigationTarget { | ||
457 | file_id: FileId( | ||
458 | 1, | ||
459 | ), | ||
460 | full_range: 29..46, | ||
461 | focus_range: Some( | ||
462 | 34..42, | ||
463 | ), | ||
464 | name: "FooInner", | ||
465 | kind: ENUM, | ||
466 | container_name: Some( | ||
467 | "foo", | ||
468 | ), | ||
469 | description: Some( | ||
470 | "enum FooInner", | ||
471 | ), | ||
472 | docs: None, | ||
473 | }, | ||
474 | ] | ||
475 | "#]] | ||
476 | .assert_debug_eq(&navs); | ||
477 | } | ||
478 | |||
479 | #[test] | ||
480 | fn test_world_symbols_are_case_sensitive() { | ||
481 | let (analysis, _) = single_file( | ||
482 | r#" | ||
483 | fn foo() {} | ||
484 | struct Foo; | ||
485 | "#, | ||
486 | ); | ||
487 | |||
488 | let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap(); | ||
489 | assert_eq!(navs.len(), 2) | ||
490 | } | ||
491 | } | ||