aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/display/navigation_target.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/display/navigation_target.rs')
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs207
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
14use crate::{FileRange, FileSymbol}; 14use crate::FileSymbol;
15 15
16use super::short_label::ShortLabel; 16use 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)]
24pub struct NavigationTarget { 24pub 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
36pub(crate) trait ToNav { 49pub(crate) trait ToNav {
@@ -42,52 +55,10 @@ pub(crate) trait TryToNav {
42} 55}
43 56
44impl NavigationTarget { 57impl 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 {
278impl ToNav for hir::ImplDef { 244impl 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)]
423mod 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#"
432enum FooInner { }
433fn 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#"
483fn foo() {}
484struct Foo;
485"#,
486 );
487
488 let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
489 assert_eq!(navs.len(), 2)
490 }
491}