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