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