1 //! See [`NavigationTarget`].
7 symbols
::FileSymbol
, AssocItem
, FieldSource
, HasContainer
, HasSource
, HirDisplay
, HirFileId
,
8 InFile
, LocalSource
, ModuleSource
,
11 base_db
::{FileId, FileRange}
,
13 documentation
::{Documentation, HasDocs}
,
14 RootDatabase
, SymbolKind
,
19 AstNode
, SmolStr
, SyntaxNode
, TextRange
,
22 /// `NavigationTarget` represents an element in the editor's UI which you can
23 /// click on to navigate to a particular piece of code.
25 /// Typically, a `NavigationTarget` corresponds to some element in the source
26 /// code, like a function or a struct, but this is not strictly required.
27 #[derive(Clone, PartialEq, Eq, Hash)]
28 pub struct NavigationTarget
{
30 /// Range which encompasses the whole element.
32 /// Should include body, doc comments, attributes, etc.
34 /// Clients should use this range to answer "is the cursor inside the
35 /// element?" question.
36 pub full_range
: TextRange
,
37 /// A "most interesting" range within the `full_range`.
39 /// Typically, `full_range` is the whole syntax node, including doc
40 /// comments, and `focus_range` is the range of the identifier.
42 /// Clients should place the cursor on this range when navigating to this target.
43 pub focus_range
: Option
<TextRange
>,
45 pub kind
: Option
<SymbolKind
>,
46 pub container_name
: Option
<SmolStr
>,
47 pub description
: Option
<String
>,
48 pub docs
: Option
<Documentation
>,
49 /// In addition to a `name` field, a `NavigationTarget` may also be aliased
50 /// In such cases we want a `NavigationTarget` to be accessible by its alias
51 pub alias
: Option
<SmolStr
>,
54 impl fmt
::Debug
for NavigationTarget
{
55 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
56 let mut f
= f
.debug_struct("NavigationTarget");
58 ($
($name
:ident
)*) => {$
(
59 if let Some(it
) = &self.$name
{
60 f
.field(stringify
!($name
), it
);
64 f
.field("file_id", &self.file_id
).field("full_range", &self.full_range
);
66 f
.field("name", &self.name
);
67 opt
!(kind container_name description docs
);
72 pub(crate) trait ToNav
{
73 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
;
76 pub(crate) trait TryToNav
{
77 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
>;
80 impl<T
: TryToNav
, U
: TryToNav
> TryToNav
for Either
<T
, U
> {
81 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
83 Either
::Left(it
) => it
.try_to_nav(db
),
84 Either
::Right(it
) => it
.try_to_nav(db
),
89 impl NavigationTarget
{
90 pub fn focus_or_full_range(&self) -> TextRange
{
91 self.focus_range
.unwrap_or(self.full_range
)
94 pub(crate) fn from_module_to_decl(db
: &RootDatabase
, module
: hir
::Module
) -> NavigationTarget
{
95 let name
= module
.name(db
).map(|it
| it
.to_smol_str()).unwrap_or_default();
96 if let Some(InFile { value, file_id }
) = &module
.declaration_source(db
) {
97 let (file_id
, full_range
, focus_range
) =
98 orig_range_with_focus(db
, *file_id
, value
.syntax(), value
.name());
99 let mut res
= NavigationTarget
::from_syntax(
106 res
.docs
= module
.docs(db
);
107 res
.description
= Some(module
.display(db
).to_string());
114 pub(crate) fn debug_render(&self) -> String
{
115 let mut buf
= format
!(
122 if let Some(focus_range
) = self.focus_range
{
123 buf
.push_str(&format
!(" {focus_range:?}"))
125 if let Some(container_name
) = &self.container_name
{
126 buf
.push_str(&format
!(" {container_name}"))
131 /// Allows `NavigationTarget` to be created from a `NameOwner`
132 pub(crate) fn from_named(
134 InFile { file_id, value }
: InFile
<&dyn ast
::HasName
>,
136 ) -> NavigationTarget
{
137 let name
= value
.name().map(|it
| it
.text().into()).unwrap_or_else(|| "_".into());
139 let (file_id
, full_range
, focus_range
) =
140 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.name());
142 NavigationTarget
::from_syntax(file_id
, name
, focus_range
, full_range
, kind
)
148 focus_range
: Option
<TextRange
>,
149 full_range
: TextRange
,
151 ) -> NavigationTarget
{
158 container_name
: None
,
166 impl TryToNav
for FileSymbol
{
167 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
168 let full_range
= self.loc
.original_range(db
);
169 let focus_range
= self.loc
.original_name_range(db
).and_then(|it
| {
170 if it
.file_id
== full_range
.file_id
{
177 Some(NavigationTarget
{
178 file_id
: full_range
.file_id
,
181 .then(|| self.def
.name(db
))
183 .map_or_else(|| self.name
.clone(), |it
| it
.to_smol_str()),
184 alias
: self.is_alias
.then(|| self.name
.clone()),
185 kind
: Some(hir
::ModuleDefId
::from(self.def
).into()),
186 full_range
: full_range
.range
,
188 container_name
: self.container_name
.clone(),
189 description
: match self.def
{
190 hir
::ModuleDef
::Module(it
) => Some(it
.display(db
).to_string()),
191 hir
::ModuleDef
::Function(it
) => Some(it
.display(db
).to_string()),
192 hir
::ModuleDef
::Adt(it
) => Some(it
.display(db
).to_string()),
193 hir
::ModuleDef
::Variant(it
) => Some(it
.display(db
).to_string()),
194 hir
::ModuleDef
::Const(it
) => Some(it
.display(db
).to_string()),
195 hir
::ModuleDef
::Static(it
) => Some(it
.display(db
).to_string()),
196 hir
::ModuleDef
::Trait(it
) => Some(it
.display(db
).to_string()),
197 hir
::ModuleDef
::TraitAlias(it
) => Some(it
.display(db
).to_string()),
198 hir
::ModuleDef
::TypeAlias(it
) => Some(it
.display(db
).to_string()),
199 hir
::ModuleDef
::Macro(it
) => Some(it
.display(db
).to_string()),
200 hir
::ModuleDef
::BuiltinType(_
) => None
,
207 impl TryToNav
for Definition
{
208 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
210 Definition
::Local(it
) => Some(it
.to_nav(db
)),
211 Definition
::Label(it
) => Some(it
.to_nav(db
)),
212 Definition
::Module(it
) => Some(it
.to_nav(db
)),
213 Definition
::Macro(it
) => it
.try_to_nav(db
),
214 Definition
::Field(it
) => it
.try_to_nav(db
),
215 Definition
::SelfType(it
) => it
.try_to_nav(db
),
216 Definition
::GenericParam(it
) => it
.try_to_nav(db
),
217 Definition
::Function(it
) => it
.try_to_nav(db
),
218 Definition
::Adt(it
) => it
.try_to_nav(db
),
219 Definition
::Variant(it
) => it
.try_to_nav(db
),
220 Definition
::Const(it
) => it
.try_to_nav(db
),
221 Definition
::Static(it
) => it
.try_to_nav(db
),
222 Definition
::Trait(it
) => it
.try_to_nav(db
),
223 Definition
::TraitAlias(it
) => it
.try_to_nav(db
),
224 Definition
::TypeAlias(it
) => it
.try_to_nav(db
),
225 Definition
::ExternCrateDecl(it
) => Some(it
.try_to_nav(db
)?
),
226 Definition
::BuiltinType(_
) => None
,
227 Definition
::ToolModule(_
) => None
,
228 Definition
::BuiltinAttr(_
) => None
,
229 // FIXME: The focus range should be set to the helper declaration
230 Definition
::DeriveHelper(it
) => it
.derive().try_to_nav(db
),
235 impl TryToNav
for hir
::ModuleDef
{
236 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
238 hir
::ModuleDef
::Module(it
) => Some(it
.to_nav(db
)),
239 hir
::ModuleDef
::Function(it
) => it
.try_to_nav(db
),
240 hir
::ModuleDef
::Adt(it
) => it
.try_to_nav(db
),
241 hir
::ModuleDef
::Variant(it
) => it
.try_to_nav(db
),
242 hir
::ModuleDef
::Const(it
) => it
.try_to_nav(db
),
243 hir
::ModuleDef
::Static(it
) => it
.try_to_nav(db
),
244 hir
::ModuleDef
::Trait(it
) => it
.try_to_nav(db
),
245 hir
::ModuleDef
::TraitAlias(it
) => it
.try_to_nav(db
),
246 hir
::ModuleDef
::TypeAlias(it
) => it
.try_to_nav(db
),
247 hir
::ModuleDef
::Macro(it
) => it
.try_to_nav(db
),
248 hir
::ModuleDef
::BuiltinType(_
) => None
,
253 pub(crate) trait ToNavFromAst
: Sized
{
254 const KIND
: SymbolKind
;
255 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
261 fn container_name(db
: &RootDatabase
, t
: impl HasContainer
) -> Option
<SmolStr
> {
262 match t
.container(db
) {
263 hir
::ItemContainer
::Trait(it
) => Some(it
.name(db
).to_smol_str()),
264 // FIXME: Handle owners of blocks correctly here
265 hir
::ItemContainer
::Module(it
) => it
.name(db
).map(|name
| name
.to_smol_str()),
270 impl ToNavFromAst
for hir
::Function
{
271 const KIND
: SymbolKind
= SymbolKind
::Function
;
272 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
273 container_name(db
, self)
277 impl ToNavFromAst
for hir
::Const
{
278 const KIND
: SymbolKind
= SymbolKind
::Const
;
279 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
280 container_name(db
, self)
283 impl ToNavFromAst
for hir
::Static
{
284 const KIND
: SymbolKind
= SymbolKind
::Static
;
285 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
286 container_name(db
, self)
289 impl ToNavFromAst
for hir
::Struct
{
290 const KIND
: SymbolKind
= SymbolKind
::Struct
;
291 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
292 container_name(db
, self)
295 impl ToNavFromAst
for hir
::Enum
{
296 const KIND
: SymbolKind
= SymbolKind
::Enum
;
297 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
298 container_name(db
, self)
301 impl ToNavFromAst
for hir
::Variant
{
302 const KIND
: SymbolKind
= SymbolKind
::Variant
;
304 impl ToNavFromAst
for hir
::Union
{
305 const KIND
: SymbolKind
= SymbolKind
::Union
;
306 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
307 container_name(db
, self)
310 impl ToNavFromAst
for hir
::TypeAlias
{
311 const KIND
: SymbolKind
= SymbolKind
::TypeAlias
;
312 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
313 container_name(db
, self)
316 impl ToNavFromAst
for hir
::Trait
{
317 const KIND
: SymbolKind
= SymbolKind
::Trait
;
318 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
319 container_name(db
, self)
322 impl ToNavFromAst
for hir
::TraitAlias
{
323 const KIND
: SymbolKind
= SymbolKind
::TraitAlias
;
324 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
325 container_name(db
, self)
329 impl<D
> TryToNav
for D
331 D
: HasSource
+ ToNavFromAst
+ Copy
+ HasDocs
+ HirDisplay
,
332 D
::Ast
: ast
::HasName
,
334 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
335 let src
= self.source(db
)?
;
336 let mut res
= NavigationTarget
::from_named(
338 src
.as_ref().map(|it
| it
as &dyn ast
::HasName
),
341 res
.docs
= self.docs(db
);
342 res
.description
= Some(self.display(db
).to_string());
343 res
.container_name
= self.container_name(db
);
348 impl ToNav
for hir
::Module
{
349 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
350 let InFile { file_id, value }
= self.definition_source(db
);
352 let name
= self.name(db
).map(|it
| it
.to_smol_str()).unwrap_or_default();
353 let (syntax
, focus
) = match &value
{
354 ModuleSource
::SourceFile(node
) => (node
.syntax(), None
),
355 ModuleSource
::Module(node
) => (node
.syntax(), node
.name()),
356 ModuleSource
::BlockExpr(node
) => (node
.syntax(), None
),
358 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
359 NavigationTarget
::from_syntax(file_id
, name
, focus_range
, full_range
, SymbolKind
::Module
)
363 impl TryToNav
for hir
::Impl
{
364 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
365 let InFile { file_id, value }
= self.source(db
)?
;
366 let derive_attr
= self.as_builtin_derive(db
);
368 let (focus
, syntax
) = match &derive_attr
{
369 Some(attr
) => (None
, attr
.value
.syntax()),
370 None
=> (value
.self_ty(), value
.syntax()),
373 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
374 Some(NavigationTarget
::from_syntax(
384 impl TryToNav
for hir
::ExternCrateDecl
{
385 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
386 let src
= self.source(db
)?
;
387 let InFile { file_id, value }
= src
;
390 .map_or_else(|| value
.name_ref().map(Either
::Left
), |it
| it
.name().map(Either
::Right
));
391 let (file_id
, full_range
, focus_range
) =
392 orig_range_with_focus(db
, file_id
, value
.syntax(), focus
);
393 let mut res
= NavigationTarget
::from_syntax(
395 self.alias_or_name(db
).unwrap_or_else(|| self.name(db
)).to_smol_str(),
401 res
.docs
= self.docs(db
);
402 res
.description
= Some(self.display(db
).to_string());
403 res
.container_name
= container_name(db
, *self);
408 impl TryToNav
for hir
::Field
{
409 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
410 let src
= self.source(db
)?
;
412 let field_source
= match &src
.value
{
413 FieldSource
::Named(it
) => {
415 NavigationTarget
::from_named(db
, src
.with_value(it
), SymbolKind
::Field
);
416 res
.docs
= self.docs(db
);
417 res
.description
= Some(self.display(db
).to_string());
420 FieldSource
::Pos(it
) => {
421 let FileRange { file_id, range }
=
422 src
.with_value(it
.syntax()).original_file_range(db
);
423 NavigationTarget
::from_syntax(file_id
, "".into(), None
, range
, SymbolKind
::Field
)
430 impl TryToNav
for hir
::Macro
{
431 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
432 let src
= self.source(db
)?
;
433 let name_owner
: &dyn ast
::HasName
= match &src
.value
{
434 Either
::Left(it
) => it
,
435 Either
::Right(it
) => it
,
437 let mut res
= NavigationTarget
::from_named(
439 src
.as_ref().with_value(name_owner
),
440 self.kind(db
).into(),
442 res
.docs
= self.docs(db
);
447 impl TryToNav
for hir
::Adt
{
448 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
450 hir
::Adt
::Struct(it
) => it
.try_to_nav(db
),
451 hir
::Adt
::Union(it
) => it
.try_to_nav(db
),
452 hir
::Adt
::Enum(it
) => it
.try_to_nav(db
),
457 impl TryToNav
for hir
::AssocItem
{
458 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
460 AssocItem
::Function(it
) => it
.try_to_nav(db
),
461 AssocItem
::Const(it
) => it
.try_to_nav(db
),
462 AssocItem
::TypeAlias(it
) => it
.try_to_nav(db
),
467 impl TryToNav
for hir
::GenericParam
{
468 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
470 hir
::GenericParam
::TypeParam(it
) => it
.try_to_nav(db
),
471 hir
::GenericParam
::ConstParam(it
) => it
.try_to_nav(db
),
472 hir
::GenericParam
::LifetimeParam(it
) => it
.try_to_nav(db
),
477 impl ToNav
for LocalSource
{
478 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
479 let InFile { file_id, value }
= &self.source
;
480 let file_id
= *file_id
;
481 let local
= self.local
;
482 let (node
, name
) = match &value
{
483 Either
::Left(bind_pat
) => (bind_pat
.syntax(), bind_pat
.name()),
484 Either
::Right(it
) => (it
.syntax(), it
.name()),
487 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, node
, name
);
489 let name
= local
.name(db
).to_smol_str();
490 let kind
= if local
.is_self(db
) {
491 SymbolKind
::SelfParam
492 } else if local
.is_param(db
) {
493 SymbolKind
::ValueParam
504 container_name
: None
,
511 impl ToNav
for hir
::Local
{
512 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
513 self.primary_source(db
).to_nav(db
)
517 impl ToNav
for hir
::Label
{
518 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
519 let InFile { file_id, value }
= self.source(db
);
520 let name
= self.name(db
).to_smol_str();
522 let (file_id
, full_range
, focus_range
) =
523 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.lifetime());
529 kind
: Some(SymbolKind
::Label
),
532 container_name
: None
,
539 impl TryToNav
for hir
::TypeParam
{
540 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
541 let InFile { file_id, value }
= self.merge().source(db
)?
;
542 let name
= self.name(db
).to_smol_str();
544 let value
= match value
{
545 Either
::Left(ast
::TypeOrConstParam
::Type(x
)) => Either
::Left(x
),
546 Either
::Left(ast
::TypeOrConstParam
::Const(_
)) => {
550 Either
::Right(x
) => Either
::Right(x
),
553 let syntax
= match &value
{
554 Either
::Left(type_param
) => type_param
.syntax(),
555 Either
::Right(trait_
) => trait_
.syntax(),
557 let focus
= value
.as_ref().either(|it
| it
.name(), |it
| it
.name());
559 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
561 Some(NavigationTarget
{
565 kind
: Some(SymbolKind
::TypeParam
),
568 container_name
: None
,
575 impl TryToNav
for hir
::TypeOrConstParam
{
576 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
577 self.split(db
).try_to_nav(db
)
581 impl TryToNav
for hir
::LifetimeParam
{
582 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
583 let InFile { file_id, value }
= self.source(db
)?
;
584 let name
= self.name(db
).to_smol_str();
586 let FileRange { file_id, range }
=
587 InFile
::new(file_id
, value
.syntax()).original_file_range(db
);
588 Some(NavigationTarget
{
592 kind
: Some(SymbolKind
::LifetimeParam
),
594 focus_range
: Some(range
),
595 container_name
: None
,
602 impl TryToNav
for hir
::ConstParam
{
603 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
604 let InFile { file_id, value }
= self.merge().source(db
)?
;
605 let name
= self.name(db
).to_smol_str();
607 let value
= match value
{
608 Either
::Left(ast
::TypeOrConstParam
::Const(x
)) => x
,
615 let (file_id
, full_range
, focus_range
) =
616 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.name());
617 Some(NavigationTarget
{
621 kind
: Some(SymbolKind
::ConstParam
),
624 container_name
: None
,
631 fn orig_range_with_focus(
635 name
: Option
<impl AstNode
>,
636 ) -> (FileId
, TextRange
, Option
<TextRange
>) {
637 let FileRange { file_id, range: full_range }
=
638 InFile
::new(hir_file
, value
).original_file_range(db
);
639 let focus_range
= name
640 .and_then(|it
| InFile
::new(hir_file
, it
.syntax()).original_file_range_opt(db
))
641 .and_then(|range
| if range
.file_id
== file_id { Some(range.range) }
else { None }
);
643 (file_id
, full_range
, focus_range
)
648 use expect_test
::expect
;
650 use crate::{fixture, Query}
;
653 fn test_nav_for_symbol() {
654 let (analysis
, _
) = fixture
::file(
657 fn foo() { enum FooInner { } }
661 let navs
= analysis
.symbol_search(Query
::new("FooInner".to_string())).unwrap();
672 description: "enum FooInner",
682 container_name: "foo",
683 description: "enum FooInner",
687 .assert_debug_eq(&navs
);
691 fn test_world_symbols_are_case_sensitive() {
692 let (analysis
, _
) = fixture
::file(
699 let navs
= analysis
.symbol_search(Query
::new("foo".to_string())).unwrap();
700 assert_eq
!(navs
.len(), 2)