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