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