1 //! See [`NavigationTarget`].
7 symbols
::FileSymbol
, AssocItem
, Documentation
, FieldSource
, HasAttrs
, HasContainer
, HasSource
,
8 HirDisplay
, HirFileId
, InFile
, LocalSource
, ModuleSource
,
11 base_db
::{FileId, FileRange}
,
14 use ide_db
::{defs::Definition, RootDatabase}
;
18 AstNode
, SmolStr
, SyntaxNode
, TextRange
,
21 /// `NavigationTarget` represents an element in the editor's UI which you can
22 /// click on to navigate to a particular piece of code.
24 /// Typically, a `NavigationTarget` corresponds to some element in the source
25 /// code, like a function or a struct, but this is not strictly required.
26 #[derive(Clone, PartialEq, Eq, Hash)]
27 pub struct NavigationTarget
{
29 /// Range which encompasses the whole element.
31 /// Should include body, doc comments, attributes, etc.
33 /// Clients should use this range to answer "is the cursor inside the
34 /// element?" question.
35 pub full_range
: TextRange
,
36 /// A "most interesting" range within the `full_range`.
38 /// Typically, `full_range` is the whole syntax node, including doc
39 /// comments, and `focus_range` is the range of the identifier.
41 /// Clients should place the cursor on this range when navigating to this target.
42 pub focus_range
: Option
<TextRange
>,
44 pub kind
: Option
<SymbolKind
>,
45 pub container_name
: Option
<SmolStr
>,
46 pub description
: Option
<String
>,
47 pub docs
: Option
<Documentation
>,
48 /// In addition to a `name` field, a `NavigationTarget` may also be aliased
49 /// In such cases we want a `NavigationTarget` to be accessible by its alias
50 pub alias
: Option
<SmolStr
>,
53 impl fmt
::Debug
for NavigationTarget
{
54 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
55 let mut f
= f
.debug_struct("NavigationTarget");
57 ($
($name
:ident
)*) => {$
(
58 if let Some(it
) = &self.$name
{
59 f
.field(stringify
!($name
), it
);
63 f
.field("file_id", &self.file_id
).field("full_range", &self.full_range
);
65 f
.field("name", &self.name
);
66 opt
!(kind container_name description docs
);
71 pub(crate) trait ToNav
{
72 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
;
75 pub(crate) trait TryToNav
{
76 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
>;
79 impl<T
: TryToNav
, U
: TryToNav
> TryToNav
for Either
<T
, U
> {
80 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
82 Either
::Left(it
) => it
.try_to_nav(db
),
83 Either
::Right(it
) => it
.try_to_nav(db
),
88 impl NavigationTarget
{
89 pub fn focus_or_full_range(&self) -> TextRange
{
90 self.focus_range
.unwrap_or(self.full_range
)
93 pub(crate) fn from_module_to_decl(db
: &RootDatabase
, module
: hir
::Module
) -> NavigationTarget
{
94 let name
= module
.name(db
).map(|it
| it
.to_smol_str()).unwrap_or_default();
95 if let Some(InFile { value, file_id }
) = &module
.declaration_source(db
) {
96 let (file_id
, full_range
, focus_range
) =
97 orig_range_with_focus(db
, *file_id
, value
.syntax(), value
.name());
98 let mut res
= NavigationTarget
::from_syntax(
105 res
.docs
= module
.docs(db
);
106 res
.description
= Some(module
.display(db
).to_string());
113 pub(crate) fn debug_render(&self) -> String
{
114 let mut buf
= format
!(
121 if let Some(focus_range
) = self.focus_range
{
122 buf
.push_str(&format
!(" {focus_range:?}"))
124 if let Some(container_name
) = &self.container_name
{
125 buf
.push_str(&format
!(" {container_name}"))
130 /// Allows `NavigationTarget` to be created from a `NameOwner`
131 pub(crate) fn from_named(
133 InFile { file_id, value }
: InFile
<&dyn ast
::HasName
>,
135 ) -> NavigationTarget
{
136 let name
= value
.name().map(|it
| it
.text().into()).unwrap_or_else(|| "_".into());
138 let (file_id
, full_range
, focus_range
) =
139 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.name());
141 NavigationTarget
::from_syntax(file_id
, name
, focus_range
, full_range
, kind
)
147 focus_range
: Option
<TextRange
>,
148 full_range
: TextRange
,
150 ) -> NavigationTarget
{
157 container_name
: None
,
165 impl TryToNav
for FileSymbol
{
166 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
167 let full_range
= self.loc
.original_range(db
);
168 let focus_range
= self.loc
.original_name_range(db
).and_then(|it
| {
169 if it
.file_id
== full_range
.file_id
{
176 Some(NavigationTarget
{
177 file_id
: full_range
.file_id
,
178 name
: if self.is_alias { self.def.name(db)?.to_smol_str() }
else { self.name.clone() }
,
179 alias
: if self.is_alias { Some(self.name.clone()) }
else { None }
,
180 kind
: Some(hir
::ModuleDefId
::from(self.def
).into()),
181 full_range
: full_range
.range
,
183 container_name
: self.container_name
.clone(),
184 description
: match self.def
{
185 hir
::ModuleDef
::Module(it
) => Some(it
.display(db
).to_string()),
186 hir
::ModuleDef
::Function(it
) => Some(it
.display(db
).to_string()),
187 hir
::ModuleDef
::Adt(it
) => Some(it
.display(db
).to_string()),
188 hir
::ModuleDef
::Variant(it
) => Some(it
.display(db
).to_string()),
189 hir
::ModuleDef
::Const(it
) => Some(it
.display(db
).to_string()),
190 hir
::ModuleDef
::Static(it
) => Some(it
.display(db
).to_string()),
191 hir
::ModuleDef
::Trait(it
) => Some(it
.display(db
).to_string()),
192 hir
::ModuleDef
::TraitAlias(it
) => Some(it
.display(db
).to_string()),
193 hir
::ModuleDef
::TypeAlias(it
) => Some(it
.display(db
).to_string()),
194 hir
::ModuleDef
::Macro(it
) => Some(it
.display(db
).to_string()),
195 hir
::ModuleDef
::BuiltinType(_
) => None
,
202 impl TryToNav
for Definition
{
203 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
205 Definition
::Local(it
) => Some(it
.to_nav(db
)),
206 Definition
::Label(it
) => Some(it
.to_nav(db
)),
207 Definition
::Module(it
) => Some(it
.to_nav(db
)),
208 Definition
::Macro(it
) => it
.try_to_nav(db
),
209 Definition
::Field(it
) => it
.try_to_nav(db
),
210 Definition
::SelfType(it
) => it
.try_to_nav(db
),
211 Definition
::GenericParam(it
) => it
.try_to_nav(db
),
212 Definition
::Function(it
) => it
.try_to_nav(db
),
213 Definition
::Adt(it
) => it
.try_to_nav(db
),
214 Definition
::Variant(it
) => it
.try_to_nav(db
),
215 Definition
::Const(it
) => it
.try_to_nav(db
),
216 Definition
::Static(it
) => it
.try_to_nav(db
),
217 Definition
::Trait(it
) => it
.try_to_nav(db
),
218 Definition
::TraitAlias(it
) => it
.try_to_nav(db
),
219 Definition
::TypeAlias(it
) => it
.try_to_nav(db
),
220 Definition
::ExternCrateDecl(it
) => Some(it
.try_to_nav(db
)?
),
221 Definition
::BuiltinType(_
) => None
,
222 Definition
::ToolModule(_
) => None
,
223 Definition
::BuiltinAttr(_
) => None
,
224 // FIXME: The focus range should be set to the helper declaration
225 Definition
::DeriveHelper(it
) => it
.derive().try_to_nav(db
),
230 impl TryToNav
for hir
::ModuleDef
{
231 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
233 hir
::ModuleDef
::Module(it
) => Some(it
.to_nav(db
)),
234 hir
::ModuleDef
::Function(it
) => it
.try_to_nav(db
),
235 hir
::ModuleDef
::Adt(it
) => it
.try_to_nav(db
),
236 hir
::ModuleDef
::Variant(it
) => it
.try_to_nav(db
),
237 hir
::ModuleDef
::Const(it
) => it
.try_to_nav(db
),
238 hir
::ModuleDef
::Static(it
) => it
.try_to_nav(db
),
239 hir
::ModuleDef
::Trait(it
) => it
.try_to_nav(db
),
240 hir
::ModuleDef
::TraitAlias(it
) => it
.try_to_nav(db
),
241 hir
::ModuleDef
::TypeAlias(it
) => it
.try_to_nav(db
),
242 hir
::ModuleDef
::Macro(it
) => it
.try_to_nav(db
),
243 hir
::ModuleDef
::BuiltinType(_
) => None
,
248 pub(crate) trait ToNavFromAst
: Sized
{
249 const KIND
: SymbolKind
;
250 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
256 fn container_name(db
: &RootDatabase
, t
: impl HasContainer
) -> Option
<SmolStr
> {
257 match t
.container(db
) {
258 hir
::ItemContainer
::Trait(it
) => Some(it
.name(db
).to_smol_str()),
259 // FIXME: Handle owners of blocks correctly here
260 hir
::ItemContainer
::Module(it
) => it
.name(db
).map(|name
| name
.to_smol_str()),
265 impl ToNavFromAst
for hir
::Function
{
266 const KIND
: SymbolKind
= SymbolKind
::Function
;
267 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
268 container_name(db
, self)
272 impl ToNavFromAst
for hir
::Const
{
273 const KIND
: SymbolKind
= SymbolKind
::Const
;
274 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
275 container_name(db
, self)
278 impl ToNavFromAst
for hir
::Static
{
279 const KIND
: SymbolKind
= SymbolKind
::Static
;
280 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
281 container_name(db
, self)
284 impl ToNavFromAst
for hir
::Struct
{
285 const KIND
: SymbolKind
= SymbolKind
::Struct
;
286 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
287 container_name(db
, self)
290 impl ToNavFromAst
for hir
::Enum
{
291 const KIND
: SymbolKind
= SymbolKind
::Enum
;
292 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
293 container_name(db
, self)
296 impl ToNavFromAst
for hir
::Variant
{
297 const KIND
: SymbolKind
= SymbolKind
::Variant
;
299 impl ToNavFromAst
for hir
::Union
{
300 const KIND
: SymbolKind
= SymbolKind
::Union
;
301 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
302 container_name(db
, self)
305 impl ToNavFromAst
for hir
::TypeAlias
{
306 const KIND
: SymbolKind
= SymbolKind
::TypeAlias
;
307 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
308 container_name(db
, self)
311 impl ToNavFromAst
for hir
::Trait
{
312 const KIND
: SymbolKind
= SymbolKind
::Trait
;
313 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
314 container_name(db
, self)
317 impl ToNavFromAst
for hir
::TraitAlias
{
318 const KIND
: SymbolKind
= SymbolKind
::TraitAlias
;
319 fn container_name(self, db
: &RootDatabase
) -> Option
<SmolStr
> {
320 container_name(db
, self)
324 impl<D
> TryToNav
for D
326 D
: HasSource
+ ToNavFromAst
+ Copy
+ HasAttrs
+ HirDisplay
,
327 D
::Ast
: ast
::HasName
,
329 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
330 let src
= self.source(db
)?
;
331 let mut res
= NavigationTarget
::from_named(
333 src
.as_ref().map(|it
| it
as &dyn ast
::HasName
),
336 res
.docs
= self.docs(db
);
337 res
.description
= Some(self.display(db
).to_string());
338 res
.container_name
= self.container_name(db
);
343 impl ToNav
for hir
::Module
{
344 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
345 let InFile { file_id, value }
= self.definition_source(db
);
347 let name
= self.name(db
).map(|it
| it
.to_smol_str()).unwrap_or_default();
348 let (syntax
, focus
) = match &value
{
349 ModuleSource
::SourceFile(node
) => (node
.syntax(), None
),
350 ModuleSource
::Module(node
) => (node
.syntax(), node
.name()),
351 ModuleSource
::BlockExpr(node
) => (node
.syntax(), None
),
353 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
354 NavigationTarget
::from_syntax(file_id
, name
, focus_range
, full_range
, SymbolKind
::Module
)
358 impl TryToNav
for hir
::Impl
{
359 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
360 let InFile { file_id, value }
= self.source(db
)?
;
361 let derive_attr
= self.as_builtin_derive(db
);
363 let (focus
, syntax
) = match &derive_attr
{
364 Some(attr
) => (None
, attr
.value
.syntax()),
365 None
=> (value
.self_ty(), value
.syntax()),
368 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
369 Some(NavigationTarget
::from_syntax(
379 impl TryToNav
for hir
::ExternCrateDecl
{
380 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
381 let src
= self.source(db
)?
;
382 let InFile { file_id, value }
= src
;
385 .map_or_else(|| value
.name_ref().map(Either
::Left
), |it
| it
.name().map(Either
::Right
));
386 let (file_id
, full_range
, focus_range
) =
387 orig_range_with_focus(db
, file_id
, value
.syntax(), focus
);
388 let mut res
= NavigationTarget
::from_syntax(
390 self.alias_or_name(db
).unwrap_or_else(|| self.name(db
)).to_smol_str(),
396 res
.docs
= self.docs(db
);
397 res
.description
= Some(self.display(db
).to_string());
398 res
.container_name
= container_name(db
, *self);
403 impl TryToNav
for hir
::Field
{
404 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
405 let src
= self.source(db
)?
;
407 let field_source
= match &src
.value
{
408 FieldSource
::Named(it
) => {
410 NavigationTarget
::from_named(db
, src
.with_value(it
), SymbolKind
::Field
);
411 res
.docs
= self.docs(db
);
412 res
.description
= Some(self.display(db
).to_string());
415 FieldSource
::Pos(it
) => {
416 let FileRange { file_id, range }
=
417 src
.with_value(it
.syntax()).original_file_range(db
);
418 NavigationTarget
::from_syntax(file_id
, "".into(), None
, range
, SymbolKind
::Field
)
425 impl TryToNav
for hir
::Macro
{
426 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
427 let src
= self.source(db
)?
;
428 let name_owner
: &dyn ast
::HasName
= match &src
.value
{
429 Either
::Left(it
) => it
,
430 Either
::Right(it
) => it
,
432 let mut res
= NavigationTarget
::from_named(
434 src
.as_ref().with_value(name_owner
),
435 self.kind(db
).into(),
437 res
.docs
= self.docs(db
);
442 impl TryToNav
for hir
::Adt
{
443 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
445 hir
::Adt
::Struct(it
) => it
.try_to_nav(db
),
446 hir
::Adt
::Union(it
) => it
.try_to_nav(db
),
447 hir
::Adt
::Enum(it
) => it
.try_to_nav(db
),
452 impl TryToNav
for hir
::AssocItem
{
453 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
455 AssocItem
::Function(it
) => it
.try_to_nav(db
),
456 AssocItem
::Const(it
) => it
.try_to_nav(db
),
457 AssocItem
::TypeAlias(it
) => it
.try_to_nav(db
),
462 impl TryToNav
for hir
::GenericParam
{
463 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
465 hir
::GenericParam
::TypeParam(it
) => it
.try_to_nav(db
),
466 hir
::GenericParam
::ConstParam(it
) => it
.try_to_nav(db
),
467 hir
::GenericParam
::LifetimeParam(it
) => it
.try_to_nav(db
),
472 impl ToNav
for LocalSource
{
473 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
474 let InFile { file_id, value }
= &self.source
;
475 let file_id
= *file_id
;
476 let local
= self.local
;
477 let (node
, name
) = match &value
{
478 Either
::Left(bind_pat
) => (bind_pat
.syntax(), bind_pat
.name()),
479 Either
::Right(it
) => (it
.syntax(), it
.name()),
482 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, node
, name
);
484 let name
= local
.name(db
).to_smol_str();
485 let kind
= if local
.is_self(db
) {
486 SymbolKind
::SelfParam
487 } else if local
.is_param(db
) {
488 SymbolKind
::ValueParam
499 container_name
: None
,
506 impl ToNav
for hir
::Local
{
507 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
508 self.primary_source(db
).to_nav(db
)
512 impl ToNav
for hir
::Label
{
513 fn to_nav(&self, db
: &RootDatabase
) -> NavigationTarget
{
514 let InFile { file_id, value }
= self.source(db
);
515 let name
= self.name(db
).to_smol_str();
517 let (file_id
, full_range
, focus_range
) =
518 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.lifetime());
524 kind
: Some(SymbolKind
::Label
),
527 container_name
: None
,
534 impl TryToNav
for hir
::TypeParam
{
535 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
536 let InFile { file_id, value }
= self.merge().source(db
)?
;
537 let name
= self.name(db
).to_smol_str();
539 let value
= match value
{
540 Either
::Left(ast
::TypeOrConstParam
::Type(x
)) => Either
::Left(x
),
541 Either
::Left(ast
::TypeOrConstParam
::Const(_
)) => {
545 Either
::Right(x
) => Either
::Right(x
),
548 let syntax
= match &value
{
549 Either
::Left(type_param
) => type_param
.syntax(),
550 Either
::Right(trait_
) => trait_
.syntax(),
552 let focus
= value
.as_ref().either(|it
| it
.name(), |it
| it
.name());
554 let (file_id
, full_range
, focus_range
) = orig_range_with_focus(db
, file_id
, syntax
, focus
);
556 Some(NavigationTarget
{
560 kind
: Some(SymbolKind
::TypeParam
),
563 container_name
: None
,
570 impl TryToNav
for hir
::TypeOrConstParam
{
571 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
572 self.split(db
).try_to_nav(db
)
576 impl TryToNav
for hir
::LifetimeParam
{
577 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
578 let InFile { file_id, value }
= self.source(db
)?
;
579 let name
= self.name(db
).to_smol_str();
581 let FileRange { file_id, range }
=
582 InFile
::new(file_id
, value
.syntax()).original_file_range(db
);
583 Some(NavigationTarget
{
587 kind
: Some(SymbolKind
::LifetimeParam
),
589 focus_range
: Some(range
),
590 container_name
: None
,
597 impl TryToNav
for hir
::ConstParam
{
598 fn try_to_nav(&self, db
: &RootDatabase
) -> Option
<NavigationTarget
> {
599 let InFile { file_id, value }
= self.merge().source(db
)?
;
600 let name
= self.name(db
).to_smol_str();
602 let value
= match value
{
603 Either
::Left(ast
::TypeOrConstParam
::Const(x
)) => x
,
610 let (file_id
, full_range
, focus_range
) =
611 orig_range_with_focus(db
, file_id
, value
.syntax(), value
.name());
612 Some(NavigationTarget
{
616 kind
: Some(SymbolKind
::ConstParam
),
619 container_name
: None
,
626 fn orig_range_with_focus(
630 name
: Option
<impl AstNode
>,
631 ) -> (FileId
, TextRange
, Option
<TextRange
>) {
632 let FileRange { file_id, range: full_range }
=
633 InFile
::new(hir_file
, value
).original_file_range(db
);
634 let focus_range
= name
635 .and_then(|it
| InFile
::new(hir_file
, it
.syntax()).original_file_range_opt(db
))
636 .and_then(|range
| if range
.file_id
== file_id { Some(range.range) }
else { None }
);
638 (file_id
, full_range
, focus_range
)
643 use expect_test
::expect
;
645 use crate::{fixture, Query}
;
648 fn test_nav_for_symbol() {
649 let (analysis
, _
) = fixture
::file(
652 fn foo() { enum FooInner { } }
656 let navs
= analysis
.symbol_search(Query
::new("FooInner".to_string())).unwrap();
667 description: "enum FooInner",
677 container_name: "foo",
678 description: "enum FooInner",
682 .assert_debug_eq(&navs
);
686 fn test_world_symbols_are_case_sensitive() {
687 let (analysis
, _
) = fixture
::file(
694 let navs
= analysis
.symbol_search(Query
::new("foo".to_string())).unwrap();
695 assert_eq
!(navs
.len(), 2)