1 //! See [`NavigationTarget`].
2
3 use std::fmt;
4
5 use either::Either;
6 use hir::{
7 symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasContainer, HasSource,
8 HirDisplay, HirFileId, InFile, LocalSource, ModuleSource,
9 };
10 use ide_db::{
11 base_db::{FileId, FileRange},
12 SymbolKind,
13 };
14 use ide_db::{defs::Definition, RootDatabase};
15 use stdx::never;
16 use syntax::{
17 ast::{self, HasName},
18 AstNode, SmolStr, SyntaxNode, TextRange,
19 };
20
21 /// `NavigationTarget` represents an element in the editor's UI which you can
22 /// click on to navigate to a particular piece of code.
23 ///
24 /// Typically, a `NavigationTarget` corresponds to some element in the source
25 /// code, like a function or a struct, but this is not strictly required.
26 #[derive(Clone, PartialEq, Eq, Hash)]
27 pub struct NavigationTarget {
28 pub file_id: FileId,
29 /// Range which encompasses the whole element.
30 ///
31 /// Should include body, doc comments, attributes, etc.
32 ///
33 /// Clients should use this range to answer "is the cursor inside the
34 /// element?" question.
35 pub full_range: TextRange,
36 /// A "most interesting" range within the `full_range`.
37 ///
38 /// Typically, `full_range` is the whole syntax node, including doc
39 /// comments, and `focus_range` is the range of the identifier.
40 ///
41 /// Clients should place the cursor on this range when navigating to this target.
42 pub focus_range: Option<TextRange>,
43 pub name: SmolStr,
44 pub kind: Option<SymbolKind>,
45 pub container_name: Option<SmolStr>,
46 pub description: Option<String>,
47 pub docs: Option<Documentation>,
48 /// In addition to a `name` field, a `NavigationTarget` may also be aliased
49 /// In such cases we want a `NavigationTarget` to be accessible by its alias
50 pub alias: Option<SmolStr>,
51 }
52
53 impl fmt::Debug for NavigationTarget {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 let mut f = f.debug_struct("NavigationTarget");
56 macro_rules! opt {
57 ($($name:ident)*) => {$(
58 if let Some(it) = &self.$name {
59 f.field(stringify!($name), it);
60 }
61 )*}
62 }
63 f.field("file_id", &self.file_id).field("full_range", &self.full_range);
64 opt!(focus_range);
65 f.field("name", &self.name);
66 opt!(kind container_name description docs);
67 f.finish()
68 }
69 }
70
71 pub(crate) trait ToNav {
to_nav(&self, db: &RootDatabase) -> NavigationTarget72 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
73 }
74
75 pub(crate) trait TryToNav {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>76 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
77 }
78
79 impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>80 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
81 match self {
82 Either::Left(it) => it.try_to_nav(db),
83 Either::Right(it) => it.try_to_nav(db),
84 }
85 }
86 }
87
88 impl NavigationTarget {
focus_or_full_range(&self) -> TextRange89 pub fn focus_or_full_range(&self) -> TextRange {
90 self.focus_range.unwrap_or(self.full_range)
91 }
92
from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget93 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
94 let name = module.name(db).map(|it| it.to_smol_str()).unwrap_or_default();
95 if let Some(InFile { value, file_id }) = &module.declaration_source(db) {
96 let (file_id, full_range, focus_range) =
97 orig_range_with_focus(db, *file_id, value.syntax(), value.name());
98 let mut res = NavigationTarget::from_syntax(
99 file_id,
100 name,
101 focus_range,
102 full_range,
103 SymbolKind::Module,
104 );
105 res.docs = module.attrs(db).docs();
106 res.description = Some(module.display(db).to_string());
107 return res;
108 }
109 module.to_nav(db)
110 }
111
112 #[cfg(test)]
debug_render(&self) -> String113 pub(crate) fn debug_render(&self) -> String {
114 let mut buf = format!(
115 "{} {:?} {:?} {:?}",
116 self.name,
117 self.kind.unwrap(),
118 self.file_id,
119 self.full_range
120 );
121 if let Some(focus_range) = self.focus_range {
122 buf.push_str(&format!(" {focus_range:?}"))
123 }
124 if let Some(container_name) = &self.container_name {
125 buf.push_str(&format!(" {container_name}"))
126 }
127 buf
128 }
129
130 /// Allows `NavigationTarget` to be created from a `NameOwner`
131 pub(crate) fn from_named(
132 db: &RootDatabase,
133 InFile { file_id, value }: InFile<&dyn ast::HasName>,
134 kind: SymbolKind,
135 ) -> NavigationTarget {
136 let name = value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into());
137
138 let (file_id, full_range, focus_range) =
139 orig_range_with_focus(db, file_id, value.syntax(), value.name());
140
141 NavigationTarget::from_syntax(file_id, name, focus_range, full_range, kind)
142 }
143
from_syntax( file_id: FileId, name: SmolStr, focus_range: Option<TextRange>, full_range: TextRange, kind: SymbolKind, ) -> NavigationTarget144 fn from_syntax(
145 file_id: FileId,
146 name: SmolStr,
147 focus_range: Option<TextRange>,
148 full_range: TextRange,
149 kind: SymbolKind,
150 ) -> NavigationTarget {
151 NavigationTarget {
152 file_id,
153 name,
154 kind: Some(kind),
155 full_range,
156 focus_range,
157 container_name: None,
158 description: None,
159 docs: None,
160 alias: None,
161 }
162 }
163 }
164
165 impl TryToNav for FileSymbol {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>166 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
167 let full_range = self.loc.original_range(db);
168 let focus_range = self.loc.original_name_range(db).and_then(|it| {
169 if it.file_id == full_range.file_id {
170 Some(it.range)
171 } else {
172 None
173 }
174 });
175
176 Some(NavigationTarget {
177 file_id: full_range.file_id,
178 name: if self.is_alias { self.def.name(db)?.to_smol_str() } else { self.name.clone() },
179 alias: if self.is_alias { Some(self.name.clone()) } else { None },
180 kind: Some(hir::ModuleDefId::from(self.def).into()),
181 full_range: full_range.range,
182 focus_range,
183 container_name: self.container_name.clone(),
184 description: match self.def {
185 hir::ModuleDef::Module(it) => Some(it.display(db).to_string()),
186 hir::ModuleDef::Function(it) => Some(it.display(db).to_string()),
187 hir::ModuleDef::Adt(it) => Some(it.display(db).to_string()),
188 hir::ModuleDef::Variant(it) => Some(it.display(db).to_string()),
189 hir::ModuleDef::Const(it) => Some(it.display(db).to_string()),
190 hir::ModuleDef::Static(it) => Some(it.display(db).to_string()),
191 hir::ModuleDef::Trait(it) => Some(it.display(db).to_string()),
192 hir::ModuleDef::TraitAlias(it) => Some(it.display(db).to_string()),
193 hir::ModuleDef::TypeAlias(it) => Some(it.display(db).to_string()),
194 hir::ModuleDef::Macro(it) => Some(it.display(db).to_string()),
195 hir::ModuleDef::BuiltinType(_) => None,
196 },
197 docs: None,
198 })
199 }
200 }
201
202 impl TryToNav for Definition {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>203 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
204 match self {
205 Definition::Local(it) => Some(it.to_nav(db)),
206 Definition::Label(it) => Some(it.to_nav(db)),
207 Definition::Module(it) => Some(it.to_nav(db)),
208 Definition::Macro(it) => it.try_to_nav(db),
209 Definition::Field(it) => it.try_to_nav(db),
210 Definition::SelfType(it) => it.try_to_nav(db),
211 Definition::GenericParam(it) => it.try_to_nav(db),
212 Definition::Function(it) => it.try_to_nav(db),
213 Definition::Adt(it) => it.try_to_nav(db),
214 Definition::Variant(it) => it.try_to_nav(db),
215 Definition::Const(it) => it.try_to_nav(db),
216 Definition::Static(it) => it.try_to_nav(db),
217 Definition::Trait(it) => it.try_to_nav(db),
218 Definition::TraitAlias(it) => it.try_to_nav(db),
219 Definition::TypeAlias(it) => it.try_to_nav(db),
220 Definition::BuiltinType(_) => None,
221 Definition::ToolModule(_) => None,
222 Definition::BuiltinAttr(_) => None,
223 // FIXME: The focus range should be set to the helper declaration
224 Definition::DeriveHelper(it) => it.derive().try_to_nav(db),
225 }
226 }
227 }
228
229 impl TryToNav for hir::ModuleDef {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>230 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
231 match self {
232 hir::ModuleDef::Module(it) => Some(it.to_nav(db)),
233 hir::ModuleDef::Function(it) => it.try_to_nav(db),
234 hir::ModuleDef::Adt(it) => it.try_to_nav(db),
235 hir::ModuleDef::Variant(it) => it.try_to_nav(db),
236 hir::ModuleDef::Const(it) => it.try_to_nav(db),
237 hir::ModuleDef::Static(it) => it.try_to_nav(db),
238 hir::ModuleDef::Trait(it) => it.try_to_nav(db),
239 hir::ModuleDef::TraitAlias(it) => it.try_to_nav(db),
240 hir::ModuleDef::TypeAlias(it) => it.try_to_nav(db),
241 hir::ModuleDef::Macro(it) => it.try_to_nav(db),
242 hir::ModuleDef::BuiltinType(_) => None,
243 }
244 }
245 }
246
247 pub(crate) trait ToNavFromAst: Sized {
248 const KIND: SymbolKind;
container_name(self, db: &RootDatabase) -> Option<SmolStr>249 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
250 _ = db;
251 None
252 }
253 }
254
container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr>255 fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr> {
256 match t.container(db) {
257 hir::ItemContainer::Trait(it) => Some(it.name(db).to_smol_str()),
258 // FIXME: Handle owners of blocks correctly here
259 hir::ItemContainer::Module(it) => it.name(db).map(|name| name.to_smol_str()),
260 _ => None,
261 }
262 }
263
264 impl ToNavFromAst for hir::Function {
265 const KIND: SymbolKind = SymbolKind::Function;
container_name(self, db: &RootDatabase) -> Option<SmolStr>266 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
267 container_name(db, self)
268 }
269 }
270
271 impl ToNavFromAst for hir::Const {
272 const KIND: SymbolKind = SymbolKind::Const;
container_name(self, db: &RootDatabase) -> Option<SmolStr>273 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
274 container_name(db, self)
275 }
276 }
277 impl ToNavFromAst for hir::Static {
278 const KIND: SymbolKind = SymbolKind::Static;
container_name(self, db: &RootDatabase) -> Option<SmolStr>279 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
280 container_name(db, self)
281 }
282 }
283 impl ToNavFromAst for hir::Struct {
284 const KIND: SymbolKind = SymbolKind::Struct;
container_name(self, db: &RootDatabase) -> Option<SmolStr>285 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
286 container_name(db, self)
287 }
288 }
289 impl ToNavFromAst for hir::Enum {
290 const KIND: SymbolKind = SymbolKind::Enum;
container_name(self, db: &RootDatabase) -> Option<SmolStr>291 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
292 container_name(db, self)
293 }
294 }
295 impl ToNavFromAst for hir::Variant {
296 const KIND: SymbolKind = SymbolKind::Variant;
297 }
298 impl ToNavFromAst for hir::Union {
299 const KIND: SymbolKind = SymbolKind::Union;
container_name(self, db: &RootDatabase) -> Option<SmolStr>300 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
301 container_name(db, self)
302 }
303 }
304 impl ToNavFromAst for hir::TypeAlias {
305 const KIND: SymbolKind = SymbolKind::TypeAlias;
container_name(self, db: &RootDatabase) -> Option<SmolStr>306 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
307 container_name(db, self)
308 }
309 }
310 impl ToNavFromAst for hir::Trait {
311 const KIND: SymbolKind = SymbolKind::Trait;
container_name(self, db: &RootDatabase) -> Option<SmolStr>312 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
313 container_name(db, self)
314 }
315 }
316 impl ToNavFromAst for hir::TraitAlias {
317 const KIND: SymbolKind = SymbolKind::TraitAlias;
container_name(self, db: &RootDatabase) -> Option<SmolStr>318 fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
319 container_name(db, self)
320 }
321 }
322
323 impl<D> TryToNav for D
324 where
325 D: HasSource + ToNavFromAst + Copy + HasAttrs + HirDisplay,
326 D::Ast: ast::HasName,
327 {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>328 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
329 let src = self.source(db)?;
330 let mut res = NavigationTarget::from_named(
331 db,
332 src.as_ref().map(|it| it as &dyn ast::HasName),
333 D::KIND,
334 );
335 res.docs = self.docs(db);
336 res.description = Some(self.display(db).to_string());
337 res.container_name = self.container_name(db);
338 Some(res)
339 }
340 }
341
342 impl ToNav for hir::Module {
to_nav(&self, db: &RootDatabase) -> NavigationTarget343 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
344 let InFile { file_id, value } = self.definition_source(db);
345
346 let name = self.name(db).map(|it| it.to_smol_str()).unwrap_or_default();
347 let (syntax, focus) = match &value {
348 ModuleSource::SourceFile(node) => (node.syntax(), None),
349 ModuleSource::Module(node) => (node.syntax(), node.name()),
350 ModuleSource::BlockExpr(node) => (node.syntax(), None),
351 };
352 let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
353 NavigationTarget::from_syntax(file_id, name, focus_range, full_range, SymbolKind::Module)
354 }
355 }
356
357 impl TryToNav for hir::Impl {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>358 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
359 let InFile { file_id, value } = self.source(db)?;
360 let derive_attr = self.is_builtin_derive(db);
361
362 let focus = if derive_attr.is_some() { None } else { value.self_ty() };
363
364 let syntax = match &derive_attr {
365 Some(attr) => attr.value.syntax(),
366 None => value.syntax(),
367 };
368
369 let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
370 Some(NavigationTarget::from_syntax(
371 file_id,
372 "impl".into(),
373 focus_range,
374 full_range,
375 SymbolKind::Impl,
376 ))
377 }
378 }
379
380 impl TryToNav for hir::Field {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>381 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
382 let src = self.source(db)?;
383
384 let field_source = match &src.value {
385 FieldSource::Named(it) => {
386 let mut res =
387 NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field);
388 res.docs = self.docs(db);
389 res.description = Some(self.display(db).to_string());
390 res
391 }
392 FieldSource::Pos(it) => {
393 let FileRange { file_id, range } =
394 src.with_value(it.syntax()).original_file_range(db);
395 NavigationTarget::from_syntax(file_id, "".into(), None, range, SymbolKind::Field)
396 }
397 };
398 Some(field_source)
399 }
400 }
401
402 impl TryToNav for hir::Macro {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>403 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
404 let src = self.source(db)?;
405 let name_owner: &dyn ast::HasName = match &src.value {
406 Either::Left(it) => it,
407 Either::Right(it) => it,
408 };
409 let mut res = NavigationTarget::from_named(
410 db,
411 src.as_ref().with_value(name_owner),
412 self.kind(db).into(),
413 );
414 res.docs = self.docs(db);
415 Some(res)
416 }
417 }
418
419 impl TryToNav for hir::Adt {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>420 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
421 match self {
422 hir::Adt::Struct(it) => it.try_to_nav(db),
423 hir::Adt::Union(it) => it.try_to_nav(db),
424 hir::Adt::Enum(it) => it.try_to_nav(db),
425 }
426 }
427 }
428
429 impl TryToNav for hir::AssocItem {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>430 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
431 match self {
432 AssocItem::Function(it) => it.try_to_nav(db),
433 AssocItem::Const(it) => it.try_to_nav(db),
434 AssocItem::TypeAlias(it) => it.try_to_nav(db),
435 }
436 }
437 }
438
439 impl TryToNav for hir::GenericParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>440 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
441 match self {
442 hir::GenericParam::TypeParam(it) => it.try_to_nav(db),
443 hir::GenericParam::ConstParam(it) => it.try_to_nav(db),
444 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(db),
445 }
446 }
447 }
448
449 impl ToNav for LocalSource {
to_nav(&self, db: &RootDatabase) -> NavigationTarget450 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
451 let InFile { file_id, value } = &self.source;
452 let file_id = *file_id;
453 let local = self.local;
454 let (node, name) = match &value {
455 Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
456 Either::Right(it) => (it.syntax(), it.name()),
457 };
458
459 let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, node, name);
460
461 let name = local.name(db).to_smol_str();
462 let kind = if local.is_self(db) {
463 SymbolKind::SelfParam
464 } else if local.is_param(db) {
465 SymbolKind::ValueParam
466 } else {
467 SymbolKind::Local
468 };
469 NavigationTarget {
470 file_id,
471 name,
472 alias: None,
473 kind: Some(kind),
474 full_range,
475 focus_range,
476 container_name: None,
477 description: None,
478 docs: None,
479 }
480 }
481 }
482
483 impl ToNav for hir::Local {
to_nav(&self, db: &RootDatabase) -> NavigationTarget484 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
485 self.primary_source(db).to_nav(db)
486 }
487 }
488
489 impl ToNav for hir::Label {
to_nav(&self, db: &RootDatabase) -> NavigationTarget490 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
491 let InFile { file_id, value } = self.source(db);
492 let name = self.name(db).to_smol_str();
493
494 let (file_id, full_range, focus_range) =
495 orig_range_with_focus(db, file_id, value.syntax(), value.lifetime());
496
497 NavigationTarget {
498 file_id,
499 name,
500 alias: None,
501 kind: Some(SymbolKind::Label),
502 full_range,
503 focus_range,
504 container_name: None,
505 description: None,
506 docs: None,
507 }
508 }
509 }
510
511 impl TryToNav for hir::TypeParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>512 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
513 let InFile { file_id, value } = self.merge().source(db)?;
514 let name = self.name(db).to_smol_str();
515
516 let value = match value {
517 Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
518 Either::Left(ast::TypeOrConstParam::Const(_)) => {
519 never!();
520 return None;
521 }
522 Either::Right(x) => Either::Right(x),
523 };
524
525 let syntax = match &value {
526 Either::Left(type_param) => type_param.syntax(),
527 Either::Right(trait_) => trait_.syntax(),
528 };
529 let focus = value.as_ref().either(|it| it.name(), |it| it.name());
530
531 let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
532
533 Some(NavigationTarget {
534 file_id,
535 name,
536 alias: None,
537 kind: Some(SymbolKind::TypeParam),
538 full_range,
539 focus_range,
540 container_name: None,
541 description: None,
542 docs: None,
543 })
544 }
545 }
546
547 impl TryToNav for hir::TypeOrConstParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>548 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
549 self.split(db).try_to_nav(db)
550 }
551 }
552
553 impl TryToNav for hir::LifetimeParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>554 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
555 let InFile { file_id, value } = self.source(db)?;
556 let name = self.name(db).to_smol_str();
557
558 let FileRange { file_id, range } =
559 InFile::new(file_id, value.syntax()).original_file_range(db);
560 Some(NavigationTarget {
561 file_id,
562 name,
563 alias: None,
564 kind: Some(SymbolKind::LifetimeParam),
565 full_range: range,
566 focus_range: Some(range),
567 container_name: None,
568 description: None,
569 docs: None,
570 })
571 }
572 }
573
574 impl TryToNav for hir::ConstParam {
try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>575 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
576 let InFile { file_id, value } = self.merge().source(db)?;
577 let name = self.name(db).to_smol_str();
578
579 let value = match value {
580 Either::Left(ast::TypeOrConstParam::Const(x)) => x,
581 _ => {
582 never!();
583 return None;
584 }
585 };
586
587 let (file_id, full_range, focus_range) =
588 orig_range_with_focus(db, file_id, value.syntax(), value.name());
589 Some(NavigationTarget {
590 file_id,
591 name,
592 alias: None,
593 kind: Some(SymbolKind::ConstParam),
594 full_range,
595 focus_range,
596 container_name: None,
597 description: None,
598 docs: None,
599 })
600 }
601 }
602
orig_range_with_focus( db: &RootDatabase, hir_file: HirFileId, value: &SyntaxNode, name: Option<impl AstNode>, ) -> (FileId, TextRange, Option<TextRange>)603 fn orig_range_with_focus(
604 db: &RootDatabase,
605 hir_file: HirFileId,
606 value: &SyntaxNode,
607 name: Option<impl AstNode>,
608 ) -> (FileId, TextRange, Option<TextRange>) {
609 let FileRange { file_id, range: full_range } =
610 InFile::new(hir_file, value).original_file_range(db);
611 let focus_range = name
612 .and_then(|it| InFile::new(hir_file, it.syntax()).original_file_range_opt(db))
613 .and_then(|range| if range.file_id == file_id { Some(range.range) } else { None });
614
615 (file_id, full_range, focus_range)
616 }
617
618 #[cfg(test)]
619 mod tests {
620 use expect_test::expect;
621
622 use crate::{fixture, Query};
623
624 #[test]
test_nav_for_symbol()625 fn test_nav_for_symbol() {
626 let (analysis, _) = fixture::file(
627 r#"
628 enum FooInner { }
629 fn foo() { enum FooInner { } }
630 "#,
631 );
632
633 let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap();
634 expect![[r#"
635 [
636 NavigationTarget {
637 file_id: FileId(
638 0,
639 ),
640 full_range: 0..17,
641 focus_range: 5..13,
642 name: "FooInner",
643 kind: Enum,
644 description: "enum FooInner",
645 },
646 NavigationTarget {
647 file_id: FileId(
648 0,
649 ),
650 full_range: 29..46,
651 focus_range: 34..42,
652 name: "FooInner",
653 kind: Enum,
654 container_name: "foo",
655 description: "enum FooInner",
656 },
657 ]
658 "#]]
659 .assert_debug_eq(&navs);
660 }
661
662 #[test]
test_world_symbols_are_case_sensitive()663 fn test_world_symbols_are_case_sensitive() {
664 let (analysis, _) = fixture::file(
665 r#"
666 fn foo() {}
667 struct Foo;
668 "#,
669 );
670
671 let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
672 assert_eq!(navs.len(), 2)
673 }
674 }
675