aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/navigation_target.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/navigation_target.rs')
-rw-r--r--crates/ra_ide_api/src/navigation_target.rs159
1 files changed, 159 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/navigation_target.rs b/crates/ra_ide_api/src/navigation_target.rs
new file mode 100644
index 000000000..b955bbe42
--- /dev/null
+++ b/crates/ra_ide_api/src/navigation_target.rs
@@ -0,0 +1,159 @@
1use ra_db::{FileId, Cancelable};
2use ra_syntax::{
3 SyntaxNode, AstNode, SmolStr, TextRange, ast,
4 SyntaxKind::{self, NAME},
5};
6use hir::{Def, ModuleSource};
7
8use crate::{FileSymbol, db::RootDatabase};
9
10/// `NavigationTarget` represents and element in the editor's UI which you can
11/// click on to navigate to a particular piece of code.
12///
13/// Typically, a `NavigationTarget` corresponds to some element in the source
14/// code, like a function or a struct, but this is not strictly required.
15#[derive(Debug, Clone)]
16pub struct NavigationTarget {
17 file_id: FileId,
18 name: SmolStr,
19 kind: SyntaxKind,
20 full_range: TextRange,
21 focus_range: Option<TextRange>,
22}
23
24impl NavigationTarget {
25 pub fn name(&self) -> &SmolStr {
26 &self.name
27 }
28
29 pub fn kind(&self) -> SyntaxKind {
30 self.kind
31 }
32
33 pub fn file_id(&self) -> FileId {
34 self.file_id
35 }
36
37 pub fn full_range(&self) -> TextRange {
38 self.full_range
39 }
40
41 /// A "most interesting" range withing the `range_full`.
42 ///
43 /// Typically, `range` is the whole syntax node, including doc comments, and
44 /// `focus_range` is the range of the identifier.
45 pub fn focus_range(&self) -> Option<TextRange> {
46 self.focus_range
47 }
48
49 pub(crate) fn from_symbol(symbol: FileSymbol) -> NavigationTarget {
50 NavigationTarget {
51 file_id: symbol.file_id,
52 name: symbol.name.clone(),
53 kind: symbol.ptr.kind(),
54 full_range: symbol.ptr.range(),
55 focus_range: None,
56 }
57 }
58
59 pub(crate) fn from_scope_entry(
60 file_id: FileId,
61 entry: &hir::ScopeEntryWithSyntax,
62 ) -> NavigationTarget {
63 NavigationTarget {
64 file_id,
65 name: entry.name().to_string().into(),
66 full_range: entry.ptr().range(),
67 focus_range: None,
68 kind: NAME,
69 }
70 }
71
72 pub(crate) fn from_module(
73 db: &RootDatabase,
74 module: hir::Module,
75 ) -> Cancelable<NavigationTarget> {
76 let (file_id, source) = module.definition_source(db)?;
77 let name = module
78 .name(db)?
79 .map(|it| it.to_string().into())
80 .unwrap_or_default();
81 let res = match source {
82 ModuleSource::SourceFile(node) => {
83 NavigationTarget::from_syntax(file_id, name, None, node.syntax())
84 }
85 ModuleSource::Module(node) => {
86 NavigationTarget::from_syntax(file_id, name, None, node.syntax())
87 }
88 };
89 Ok(res)
90 }
91
92 // TODO once Def::Item is gone, this should be able to always return a NavigationTarget
93 pub(crate) fn from_def(db: &RootDatabase, def: Def) -> Cancelable<Option<NavigationTarget>> {
94 let res = match def {
95 Def::Struct(s) => {
96 let (file_id, node) = s.source(db)?;
97 NavigationTarget::from_named(file_id.original_file(db), &*node)
98 }
99 Def::Enum(e) => {
100 let (file_id, node) = e.source(db)?;
101 NavigationTarget::from_named(file_id.original_file(db), &*node)
102 }
103 Def::EnumVariant(ev) => {
104 let (file_id, node) = ev.source(db)?;
105 NavigationTarget::from_named(file_id.original_file(db), &*node)
106 }
107 Def::Function(f) => {
108 let (file_id, node) = f.source(db)?;
109 NavigationTarget::from_named(file_id.original_file(db), &*node)
110 }
111 Def::Module(m) => NavigationTarget::from_module(db, m)?,
112 Def::Item => return Ok(None),
113 };
114 Ok(Some(res))
115 }
116
117 #[cfg(test)]
118 pub(crate) fn assert_match(&self, expected: &str) {
119 let actual = self.debug_render();
120 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
121 }
122
123 #[cfg(test)]
124 pub(crate) fn debug_render(&self) -> String {
125 let mut buf = format!(
126 "{} {:?} {:?} {:?}",
127 self.name(),
128 self.kind(),
129 self.file_id(),
130 self.full_range()
131 );
132 if let Some(focus_range) = self.focus_range() {
133 buf.push_str(&format!(" {:?}", focus_range))
134 }
135 buf
136 }
137
138 fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget {
139 let name = node.name().map(|it| it.text().clone()).unwrap_or_default();
140 let focus_range = node.name().map(|it| it.syntax().range());
141 NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax())
142 }
143
144 fn from_syntax(
145 file_id: FileId,
146 name: SmolStr,
147 focus_range: Option<TextRange>,
148 node: &SyntaxNode,
149 ) -> NavigationTarget {
150 NavigationTarget {
151 file_id,
152 name,
153 kind: node.kind(),
154 full_range: node.range(),
155 focus_range,
156 // ptr: Some(LocalSyntaxPtr::new(node)),
157 }
158 }
159}