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<'static>>,
56 pub alias: Option<Symbol>,
59}
60
61impl fmt::Debug for NavigationTarget {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let mut f = f.debug_struct("NavigationTarget");
64 macro_rules! opt {
65 ($($name:ident)*) => {$(
66 if let Some(it) = &self.$name {
67 f.field(stringify!($name), it);
68 }
69 )*}
70 }
71 f.field("file_id", &self.file_id).field("full_range", &self.full_range);
72 opt!(focus_range);
73 f.field("name", &self.name);
74 opt!(kind container_name description docs);
75 f.finish()
76 }
77}
78
79impl UpmapFromRaFixture for NavigationTarget {
80 fn upmap_from_ra_fixture(
81 self,
82 analysis: &ide_db::ra_fixture::RaFixtureAnalysis,
83 _virtual_file_id: FileId,
84 real_file_id: FileId,
85 ) -> Result<Self, ()> {
86 let virtual_file_id = self.file_id;
87 Ok(NavigationTarget {
88 file_id: real_file_id,
89 full_range: self.full_range.upmap_from_ra_fixture(
90 analysis,
91 virtual_file_id,
92 real_file_id,
93 )?,
94 focus_range: self.focus_range.upmap_from_ra_fixture(
95 analysis,
96 virtual_file_id,
97 real_file_id,
98 )?,
99 name: self.name.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
100 kind: self.kind.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
101 container_name: self.container_name.upmap_from_ra_fixture(
102 analysis,
103 virtual_file_id,
104 real_file_id,
105 )?,
106 description: self.description.upmap_from_ra_fixture(
107 analysis,
108 virtual_file_id,
109 real_file_id,
110 )?,
111 docs: self.docs.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
112 alias: self.alias.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
113 })
114 }
115}
116
117pub(crate) trait ToNav {
118 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget>;
119}
120
121pub trait TryToNav {
122 fn try_to_nav(
123 &self,
124 sema: &Semantics<'_, RootDatabase>,
125 ) -> Option<UpmappingResult<NavigationTarget>>;
126}
127
128impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
129 fn try_to_nav(
130 &self,
131 sema: &Semantics<'_, RootDatabase>,
132 ) -> Option<UpmappingResult<NavigationTarget>> {
133 match self {
134 Either::Left(it) => it.try_to_nav(sema),
135 Either::Right(it) => it.try_to_nav(sema),
136 }
137 }
138}
139
140impl NavigationTarget {
141 pub fn focus_or_full_range(&self) -> TextRange {
142 self.focus_range.unwrap_or(self.full_range)
143 }
144
145 pub(crate) fn from_module_to_decl(
146 db: &RootDatabase,
147 module: hir::Module,
148 ) -> UpmappingResult<NavigationTarget> {
149 let name = module.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
150 match module.declaration_source(db) {
151 Some(InFile { value, file_id }) => {
152 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
153 |(FileRange { file_id, range: full_range }, focus_range)| {
154 let mut res = NavigationTarget::from_syntax(
155 file_id,
156 name.clone(),
157 focus_range,
158 full_range,
159 SymbolKind::Module,
160 );
161 res.docs = module.docs(db).map(Documentation::into_owned);
162 res.description = Some(
163 module.display(db, module.krate(db).to_display_target(db)).to_string(),
164 );
165 res
166 },
167 )
168 }
169 _ => module.to_nav(db),
170 }
171 }
172
173 #[cfg(test)]
174 pub(crate) fn debug_render(&self) -> String {
175 let mut buf = format!(
176 "{} {:?} {:?} {:?}",
177 self.name,
178 self.kind.unwrap(),
179 self.file_id,
180 self.full_range
181 );
182 if let Some(focus_range) = self.focus_range {
183 buf.push_str(&format!(" {focus_range:?}"))
184 }
185 if let Some(container_name) = &self.container_name {
186 buf.push_str(&format!(" {container_name}"))
187 }
188 buf
189 }
190
191 pub(crate) fn from_named(
193 db: &RootDatabase,
194 InFile { file_id, value }: InFile<&dyn ast::HasName>,
195 kind: SymbolKind,
196 ) -> UpmappingResult<NavigationTarget> {
197 let name =
198 value.name().map(|it| Symbol::intern(&it.text())).unwrap_or_else(|| sym::underscore);
199
200 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
201 |(FileRange { file_id, range: full_range }, focus_range)| {
202 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
203 },
204 )
205 }
206
207 pub(crate) fn from_syntax(
208 file_id: FileId,
209 name: Symbol,
210 focus_range: Option<TextRange>,
211 full_range: TextRange,
212 kind: SymbolKind,
213 ) -> NavigationTarget {
214 NavigationTarget {
215 file_id,
216 name,
217 kind: Some(kind),
218 full_range,
219 focus_range,
220 container_name: None,
221 description: None,
222 docs: None,
223 alias: None,
224 }
225 }
226}
227
228impl<'db> TryToNav for FileSymbol<'db> {
229 fn try_to_nav(
230 &self,
231 sema: &Semantics<'_, RootDatabase>,
232 ) -> Option<UpmappingResult<NavigationTarget>> {
233 let db = sema.db;
234 let display_target = self.def.krate(db).to_display_target(db);
235 Some(
236 orig_range_with_focus_r(
237 db,
238 self.loc.hir_file_id,
239 self.loc.ptr.text_range(),
240 Some(self.loc.name_ptr.text_range()),
241 )
242 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
243 NavigationTarget {
244 file_id,
245 name: self
246 .is_alias
247 .then(|| self.def.name(db))
248 .flatten()
249 .map_or_else(|| self.name.clone(), |it| it.symbol().clone()),
250 alias: self.is_alias.then(|| self.name.clone()),
251 kind: Some(self.def.into()),
252 full_range,
253 focus_range,
254 container_name: self.container_name.clone(),
255 description: match self.def {
256 hir::ModuleDef::Module(it) => {
257 Some(it.display(db, display_target).to_string())
258 }
259 hir::ModuleDef::Function(it) => {
260 Some(it.display(db, display_target).to_string())
261 }
262 hir::ModuleDef::Adt(it) => Some(it.display(db, display_target).to_string()),
263 hir::ModuleDef::Variant(it) => {
264 Some(it.display(db, display_target).to_string())
265 }
266 hir::ModuleDef::Const(it) => {
267 Some(it.display(db, display_target).to_string())
268 }
269 hir::ModuleDef::Static(it) => {
270 Some(it.display(db, display_target).to_string())
271 }
272 hir::ModuleDef::Trait(it) => {
273 Some(it.display(db, display_target).to_string())
274 }
275 hir::ModuleDef::TypeAlias(it) => {
276 Some(it.display(db, display_target).to_string())
277 }
278 hir::ModuleDef::Macro(it) => {
279 Some(it.display(db, display_target).to_string())
280 }
281 hir::ModuleDef::BuiltinType(_) => None,
282 },
283 docs: None,
284 }
285 }),
286 )
287 }
288}
289
290impl TryToNav for Definition {
291 fn try_to_nav(
292 &self,
293 sema: &Semantics<'_, RootDatabase>,
294 ) -> Option<UpmappingResult<NavigationTarget>> {
295 match self {
296 Definition::Local(it) => Some(it.to_nav(sema.db)),
297 Definition::Label(it) => it.try_to_nav(sema),
298 Definition::Module(it) => Some(it.to_nav(sema.db)),
299 Definition::Crate(it) => Some(it.to_nav(sema.db)),
300 Definition::Macro(it) => it.try_to_nav(sema),
301 Definition::Field(it) => it.try_to_nav(sema),
302 Definition::SelfType(it) => it.try_to_nav(sema),
303 Definition::GenericParam(it) => it.try_to_nav(sema),
304 Definition::Function(it) => it.try_to_nav(sema),
305 Definition::Adt(it) => it.try_to_nav(sema),
306 Definition::Variant(it) => it.try_to_nav(sema),
307 Definition::Const(it) => it.try_to_nav(sema),
308 Definition::Static(it) => it.try_to_nav(sema),
309 Definition::Trait(it) => it.try_to_nav(sema),
310 Definition::TypeAlias(it) => it.try_to_nav(sema),
311 Definition::ExternCrateDecl(it) => it.try_to_nav(sema),
312 Definition::InlineAsmOperand(it) => it.try_to_nav(sema),
313 Definition::BuiltinType(it) => it.try_to_nav(sema),
314 Definition::BuiltinLifetime(_)
315 | Definition::TupleField(_)
316 | Definition::ToolModule(_)
317 | Definition::InlineAsmRegOrRegClass(_)
318 | Definition::BuiltinAttr(_) => None,
319 Definition::DeriveHelper(it) => it.derive().try_to_nav(sema),
321 }
322 }
323}
324
325impl TryToNav for hir::ModuleDef {
326 fn try_to_nav(
327 &self,
328 sema: &Semantics<'_, RootDatabase>,
329 ) -> Option<UpmappingResult<NavigationTarget>> {
330 match self {
331 hir::ModuleDef::Module(it) => Some(it.to_nav(sema.db)),
332 hir::ModuleDef::Function(it) => it.try_to_nav(sema),
333 hir::ModuleDef::Adt(it) => it.try_to_nav(sema),
334 hir::ModuleDef::Variant(it) => it.try_to_nav(sema),
335 hir::ModuleDef::Const(it) => it.try_to_nav(sema),
336 hir::ModuleDef::Static(it) => it.try_to_nav(sema),
337 hir::ModuleDef::Trait(it) => it.try_to_nav(sema),
338 hir::ModuleDef::TypeAlias(it) => it.try_to_nav(sema),
339 hir::ModuleDef::Macro(it) => it.try_to_nav(sema),
340 hir::ModuleDef::BuiltinType(_) => None,
341 }
342 }
343}
344
345pub(crate) trait ToNavFromAst: Sized {
346 const KIND: SymbolKind;
347 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
348 _ = db;
349 None
350 }
351}
352
353fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<Symbol> {
354 match t.container(db) {
355 hir::ItemContainer::Trait(it) => Some(it.name(db).symbol().clone()),
356 hir::ItemContainer::Module(it) => it.name(db).map(|name| name.symbol().clone()),
358 _ => None,
359 }
360}
361
362impl ToNavFromAst for hir::Function {
363 const KIND: SymbolKind = SymbolKind::Function;
364 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
365 container_name(db, self)
366 }
367}
368
369impl ToNavFromAst for hir::Const {
370 const KIND: SymbolKind = SymbolKind::Const;
371 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
372 container_name(db, self)
373 }
374}
375impl ToNavFromAst for hir::Static {
376 const KIND: SymbolKind = SymbolKind::Static;
377 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
378 container_name(db, self)
379 }
380}
381impl ToNavFromAst for hir::Struct {
382 const KIND: SymbolKind = SymbolKind::Struct;
383 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
384 container_name(db, self)
385 }
386}
387impl ToNavFromAst for hir::Enum {
388 const KIND: SymbolKind = SymbolKind::Enum;
389 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
390 container_name(db, self)
391 }
392}
393impl ToNavFromAst for hir::Variant {
394 const KIND: SymbolKind = SymbolKind::Variant;
395}
396impl ToNavFromAst for hir::Union {
397 const KIND: SymbolKind = SymbolKind::Union;
398 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
399 container_name(db, self)
400 }
401}
402impl ToNavFromAst for hir::TypeAlias {
403 const KIND: SymbolKind = SymbolKind::TypeAlias;
404 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
405 container_name(db, self)
406 }
407}
408impl ToNavFromAst for hir::Trait {
409 const KIND: SymbolKind = SymbolKind::Trait;
410 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
411 container_name(db, self)
412 }
413}
414
415impl<D> TryToNav for D
416where
417 D: HasSource + ToNavFromAst + Copy + HasDocs + for<'db> HirDisplay<'db> + HasCrate,
418 D::Ast: ast::HasName,
419{
420 fn try_to_nav(
421 &self,
422 sema: &Semantics<'_, RootDatabase>,
423 ) -> Option<UpmappingResult<NavigationTarget>> {
424 let db = sema.db;
425 let src = self.source(db)?;
426 Some(
427 NavigationTarget::from_named(
428 db,
429 src.as_ref().map(|it| it as &dyn ast::HasName),
430 D::KIND,
431 )
432 .map(|mut res| {
433 res.docs = self.docs(db).map(Documentation::into_owned);
434 res.description = hir::attach_db(db, || {
435 Some(self.display(db, self.krate(db).to_display_target(db)).to_string())
436 });
437 res.container_name = self.container_name(db);
438 res
439 }),
440 )
441 }
442}
443
444impl ToNav for hir::Module {
445 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
446 let InFile { file_id, value } = self.definition_source(db);
447
448 let name = self.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
449 let (syntax, focus) = match &value {
450 ModuleSource::SourceFile(node) => (node.syntax(), None),
451 ModuleSource::Module(node) => (node.syntax(), node.name()),
452 ModuleSource::BlockExpr(node) => (node.syntax(), None),
453 };
454
455 orig_range_with_focus(db, file_id, syntax, focus).map(
456 |(FileRange { file_id, range: full_range }, focus_range)| {
457 NavigationTarget::from_syntax(
458 file_id,
459 name.clone(),
460 focus_range,
461 full_range,
462 SymbolKind::Module,
463 )
464 },
465 )
466 }
467}
468
469impl ToNav for hir::Crate {
470 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
471 self.root_module(db).to_nav(db)
472 }
473}
474
475impl TryToNav for hir::Impl {
476 fn try_to_nav(
477 &self,
478 sema: &Semantics<'_, RootDatabase>,
479 ) -> Option<UpmappingResult<NavigationTarget>> {
480 let db = sema.db;
481 let InFile { file_id, value } = self.source(db)?;
482 let derive_path = self.as_builtin_derive_path(db);
483
484 let (file_id, focus, syntax) = match &derive_path {
485 Some(attr) => (attr.file_id.into(), None, attr.value.syntax()),
486 None => (file_id, value.self_ty(), value.syntax()),
487 };
488
489 Some(orig_range_with_focus(db, file_id, syntax, focus).map(
490 |(FileRange { file_id, range: full_range }, focus_range)| {
491 NavigationTarget::from_syntax(
492 file_id,
493 sym::kw_impl,
494 focus_range,
495 full_range,
496 SymbolKind::Impl,
497 )
498 },
499 ))
500 }
501}
502
503impl TryToNav for hir::ExternCrateDecl {
504 fn try_to_nav(
505 &self,
506 sema: &Semantics<'_, RootDatabase>,
507 ) -> Option<UpmappingResult<NavigationTarget>> {
508 let db = sema.db;
509 let src = self.source(db)?;
510 let InFile { file_id, value } = src;
511 let focus = value
512 .rename()
513 .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
514 let krate = self.module(db).krate(db);
515
516 Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map(
517 |(FileRange { file_id, range: full_range }, focus_range)| {
518 let mut res = NavigationTarget::from_syntax(
519 file_id,
520 self.alias_or_name(db).unwrap_or_else(|| self.name(db)).symbol().clone(),
521 focus_range,
522 full_range,
523 SymbolKind::Module,
524 );
525
526 res.docs = self.docs(db).map(Documentation::into_owned);
527 res.description = Some(self.display(db, krate.to_display_target(db)).to_string());
528 res.container_name = container_name(db, *self);
529 res
530 },
531 ))
532 }
533}
534
535impl TryToNav for hir::Field {
536 fn try_to_nav(
537 &self,
538 sema: &Semantics<'_, RootDatabase>,
539 ) -> Option<UpmappingResult<NavigationTarget>> {
540 let db = sema.db;
541 let src = self.source(db)?;
542 let krate = self.parent_def(db).module(db).krate(db);
543
544 let field_source = match &src.value {
545 FieldSource::Named(it) => {
546 NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map(
547 |mut res| {
548 res.docs = self.docs(db).map(Documentation::into_owned);
549 res.description =
550 Some(self.display(db, krate.to_display_target(db)).to_string());
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).map(Documentation::into_owned);
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 = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap());
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 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}