• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! See [`NavigationTarget`].
2 
3 use std::fmt;
4 
5 use either::Either;
6 use hir::{
7     symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasContainer, HasSource,
8     HirDisplay, HirFileId, InFile, LocalSource, ModuleSource,
9 };
10 use ide_db::{
11     base_db::{FileId, FileRange},
12     SymbolKind,
13 };
14 use ide_db::{defs::Definition, RootDatabase};
15 use stdx::never;
16 use syntax::{
17     ast::{self, HasName},
18     AstNode, SmolStr, SyntaxNode, TextRange,
19 };
20 
21 /// `NavigationTarget` represents an element in the editor's UI which you can
22 /// click on to navigate to a particular piece of code.
23 ///
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 {
28     pub file_id: FileId,
29     /// Range which encompasses the whole element.
30     ///
31     /// Should include body, doc comments, attributes, etc.
32     ///
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`.
37     ///
38     /// Typically, `full_range` is the whole syntax node, including doc
39     /// comments, and `focus_range` is the range of the identifier.
40     ///
41     /// Clients should place the cursor on this range when navigating to this target.
42     pub focus_range: Option<TextRange>,
43     pub name: SmolStr,
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>,
51 }
52 
53 impl fmt::Debug for NavigationTarget {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result54     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55         let mut f = f.debug_struct("NavigationTarget");
56         macro_rules! opt {
57             ($($name:ident)*) => {$(
58                 if let Some(it) = &self.$name {
59                     f.field(stringify!($name), it);
60                 }
61             )*}
62         }
63         f.field("file_id", &self.file_id).field("full_range", &self.full_range);
64         opt!(focus_range);
65         f.field("name", &self.name);
66         opt!(kind container_name description docs);
67         f.finish()
68     }
69 }
70 
71 pub(crate) trait ToNav {
to_nav(&self, db: &RootDatabase) -> NavigationTarget72     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
73 }
74 
75 pub(crate) trait TryToNav {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>76     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
77 }
78 
79 impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>80     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
81         match self {
82             Either::Left(it) => it.try_to_nav(db),
83             Either::Right(it) => it.try_to_nav(db),
84         }
85     }
86 }
87 
88 impl NavigationTarget {
focus_or_full_range(&self) -> TextRange89     pub fn focus_or_full_range(&self) -> TextRange {
90         self.focus_range.unwrap_or(self.full_range)
91     }
92 
from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget93     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(
99                 file_id,
100                 name,
101                 focus_range,
102                 full_range,
103                 SymbolKind::Module,
104             );
105             res.docs = module.attrs(db).docs();
106             res.description = Some(module.display(db).to_string());
107             return res;
108         }
109         module.to_nav(db)
110     }
111 
112     #[cfg(test)]
debug_render(&self) -> String113     pub(crate) fn debug_render(&self) -> String {
114         let mut buf = format!(
115             "{} {:?} {:?} {:?}",
116             self.name,
117             self.kind.unwrap(),
118             self.file_id,
119             self.full_range
120         );
121         if let Some(focus_range) = self.focus_range {
122             buf.push_str(&format!(" {focus_range:?}"))
123         }
124         if let Some(container_name) = &self.container_name {
125             buf.push_str(&format!(" {container_name}"))
126         }
127         buf
128     }
129 
130     /// Allows `NavigationTarget` to be created from a `NameOwner`
131     pub(crate) fn from_named(
132         db: &RootDatabase,
133         InFile { file_id, value }: InFile<&dyn ast::HasName>,
134         kind: SymbolKind,
135     ) -> NavigationTarget {
136         let name = value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into());
137 
138         let (file_id, full_range, focus_range) =
139             orig_range_with_focus(db, file_id, value.syntax(), value.name());
140 
141         NavigationTarget::from_syntax(file_id, name, focus_range, full_range, kind)
142     }
143 
from_syntax( file_id: FileId, name: SmolStr, focus_range: Option<TextRange>, full_range: TextRange, kind: SymbolKind, ) -> NavigationTarget144     fn from_syntax(
145         file_id: FileId,
146         name: SmolStr,
147         focus_range: Option<TextRange>,
148         full_range: TextRange,
149         kind: SymbolKind,
150     ) -> NavigationTarget {
151         NavigationTarget {
152             file_id,
153             name,
154             kind: Some(kind),
155             full_range,
156             focus_range,
157             container_name: None,
158             description: None,
159             docs: None,
160             alias: None,
161         }
162     }
163 }
164 
165 impl TryToNav for FileSymbol {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>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 {
170                 Some(it.range)
171             } else {
172                 None
173             }
174         });
175 
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,
182             focus_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,
196             },
197             docs: None,
198         })
199     }
200 }
201 
202 impl TryToNav for Definition {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>203     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
204         match self {
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::BuiltinType(_) => None,
221             Definition::ToolModule(_) => None,
222             Definition::BuiltinAttr(_) => None,
223             // FIXME: The focus range should be set to the helper declaration
224             Definition::DeriveHelper(it) => it.derive().try_to_nav(db),
225         }
226     }
227 }
228 
229 impl TryToNav for hir::ModuleDef {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>230     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
231         match self {
232             hir::ModuleDef::Module(it) => Some(it.to_nav(db)),
233             hir::ModuleDef::Function(it) => it.try_to_nav(db),
234             hir::ModuleDef::Adt(it) => it.try_to_nav(db),
235             hir::ModuleDef::Variant(it) => it.try_to_nav(db),
236             hir::ModuleDef::Const(it) => it.try_to_nav(db),
237             hir::ModuleDef::Static(it) => it.try_to_nav(db),
238             hir::ModuleDef::Trait(it) => it.try_to_nav(db),
239             hir::ModuleDef::TraitAlias(it) => it.try_to_nav(db),
240             hir::ModuleDef::TypeAlias(it) => it.try_to_nav(db),
241             hir::ModuleDef::Macro(it) => it.try_to_nav(db),
242             hir::ModuleDef::BuiltinType(_) => None,
243         }
244     }
245 }
246 
247 pub(crate) trait ToNavFromAst: Sized {
248     const KIND: SymbolKind;
container_name(self, db: &RootDatabase) -> Option<SmolStr>249     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
250         _ = db;
251         None
252     }
253 }
254 
container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr>255 fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr> {
256     match t.container(db) {
257         hir::ItemContainer::Trait(it) => Some(it.name(db).to_smol_str()),
258         // FIXME: Handle owners of blocks correctly here
259         hir::ItemContainer::Module(it) => it.name(db).map(|name| name.to_smol_str()),
260         _ => None,
261     }
262 }
263 
264 impl ToNavFromAst for hir::Function {
265     const KIND: SymbolKind = SymbolKind::Function;
container_name(self, db: &RootDatabase) -> Option<SmolStr>266     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
267         container_name(db, self)
268     }
269 }
270 
271 impl ToNavFromAst for hir::Const {
272     const KIND: SymbolKind = SymbolKind::Const;
container_name(self, db: &RootDatabase) -> Option<SmolStr>273     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
274         container_name(db, self)
275     }
276 }
277 impl ToNavFromAst for hir::Static {
278     const KIND: SymbolKind = SymbolKind::Static;
container_name(self, db: &RootDatabase) -> Option<SmolStr>279     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
280         container_name(db, self)
281     }
282 }
283 impl ToNavFromAst for hir::Struct {
284     const KIND: SymbolKind = SymbolKind::Struct;
container_name(self, db: &RootDatabase) -> Option<SmolStr>285     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
286         container_name(db, self)
287     }
288 }
289 impl ToNavFromAst for hir::Enum {
290     const KIND: SymbolKind = SymbolKind::Enum;
container_name(self, db: &RootDatabase) -> Option<SmolStr>291     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
292         container_name(db, self)
293     }
294 }
295 impl ToNavFromAst for hir::Variant {
296     const KIND: SymbolKind = SymbolKind::Variant;
297 }
298 impl ToNavFromAst for hir::Union {
299     const KIND: SymbolKind = SymbolKind::Union;
container_name(self, db: &RootDatabase) -> Option<SmolStr>300     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
301         container_name(db, self)
302     }
303 }
304 impl ToNavFromAst for hir::TypeAlias {
305     const KIND: SymbolKind = SymbolKind::TypeAlias;
container_name(self, db: &RootDatabase) -> Option<SmolStr>306     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
307         container_name(db, self)
308     }
309 }
310 impl ToNavFromAst for hir::Trait {
311     const KIND: SymbolKind = SymbolKind::Trait;
container_name(self, db: &RootDatabase) -> Option<SmolStr>312     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
313         container_name(db, self)
314     }
315 }
316 impl ToNavFromAst for hir::TraitAlias {
317     const KIND: SymbolKind = SymbolKind::TraitAlias;
container_name(self, db: &RootDatabase) -> Option<SmolStr>318     fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
319         container_name(db, self)
320     }
321 }
322 
323 impl<D> TryToNav for D
324 where
325     D: HasSource + ToNavFromAst + Copy + HasAttrs + HirDisplay,
326     D::Ast: ast::HasName,
327 {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>328     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
329         let src = self.source(db)?;
330         let mut res = NavigationTarget::from_named(
331             db,
332             src.as_ref().map(|it| it as &dyn ast::HasName),
333             D::KIND,
334         );
335         res.docs = self.docs(db);
336         res.description = Some(self.display(db).to_string());
337         res.container_name = self.container_name(db);
338         Some(res)
339     }
340 }
341 
342 impl ToNav for hir::Module {
to_nav(&self, db: &RootDatabase) -> NavigationTarget343     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
344         let InFile { file_id, value } = self.definition_source(db);
345 
346         let name = self.name(db).map(|it| it.to_smol_str()).unwrap_or_default();
347         let (syntax, focus) = match &value {
348             ModuleSource::SourceFile(node) => (node.syntax(), None),
349             ModuleSource::Module(node) => (node.syntax(), node.name()),
350             ModuleSource::BlockExpr(node) => (node.syntax(), None),
351         };
352         let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
353         NavigationTarget::from_syntax(file_id, name, focus_range, full_range, SymbolKind::Module)
354     }
355 }
356 
357 impl TryToNav for hir::Impl {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>358     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
359         let InFile { file_id, value } = self.source(db)?;
360         let derive_attr = self.is_builtin_derive(db);
361 
362         let focus = if derive_attr.is_some() { None } else { value.self_ty() };
363 
364         let syntax = match &derive_attr {
365             Some(attr) => attr.value.syntax(),
366             None => value.syntax(),
367         };
368 
369         let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
370         Some(NavigationTarget::from_syntax(
371             file_id,
372             "impl".into(),
373             focus_range,
374             full_range,
375             SymbolKind::Impl,
376         ))
377     }
378 }
379 
380 impl TryToNav for hir::Field {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>381     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
382         let src = self.source(db)?;
383 
384         let field_source = match &src.value {
385             FieldSource::Named(it) => {
386                 let mut res =
387                     NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field);
388                 res.docs = self.docs(db);
389                 res.description = Some(self.display(db).to_string());
390                 res
391             }
392             FieldSource::Pos(it) => {
393                 let FileRange { file_id, range } =
394                     src.with_value(it.syntax()).original_file_range(db);
395                 NavigationTarget::from_syntax(file_id, "".into(), None, range, SymbolKind::Field)
396             }
397         };
398         Some(field_source)
399     }
400 }
401 
402 impl TryToNav for hir::Macro {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>403     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
404         let src = self.source(db)?;
405         let name_owner: &dyn ast::HasName = match &src.value {
406             Either::Left(it) => it,
407             Either::Right(it) => it,
408         };
409         let mut res = NavigationTarget::from_named(
410             db,
411             src.as_ref().with_value(name_owner),
412             self.kind(db).into(),
413         );
414         res.docs = self.docs(db);
415         Some(res)
416     }
417 }
418 
419 impl TryToNav for hir::Adt {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>420     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
421         match self {
422             hir::Adt::Struct(it) => it.try_to_nav(db),
423             hir::Adt::Union(it) => it.try_to_nav(db),
424             hir::Adt::Enum(it) => it.try_to_nav(db),
425         }
426     }
427 }
428 
429 impl TryToNav for hir::AssocItem {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>430     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
431         match self {
432             AssocItem::Function(it) => it.try_to_nav(db),
433             AssocItem::Const(it) => it.try_to_nav(db),
434             AssocItem::TypeAlias(it) => it.try_to_nav(db),
435         }
436     }
437 }
438 
439 impl TryToNav for hir::GenericParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>440     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
441         match self {
442             hir::GenericParam::TypeParam(it) => it.try_to_nav(db),
443             hir::GenericParam::ConstParam(it) => it.try_to_nav(db),
444             hir::GenericParam::LifetimeParam(it) => it.try_to_nav(db),
445         }
446     }
447 }
448 
449 impl ToNav for LocalSource {
to_nav(&self, db: &RootDatabase) -> NavigationTarget450     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
451         let InFile { file_id, value } = &self.source;
452         let file_id = *file_id;
453         let local = self.local;
454         let (node, name) = match &value {
455             Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
456             Either::Right(it) => (it.syntax(), it.name()),
457         };
458 
459         let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, node, name);
460 
461         let name = local.name(db).to_smol_str();
462         let kind = if local.is_self(db) {
463             SymbolKind::SelfParam
464         } else if local.is_param(db) {
465             SymbolKind::ValueParam
466         } else {
467             SymbolKind::Local
468         };
469         NavigationTarget {
470             file_id,
471             name,
472             alias: None,
473             kind: Some(kind),
474             full_range,
475             focus_range,
476             container_name: None,
477             description: None,
478             docs: None,
479         }
480     }
481 }
482 
483 impl ToNav for hir::Local {
to_nav(&self, db: &RootDatabase) -> NavigationTarget484     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
485         self.primary_source(db).to_nav(db)
486     }
487 }
488 
489 impl ToNav for hir::Label {
to_nav(&self, db: &RootDatabase) -> NavigationTarget490     fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
491         let InFile { file_id, value } = self.source(db);
492         let name = self.name(db).to_smol_str();
493 
494         let (file_id, full_range, focus_range) =
495             orig_range_with_focus(db, file_id, value.syntax(), value.lifetime());
496 
497         NavigationTarget {
498             file_id,
499             name,
500             alias: None,
501             kind: Some(SymbolKind::Label),
502             full_range,
503             focus_range,
504             container_name: None,
505             description: None,
506             docs: None,
507         }
508     }
509 }
510 
511 impl TryToNav for hir::TypeParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>512     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
513         let InFile { file_id, value } = self.merge().source(db)?;
514         let name = self.name(db).to_smol_str();
515 
516         let value = match value {
517             Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
518             Either::Left(ast::TypeOrConstParam::Const(_)) => {
519                 never!();
520                 return None;
521             }
522             Either::Right(x) => Either::Right(x),
523         };
524 
525         let syntax = match &value {
526             Either::Left(type_param) => type_param.syntax(),
527             Either::Right(trait_) => trait_.syntax(),
528         };
529         let focus = value.as_ref().either(|it| it.name(), |it| it.name());
530 
531         let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
532 
533         Some(NavigationTarget {
534             file_id,
535             name,
536             alias: None,
537             kind: Some(SymbolKind::TypeParam),
538             full_range,
539             focus_range,
540             container_name: None,
541             description: None,
542             docs: None,
543         })
544     }
545 }
546 
547 impl TryToNav for hir::TypeOrConstParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>548     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
549         self.split(db).try_to_nav(db)
550     }
551 }
552 
553 impl TryToNav for hir::LifetimeParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>554     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
555         let InFile { file_id, value } = self.source(db)?;
556         let name = self.name(db).to_smol_str();
557 
558         let FileRange { file_id, range } =
559             InFile::new(file_id, value.syntax()).original_file_range(db);
560         Some(NavigationTarget {
561             file_id,
562             name,
563             alias: None,
564             kind: Some(SymbolKind::LifetimeParam),
565             full_range: range,
566             focus_range: Some(range),
567             container_name: None,
568             description: None,
569             docs: None,
570         })
571     }
572 }
573 
574 impl TryToNav for hir::ConstParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>575     fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
576         let InFile { file_id, value } = self.merge().source(db)?;
577         let name = self.name(db).to_smol_str();
578 
579         let value = match value {
580             Either::Left(ast::TypeOrConstParam::Const(x)) => x,
581             _ => {
582                 never!();
583                 return None;
584             }
585         };
586 
587         let (file_id, full_range, focus_range) =
588             orig_range_with_focus(db, file_id, value.syntax(), value.name());
589         Some(NavigationTarget {
590             file_id,
591             name,
592             alias: None,
593             kind: Some(SymbolKind::ConstParam),
594             full_range,
595             focus_range,
596             container_name: None,
597             description: None,
598             docs: None,
599         })
600     }
601 }
602 
orig_range_with_focus( db: &RootDatabase, hir_file: HirFileId, value: &SyntaxNode, name: Option<impl AstNode>, ) -> (FileId, TextRange, Option<TextRange>)603 fn orig_range_with_focus(
604     db: &RootDatabase,
605     hir_file: HirFileId,
606     value: &SyntaxNode,
607     name: Option<impl AstNode>,
608 ) -> (FileId, TextRange, Option<TextRange>) {
609     let FileRange { file_id, range: full_range } =
610         InFile::new(hir_file, value).original_file_range(db);
611     let focus_range = name
612         .and_then(|it| InFile::new(hir_file, it.syntax()).original_file_range_opt(db))
613         .and_then(|range| if range.file_id == file_id { Some(range.range) } else { None });
614 
615     (file_id, full_range, focus_range)
616 }
617 
618 #[cfg(test)]
619 mod tests {
620     use expect_test::expect;
621 
622     use crate::{fixture, Query};
623 
624     #[test]
test_nav_for_symbol()625     fn test_nav_for_symbol() {
626         let (analysis, _) = fixture::file(
627             r#"
628 enum FooInner { }
629 fn foo() { enum FooInner { } }
630 "#,
631         );
632 
633         let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap();
634         expect![[r#"
635             [
636                 NavigationTarget {
637                     file_id: FileId(
638                         0,
639                     ),
640                     full_range: 0..17,
641                     focus_range: 5..13,
642                     name: "FooInner",
643                     kind: Enum,
644                     description: "enum FooInner",
645                 },
646                 NavigationTarget {
647                     file_id: FileId(
648                         0,
649                     ),
650                     full_range: 29..46,
651                     focus_range: 34..42,
652                     name: "FooInner",
653                     kind: Enum,
654                     container_name: "foo",
655                     description: "enum FooInner",
656                 },
657             ]
658         "#]]
659         .assert_debug_eq(&navs);
660     }
661 
662     #[test]
test_world_symbols_are_case_sensitive()663     fn test_world_symbols_are_case_sensitive() {
664         let (analysis, _) = fixture::file(
665             r#"
666 fn foo() {}
667 struct Foo;
668 "#,
669         );
670 
671         let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
672         assert_eq!(navs.len(), 2)
673     }
674 }
675