1use std::fmt;
4
5use arrayvec::ArrayVec;
6use either::Either;
7use hir::{
8 AssocItem, Crate, FieldSource, HasContainer, HasCrate, HasSource, HirDisplay, HirFileId,
9 InFile, LocalSource, ModuleSource, Name, Semantics, Symbol, sym, symbols::FileSymbol,
10};
11use ide_db::{
12 FileId, FileRange, RootDatabase, SymbolKind,
13 base_db::{CrateOrigin, LangCrateOrigin, all_crates},
14 defs::{Definition, find_std_module},
15 documentation::{Documentation, HasDocs},
16 famous_defs::FamousDefs,
17 ra_fixture::UpmapFromRaFixture,
18};
19use stdx::never;
20use syntax::{
21 AstNode, AstPtr, SyntaxNode, TextRange,
22 ast::{self, HasName},
23};
24
25#[derive(Clone, PartialEq, Eq, Hash)]
31pub struct NavigationTarget {
32 pub file_id: FileId,
33 pub full_range: TextRange,
40 pub focus_range: Option<TextRange>,
49 pub name: Symbol,
50 pub kind: Option<SymbolKind>,
51 pub container_name: Option<Symbol>,
52 pub description: Option<String>,
53 pub docs: Option<Documentation<'static>>,
54 pub alias: Option<Symbol>,
57}
58
59impl fmt::Debug for NavigationTarget {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 let mut f = f.debug_struct("NavigationTarget");
62 macro_rules! opt {
63 ($($name:ident)*) => {$(
64 if let Some(it) = &self.$name {
65 f.field(stringify!($name), it);
66 }
67 )*}
68 }
69 f.field("file_id", &self.file_id).field("full_range", &self.full_range);
70 opt!(focus_range);
71 f.field("name", &self.name);
72 opt!(kind container_name description docs);
73 f.finish()
74 }
75}
76
77impl UpmapFromRaFixture for NavigationTarget {
78 fn upmap_from_ra_fixture(
79 self,
80 analysis: &ide_db::ra_fixture::RaFixtureAnalysis,
81 _virtual_file_id: FileId,
82 real_file_id: FileId,
83 ) -> Result<Self, ()> {
84 let virtual_file_id = self.file_id;
85 Ok(NavigationTarget {
86 file_id: real_file_id,
87 full_range: self.full_range.upmap_from_ra_fixture(
88 analysis,
89 virtual_file_id,
90 real_file_id,
91 )?,
92 focus_range: self.focus_range.upmap_from_ra_fixture(
93 analysis,
94 virtual_file_id,
95 real_file_id,
96 )?,
97 name: self.name.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
98 kind: self.kind.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
99 container_name: self.container_name.upmap_from_ra_fixture(
100 analysis,
101 virtual_file_id,
102 real_file_id,
103 )?,
104 description: self.description.upmap_from_ra_fixture(
105 analysis,
106 virtual_file_id,
107 real_file_id,
108 )?,
109 docs: self.docs.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
110 alias: self.alias.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
111 })
112 }
113}
114
115pub(crate) trait ToNav {
116 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget>;
117}
118
119pub trait TryToNav {
120 fn try_to_nav(
121 &self,
122 sema: &Semantics<'_, RootDatabase>,
123 ) -> Option<UpmappingResult<NavigationTarget>>;
124}
125
126impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
127 fn try_to_nav(
128 &self,
129 sema: &Semantics<'_, RootDatabase>,
130 ) -> Option<UpmappingResult<NavigationTarget>> {
131 match self {
132 Either::Left(it) => it.try_to_nav(sema),
133 Either::Right(it) => it.try_to_nav(sema),
134 }
135 }
136}
137
138impl NavigationTarget {
139 pub fn focus_or_full_range(&self) -> TextRange {
140 self.focus_range.unwrap_or(self.full_range)
141 }
142
143 pub(crate) fn from_module_to_decl(
144 db: &RootDatabase,
145 module: hir::Module,
146 ) -> UpmappingResult<NavigationTarget> {
147 let name = module.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
148 match module.declaration_source(db) {
149 Some(InFile { value, file_id }) => {
150 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
151 |(FileRange { file_id, range: full_range }, focus_range)| {
152 let mut res = NavigationTarget::from_syntax(
153 file_id,
154 name.clone(),
155 focus_range,
156 full_range,
157 SymbolKind::Module,
158 );
159 res.docs = module.docs(db).map(Documentation::into_owned);
160 res.description = Some(
161 module.display(db, module.krate(db).to_display_target(db)).to_string(),
162 );
163 res
164 },
165 )
166 }
167 _ => module.to_nav(db),
168 }
169 }
170
171 #[cfg(test)]
172 pub(crate) fn debug_render(&self) -> String {
173 let mut buf = format!(
174 "{} {:?} {:?} {:?}",
175 self.name,
176 self.kind.unwrap(),
177 self.file_id,
178 self.full_range
179 );
180 if let Some(focus_range) = self.focus_range {
181 buf.push_str(&format!(" {focus_range:?}"))
182 }
183 if let Some(container_name) = &self.container_name {
184 buf.push_str(&format!(" {container_name}"))
185 }
186 buf
187 }
188
189 pub(crate) fn from_named(
191 db: &RootDatabase,
192 InFile { file_id, value }: InFile<&dyn ast::HasName>,
193 kind: SymbolKind,
194 ) -> UpmappingResult<NavigationTarget> {
195 let name =
196 value.name().map(|it| Symbol::intern(&it.text())).unwrap_or_else(|| sym::underscore);
197
198 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
199 |(FileRange { file_id, range: full_range }, focus_range)| {
200 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
201 },
202 )
203 }
204
205 pub(crate) fn from_named_with_range(
206 db: &RootDatabase,
207 ranges: InFile<(TextRange, Option<TextRange>)>,
208 name: Option<Name>,
209 kind: SymbolKind,
210 ) -> UpmappingResult<NavigationTarget> {
211 let InFile { file_id, value: (full_range, focus_range) } = ranges;
212 let name = name.map(|name| name.symbol().clone()).unwrap_or_else(|| sym::underscore);
213
214 orig_range_with_focus_r(db, file_id, full_range, focus_range).map(
215 |(FileRange { file_id, range: full_range }, focus_range)| {
216 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
217 },
218 )
219 }
220
221 pub(crate) fn from_syntax(
222 file_id: FileId,
223 name: Symbol,
224 focus_range: Option<TextRange>,
225 full_range: TextRange,
226 kind: SymbolKind,
227 ) -> NavigationTarget {
228 NavigationTarget {
229 file_id,
230 name,
231 kind: Some(kind),
232 full_range,
233 focus_range,
234 container_name: None,
235 description: None,
236 docs: None,
237 alias: None,
238 }
239 }
240}
241
242impl<'db> TryToNav for FileSymbol<'db> {
243 fn try_to_nav(
244 &self,
245 sema: &Semantics<'_, RootDatabase>,
246 ) -> Option<UpmappingResult<NavigationTarget>> {
247 let db = sema.db;
248 let display_target = self.def.krate(db).to_display_target(db);
249 Some(
250 orig_range_with_focus_r(
251 db,
252 self.loc.hir_file_id,
253 self.loc.ptr.text_range(),
254 self.loc.name_ptr.map(AstPtr::text_range),
255 )
256 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
257 NavigationTarget {
258 file_id,
259 name: self
260 .is_alias
261 .then(|| self.def.name(db))
262 .flatten()
263 .map_or_else(|| self.name.clone(), |it| it.symbol().clone()),
264 alias: self.is_alias.then(|| self.name.clone()),
265 kind: Some(SymbolKind::from_module_def(db, self.def)),
266 full_range,
267 focus_range,
268 container_name: self.container_name.clone(),
269 description: match self.def {
270 hir::ModuleDef::Module(it) => {
271 Some(it.display(db, display_target).to_string())
272 }
273 hir::ModuleDef::Function(it) => {
274 Some(it.display(db, display_target).to_string())
275 }
276 hir::ModuleDef::Adt(it) => Some(it.display(db, display_target).to_string()),
277 hir::ModuleDef::EnumVariant(it) => {
278 Some(it.display(db, display_target).to_string())
279 }
280 hir::ModuleDef::Const(it) => {
281 Some(it.display(db, display_target).to_string())
282 }
283 hir::ModuleDef::Static(it) => {
284 Some(it.display(db, display_target).to_string())
285 }
286 hir::ModuleDef::Trait(it) => {
287 Some(it.display(db, display_target).to_string())
288 }
289 hir::ModuleDef::TypeAlias(it) => {
290 Some(it.display(db, display_target).to_string())
291 }
292 hir::ModuleDef::Macro(it) => {
293 Some(it.display(db, display_target).to_string())
294 }
295 hir::ModuleDef::BuiltinType(_) => None,
296 },
297 docs: None,
298 }
299 }),
300 )
301 }
302}
303
304impl TryToNav for Definition {
305 fn try_to_nav(
306 &self,
307 sema: &Semantics<'_, RootDatabase>,
308 ) -> Option<UpmappingResult<NavigationTarget>> {
309 match self {
310 Definition::Local(it) => Some(it.to_nav(sema.db)),
311 Definition::Label(it) => it.try_to_nav(sema),
312 Definition::Module(it) => Some(it.to_nav(sema.db)),
313 Definition::Crate(it) => Some(it.to_nav(sema.db)),
314 Definition::Macro(it) => it.try_to_nav(sema),
315 Definition::Field(it) => it.try_to_nav(sema),
316 Definition::SelfType(it) => it.try_to_nav(sema),
317 Definition::GenericParam(it) => it.try_to_nav(sema),
318 Definition::Function(it) => it.try_to_nav(sema),
319 Definition::Adt(it) => it.try_to_nav(sema),
320 Definition::EnumVariant(it) => it.try_to_nav(sema),
321 Definition::Const(it) => it.try_to_nav(sema),
322 Definition::Static(it) => it.try_to_nav(sema),
323 Definition::Trait(it) => it.try_to_nav(sema),
324 Definition::TypeAlias(it) => it.try_to_nav(sema),
325 Definition::ExternCrateDecl(it) => it.try_to_nav(sema),
326 Definition::InlineAsmOperand(it) => it.try_to_nav(sema),
327 Definition::BuiltinType(it) => it.try_to_nav(sema),
328 Definition::BuiltinLifetime(_)
329 | Definition::TupleField(_)
330 | Definition::ToolModule(_)
331 | Definition::InlineAsmRegOrRegClass(_)
332 | Definition::BuiltinAttr(_) => None,
333 Definition::DeriveHelper(it) => it.derive().try_to_nav(sema),
335 }
336 }
337}
338
339impl TryToNav for hir::ModuleDef {
340 fn try_to_nav(
341 &self,
342 sema: &Semantics<'_, RootDatabase>,
343 ) -> Option<UpmappingResult<NavigationTarget>> {
344 match self {
345 hir::ModuleDef::Module(it) => Some(it.to_nav(sema.db)),
346 hir::ModuleDef::Function(it) => it.try_to_nav(sema),
347 hir::ModuleDef::Adt(it) => it.try_to_nav(sema),
348 hir::ModuleDef::EnumVariant(it) => it.try_to_nav(sema),
349 hir::ModuleDef::Const(it) => it.try_to_nav(sema),
350 hir::ModuleDef::Static(it) => it.try_to_nav(sema),
351 hir::ModuleDef::Trait(it) => it.try_to_nav(sema),
352 hir::ModuleDef::TypeAlias(it) => it.try_to_nav(sema),
353 hir::ModuleDef::Macro(it) => it.try_to_nav(sema),
354 hir::ModuleDef::BuiltinType(_) => None,
355 }
356 }
357}
358
359pub(crate) trait ToNavFromAst: Sized {
360 const KIND: SymbolKind;
361 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
362 _ = db;
363 None
364 }
365}
366
367fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<Symbol> {
368 match t.container(db) {
369 hir::ItemContainer::Trait(it) => Some(it.name(db).symbol().clone()),
370 hir::ItemContainer::Module(it) => it.name(db).map(|name| name.symbol().clone()),
372 _ => None,
373 }
374}
375
376impl ToNavFromAst for hir::Function {
377 const KIND: SymbolKind = SymbolKind::Function;
378 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
379 container_name(db, self)
380 }
381}
382
383impl ToNavFromAst for hir::Const {
384 const KIND: SymbolKind = SymbolKind::Const;
385 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
386 container_name(db, self)
387 }
388}
389impl ToNavFromAst for hir::Static {
390 const KIND: SymbolKind = SymbolKind::Static;
391 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
392 container_name(db, self)
393 }
394}
395impl ToNavFromAst for hir::Struct {
396 const KIND: SymbolKind = SymbolKind::Struct;
397 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
398 container_name(db, self)
399 }
400}
401impl ToNavFromAst for hir::Enum {
402 const KIND: SymbolKind = SymbolKind::Enum;
403 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
404 container_name(db, self)
405 }
406}
407impl ToNavFromAst for hir::EnumVariant {
408 const KIND: SymbolKind = SymbolKind::Variant;
409}
410impl ToNavFromAst for hir::Union {
411 const KIND: SymbolKind = SymbolKind::Union;
412 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
413 container_name(db, self)
414 }
415}
416impl ToNavFromAst for hir::TypeAlias {
417 const KIND: SymbolKind = SymbolKind::TypeAlias;
418 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
419 container_name(db, self)
420 }
421}
422impl ToNavFromAst for hir::Trait {
423 const KIND: SymbolKind = SymbolKind::Trait;
424 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
425 container_name(db, self)
426 }
427}
428
429impl<D> TryToNav for D
430where
431 D: HasSource
432 + ToNavFromAst
433 + Copy
434 + HasDocs
435 + for<'db> HirDisplay<'db>
436 + HasCrate
437 + hir::HasName,
438 D::Ast: ast::HasName,
439{
440 fn try_to_nav(
441 &self,
442 sema: &Semantics<'_, RootDatabase>,
443 ) -> Option<UpmappingResult<NavigationTarget>> {
444 let db = sema.db;
445 let src = self.source_with_range(db)?;
446 Some(
447 NavigationTarget::from_named_with_range(
448 db,
449 src.map(|(full_range, node)| {
450 (
451 full_range,
452 node.and_then(|node| {
453 Some(ast::HasName::name(&node)?.syntax().text_range())
454 }),
455 )
456 }),
457 self.name(db),
458 D::KIND,
459 )
460 .map(|mut res| {
461 res.docs = self.docs(db).map(Documentation::into_owned);
462 res.description =
463 Some(self.display(db, self.krate(db).to_display_target(db)).to_string());
464 res.container_name = self.container_name(db);
465 res
466 }),
467 )
468 }
469}
470
471impl ToNav for hir::Module {
472 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
473 let InFile { file_id, value } = self.definition_source(db);
474
475 let name = self.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
476 let (syntax, focus) = match &value {
477 ModuleSource::SourceFile(node) => (node.syntax(), None),
478 ModuleSource::Module(node) => (node.syntax(), node.name()),
479 ModuleSource::BlockExpr(node) => (node.syntax(), None),
480 };
481 let kind = if self.is_crate_root(db) { SymbolKind::CrateRoot } else { SymbolKind::Module };
482
483 orig_range_with_focus(db, file_id, syntax, focus).map(
484 |(FileRange { file_id, range: full_range }, focus_range)| {
485 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
486 },
487 )
488 }
489}
490
491impl ToNav for hir::Crate {
492 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
493 self.root_module(db).to_nav(db)
494 }
495}
496
497impl TryToNav for hir::Impl {
498 fn try_to_nav(
499 &self,
500 sema: &Semantics<'_, RootDatabase>,
501 ) -> Option<UpmappingResult<NavigationTarget>> {
502 let db = sema.db;
503 let InFile { file_id, value: (full_range, source) } = self.source_with_range(db)?;
504
505 Some(
506 orig_range_with_focus_r(
507 db,
508 file_id,
509 full_range,
510 source.and_then(|source| Some(source.self_ty()?.syntax().text_range())),
511 )
512 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
513 NavigationTarget::from_syntax(
514 file_id,
515 sym::kw_impl,
516 focus_range,
517 full_range,
518 SymbolKind::Impl,
519 )
520 }),
521 )
522 }
523}
524
525impl TryToNav for hir::ExternCrateDecl {
526 fn try_to_nav(
527 &self,
528 sema: &Semantics<'_, RootDatabase>,
529 ) -> Option<UpmappingResult<NavigationTarget>> {
530 let db = sema.db;
531 let src = self.source(db)?;
532 let InFile { file_id, value } = src;
533 let focus = value
534 .rename()
535 .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
536 let krate = self.module(db).krate(db);
537
538 Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map(
539 |(FileRange { file_id, range: full_range }, focus_range)| {
540 let mut res = NavigationTarget::from_syntax(
541 file_id,
542 self.alias_or_name(db).unwrap_or_else(|| self.name(db)).symbol().clone(),
543 focus_range,
544 full_range,
545 SymbolKind::CrateRoot,
546 );
547
548 res.docs = self.docs(db).map(Documentation::into_owned);
549 res.description = Some(self.display(db, krate.to_display_target(db)).to_string());
550 res.container_name = container_name(db, *self);
551 res
552 },
553 ))
554 }
555}
556
557impl TryToNav for hir::Field {
558 fn try_to_nav(
559 &self,
560 sema: &Semantics<'_, RootDatabase>,
561 ) -> Option<UpmappingResult<NavigationTarget>> {
562 let db = sema.db;
563 let src = self.source(db)?;
564 let krate = self.parent_def(db).module(db).krate(db);
565
566 let field_source = match &src.value {
567 FieldSource::Named(it) => {
568 NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map(
569 |mut res| {
570 res.docs = self.docs(db).map(Documentation::into_owned);
571 res.description =
572 Some(self.display(db, krate.to_display_target(db)).to_string());
573 res
574 },
575 )
576 }
577 FieldSource::Pos(it) => orig_range(db, src.file_id, it.syntax()).map(
578 |(FileRange { file_id, range: full_range }, focus_range)| {
579 NavigationTarget::from_syntax(
580 file_id,
581 sym::Integer::get(self.index()),
582 focus_range,
583 full_range,
584 SymbolKind::Field,
585 )
586 },
587 ),
588 };
589 Some(field_source)
590 }
591}
592
593impl TryToNav for hir::Macro {
594 fn try_to_nav(
595 &self,
596 sema: &Semantics<'_, RootDatabase>,
597 ) -> Option<UpmappingResult<NavigationTarget>> {
598 let db = sema.db;
599 let src = self.source(db)?;
600 let name_owner: &dyn ast::HasName = match &src.value {
601 Either::Left(it) => it,
602 Either::Right(it) => it,
603 };
604 Some(
605 NavigationTarget::from_named(
606 db,
607 src.as_ref().with_value(name_owner),
608 self.kind(db).into(),
609 )
610 .map(|mut res| {
611 res.docs = self.docs(db).map(Documentation::into_owned);
612 res
613 }),
614 )
615 }
616}
617
618impl TryToNav for hir::Adt {
619 fn try_to_nav(
620 &self,
621 sema: &Semantics<'_, RootDatabase>,
622 ) -> Option<UpmappingResult<NavigationTarget>> {
623 match self {
624 hir::Adt::Struct(it) => it.try_to_nav(sema),
625 hir::Adt::Union(it) => it.try_to_nav(sema),
626 hir::Adt::Enum(it) => it.try_to_nav(sema),
627 }
628 }
629}
630
631impl TryToNav for hir::AssocItem {
632 fn try_to_nav(
633 &self,
634 sema: &Semantics<'_, RootDatabase>,
635 ) -> Option<UpmappingResult<NavigationTarget>> {
636 match self {
637 AssocItem::Function(it) => it.try_to_nav(sema),
638 AssocItem::Const(it) => it.try_to_nav(sema),
639 AssocItem::TypeAlias(it) => it.try_to_nav(sema),
640 }
641 }
642}
643
644impl TryToNav for hir::GenericParam {
645 fn try_to_nav(
646 &self,
647 sema: &Semantics<'_, RootDatabase>,
648 ) -> Option<UpmappingResult<NavigationTarget>> {
649 match self {
650 hir::GenericParam::TypeParam(it) => it.try_to_nav(sema),
651 hir::GenericParam::ConstParam(it) => it.try_to_nav(sema),
652 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(sema),
653 }
654 }
655}
656
657impl ToNav for LocalSource {
658 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
659 let InFile { file_id, value } = &self.source;
660 let file_id = *file_id;
661 let local = self.local;
662 let (node, name) = match &value {
663 Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
664 Either::Right(it) => (it.syntax(), it.name()),
665 };
666
667 orig_range_with_focus(db, file_id, node, name).map(
668 |(FileRange { file_id, range: full_range }, focus_range)| {
669 let name = local.name(db).symbol().clone();
670 let kind = if local.is_self(db) {
671 SymbolKind::SelfParam
672 } else if local.is_param(db) {
673 SymbolKind::ValueParam
674 } else {
675 SymbolKind::Local
676 };
677 NavigationTarget {
678 file_id,
679 name,
680 alias: None,
681 kind: Some(kind),
682 full_range,
683 focus_range,
684 container_name: None,
685 description: None,
686 docs: None,
687 }
688 },
689 )
690 }
691}
692
693impl ToNav for hir::Local {
694 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
695 self.primary_source(db).to_nav(db)
696 }
697}
698
699impl TryToNav for hir::Label {
700 fn try_to_nav(
701 &self,
702 sema: &Semantics<'_, RootDatabase>,
703 ) -> Option<UpmappingResult<NavigationTarget>> {
704 let db = sema.db;
705 let InFile { file_id, value } = self.source(db)?;
706 let name = self.name(db).symbol().clone();
707
708 Some(orig_range_with_focus(db, file_id, value.syntax(), value.lifetime()).map(
709 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
710 file_id,
711 name: name.clone(),
712 alias: None,
713 kind: Some(SymbolKind::Label),
714 full_range,
715 focus_range,
716 container_name: None,
717 description: None,
718 docs: None,
719 },
720 ))
721 }
722}
723
724impl TryToNav for hir::TypeParam {
725 fn try_to_nav(
726 &self,
727 sema: &Semantics<'_, RootDatabase>,
728 ) -> Option<UpmappingResult<NavigationTarget>> {
729 let db = sema.db;
730 let InFile { file_id, value } = self.merge().source(db)?;
731 let name = self.name(db).symbol().clone();
732
733 let value = match value {
734 Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
735 Either::Left(ast::TypeOrConstParam::Const(_)) => {
736 never!();
737 return None;
738 }
739 Either::Right(x) => Either::Right(x),
740 };
741
742 let syntax = match &value {
743 Either::Left(type_param) => type_param.syntax(),
744 Either::Right(trait_) => trait_.syntax(),
745 };
746 let focus = value.as_ref().either(|it| it.name(), |it| it.name());
747
748 Some(orig_range_with_focus(db, file_id, syntax, focus).map(
749 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
750 file_id,
751 name: name.clone(),
752 alias: None,
753 kind: Some(SymbolKind::TypeParam),
754 full_range,
755 focus_range,
756 container_name: None,
757 description: None,
758 docs: None,
759 },
760 ))
761 }
762}
763
764impl TryToNav for hir::TypeOrConstParam {
765 fn try_to_nav(
766 &self,
767 sema: &Semantics<'_, RootDatabase>,
768 ) -> Option<UpmappingResult<NavigationTarget>> {
769 self.split(sema.db).try_to_nav(sema)
770 }
771}
772
773impl TryToNav for hir::LifetimeParam {
774 fn try_to_nav(
775 &self,
776 sema: &Semantics<'_, RootDatabase>,
777 ) -> Option<UpmappingResult<NavigationTarget>> {
778 let db = sema.db;
779 let InFile { file_id, value } = self.source(db)?;
780 let name = self.name(db).symbol().clone();
781
782 Some(orig_range(db, file_id, value.syntax()).map(
783 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
784 file_id,
785 name: name.clone(),
786 alias: None,
787 kind: Some(SymbolKind::LifetimeParam),
788 full_range,
789 focus_range,
790 container_name: None,
791 description: None,
792 docs: None,
793 },
794 ))
795 }
796}
797
798impl TryToNav for hir::ConstParam {
799 fn try_to_nav(
800 &self,
801 sema: &Semantics<'_, RootDatabase>,
802 ) -> Option<UpmappingResult<NavigationTarget>> {
803 let db = sema.db;
804 let InFile { file_id, value } = self.merge().source(db)?;
805 let name = self.name(db).symbol().clone();
806
807 let value = match value {
808 Either::Left(ast::TypeOrConstParam::Const(x)) => x,
809 _ => {
810 never!();
811 return None;
812 }
813 };
814
815 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
816 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
817 file_id,
818 name: name.clone(),
819 alias: None,
820 kind: Some(SymbolKind::ConstParam),
821 full_range,
822 focus_range,
823 container_name: None,
824 description: None,
825 docs: None,
826 },
827 ))
828 }
829}
830
831impl TryToNav for hir::InlineAsmOperand {
832 fn try_to_nav(
833 &self,
834 sema: &Semantics<'_, RootDatabase>,
835 ) -> Option<UpmappingResult<NavigationTarget>> {
836 let db = sema.db;
837 let InFile { file_id, value } = &self.source(db)?;
838 let file_id = *file_id;
839 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
840 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
841 file_id,
842 name:
843 self.name(db).map_or_else(|| sym::underscore.clone(), |it| it.symbol().clone()),
844 alias: None,
845 kind: Some(SymbolKind::Local),
846 full_range,
847 focus_range,
848 container_name: None,
849 description: None,
850 docs: None,
851 },
852 ))
853 }
854}
855
856impl TryToNav for hir::BuiltinType {
857 fn try_to_nav(
858 &self,
859 sema: &Semantics<'_, RootDatabase>,
860 ) -> Option<UpmappingResult<NavigationTarget>> {
861 let db = sema.db;
862 let krate = all_crates(db)
863 .iter()
864 .copied()
865 .find(|&krate| matches!(krate.data(db).origin, CrateOrigin::Lang(LangCrateOrigin::Std)))
866 .map(Crate::from)?;
867 let edition = krate.edition(db);
868
869 let fd = FamousDefs(sema, krate);
870 let primitive_mod = format!("prim_{}", self.name().display(fd.0.db, edition));
871 let doc_owner = find_std_module(&fd, &primitive_mod, edition)?;
872
873 Some(doc_owner.to_nav(db))
874 }
875}
876
877#[derive(Debug)]
878pub struct UpmappingResult<T> {
879 pub call_site: T,
881 pub def_site: Option<T>,
883}
884
885impl<T> UpmappingResult<T> {
886 pub fn call_site(self) -> T {
887 self.call_site
888 }
889
890 pub fn collect<FI: FromIterator<T>>(self) -> FI {
891 FI::from_iter(self)
892 }
893}
894
895impl<T> IntoIterator for UpmappingResult<T> {
896 type Item = T;
897
898 type IntoIter = <ArrayVec<T, 2> as IntoIterator>::IntoIter;
899
900 fn into_iter(self) -> Self::IntoIter {
901 self.def_site
902 .into_iter()
903 .chain(Some(self.call_site))
904 .collect::<ArrayVec<_, 2>>()
905 .into_iter()
906 }
907}
908
909impl<T> UpmappingResult<T> {
910 pub(crate) fn map<U>(self, f: impl Fn(T) -> U) -> UpmappingResult<U> {
911 UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) }
912 }
913}
914
915fn orig_range_with_focus(
919 db: &RootDatabase,
920 hir_file: HirFileId,
921 value: &SyntaxNode,
922 name: Option<impl AstNode>,
923) -> UpmappingResult<(FileRange, Option<TextRange>)> {
924 orig_range_with_focus_r(
925 db,
926 hir_file,
927 value.text_range(),
928 name.map(|it| it.syntax().text_range()),
929 )
930}
931
932pub(crate) fn orig_range_with_focus_r(
933 db: &RootDatabase,
934 hir_file: HirFileId,
935 value: TextRange,
936 focus_range: Option<TextRange>,
937) -> UpmappingResult<(FileRange, Option<TextRange>)> {
938 let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) };
939
940 let call = || hir_file.macro_file().unwrap().loc(db);
941
942 let def_range = || hir_file.macro_file().unwrap().loc(db).def.definition_range(db);
943
944 let value_range = InFile::new(hir_file, value).original_node_file_range_opt(db);
946 let ((call_site_range, call_site_focus), def_site) =
947 match InFile::new(hir_file, name).original_node_file_range_opt(db) {
948 Some((focus_range, ctxt)) if ctxt.is_root() => {
950 (
952 (
953 match value_range {
954 Some((range, ctxt))
956 if ctxt.is_root()
957 && range.file_id == focus_range.file_id
958 && range.range.contains_range(focus_range.range) =>
959 {
960 range
961 }
962 _ => {
965 let call = call();
966 let kind = &call.kind;
967 let range = kind.clone().original_call_range_with_input(db);
968 if range.file_id == focus_range.file_id
974 && range.range.contains_range(focus_range.range)
975 {
976 range
977 } else {
978 kind.original_call_range(db, call.krate)
979 }
980 }
981 },
982 Some(focus_range),
983 ),
984 None,
986 )
987 }
988
989 Some((focus_range, _ctxt)) => {
992 match value_range {
993 Some((range, ctxt)) if ctxt.is_root() => (
995 (range, None),
997 {
1000 let (def_site, _) = def_range().original_node_file_range(db);
1001 (def_site.file_id == focus_range.file_id
1002 && def_site.range.contains_range(focus_range.range))
1003 .then_some((def_site, Some(focus_range)))
1004 },
1005 ),
1006 _ => {
1008 let call = call();
1009 (
1010 (call.kind.original_call_range(db, call.krate), None),
1012 Some((focus_range, Some(focus_range))),
1013 )
1014 }
1015 }
1016 }
1017 None => return orig_range_r(db, hir_file, value),
1019 };
1020
1021 UpmappingResult {
1022 call_site: (
1023 call_site_range.into_file_id(db),
1024 call_site_focus.and_then(|hir::FileRange { file_id, range }| {
1025 if call_site_range.file_id == file_id && call_site_range.range.contains_range(range)
1026 {
1027 Some(range)
1028 } else {
1029 None
1030 }
1031 }),
1032 ),
1033 def_site: def_site.map(|(def_site_range, def_site_focus)| {
1034 (
1035 def_site_range.into_file_id(db),
1036 def_site_focus.and_then(|hir::FileRange { file_id, range }| {
1037 if def_site_range.file_id == file_id
1038 && def_site_range.range.contains_range(range)
1039 {
1040 Some(range)
1041 } else {
1042 None
1043 }
1044 }),
1045 )
1046 }),
1047 }
1048}
1049
1050fn orig_range(
1051 db: &RootDatabase,
1052 hir_file: HirFileId,
1053 value: &SyntaxNode,
1054) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1055 UpmappingResult {
1056 call_site: (
1057 InFile::new(hir_file, value).original_file_range_rooted(db).into_file_id(db),
1058 None,
1059 ),
1060 def_site: None,
1061 }
1062}
1063
1064fn orig_range_r(
1065 db: &RootDatabase,
1066 hir_file: HirFileId,
1067 value: TextRange,
1068) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1069 UpmappingResult {
1070 call_site: (
1071 InFile::new(hir_file, value).original_node_file_range(db).0.into_file_id(db),
1072 None,
1073 ),
1074 def_site: None,
1075 }
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 use expect_test::expect;
1081
1082 use crate::{Query, fixture};
1083
1084 #[test]
1085 fn test_nav_for_symbol() {
1086 let (analysis, _) = fixture::file(
1087 r#"
1088enum FooInner { }
1089fn foo() { enum FooInner { } }
1090"#,
1091 );
1092
1093 let navs = analysis.symbol_search(Query::new("FooInner".to_owned()), !0).unwrap();
1094 expect![[r#"
1095 [
1096 NavigationTarget {
1097 file_id: FileId(
1098 0,
1099 ),
1100 full_range: 0..17,
1101 focus_range: 5..13,
1102 name: "FooInner",
1103 kind: Enum,
1104 description: "enum FooInner",
1105 },
1106 NavigationTarget {
1107 file_id: FileId(
1108 0,
1109 ),
1110 full_range: 29..46,
1111 focus_range: 34..42,
1112 name: "FooInner",
1113 kind: Enum,
1114 container_name: "foo",
1115 description: "enum FooInner",
1116 },
1117 ]
1118 "#]]
1119 .assert_debug_eq(&navs);
1120 }
1121
1122 #[test]
1123 fn test_world_symbols_are_case_sensitive() {
1124 let (analysis, _) = fixture::file(
1125 r#"
1126fn foo() {}
1127struct Foo;
1128"#,
1129 );
1130
1131 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1132 assert_eq!(navs.len(), 2)
1133 }
1134
1135 #[test]
1136 fn test_ensure_hidden_symbols_are_not_returned() {
1137 let (analysis, _) = fixture::file(
1138 r#"
1139fn foo() {}
1140struct Foo;
1141static __FOO_CALLSITE: () = ();
1142"#,
1143 );
1144
1145 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1147 assert_eq!(navs.len(), 2);
1148 let navs = analysis.symbol_search(Query::new("_foo".to_owned()), !0).unwrap();
1149 assert_eq!(navs.len(), 0);
1150
1151 let query = Query::new("__foo".to_owned());
1153 let navs = analysis.symbol_search(query, !0).unwrap();
1154 assert_eq!(navs.len(), 1);
1155 }
1156}