diff options
Diffstat (limited to 'crates/ra_ide_api/src/display')
-rw-r--r-- | crates/ra_ide_api/src/display/navigation_target.rs | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs new file mode 100644 index 000000000..f6d7f3192 --- /dev/null +++ b/crates/ra_ide_api/src/display/navigation_target.rs | |||
@@ -0,0 +1,251 @@ | |||
1 | use ra_db::FileId; | ||
2 | use ra_syntax::{ | ||
3 | SyntaxNode, SyntaxNodePtr, AstNode, SmolStr, TextRange, ast, | ||
4 | SyntaxKind::{self, NAME}, | ||
5 | }; | ||
6 | use hir::{ModuleSource, FieldSource, Name, ImplItem}; | ||
7 | |||
8 | use 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)] | ||
16 | pub struct NavigationTarget { | ||
17 | file_id: FileId, | ||
18 | name: SmolStr, | ||
19 | kind: SyntaxKind, | ||
20 | full_range: TextRange, | ||
21 | focus_range: Option<TextRange>, | ||
22 | container_name: Option<SmolStr>, | ||
23 | } | ||
24 | |||
25 | impl NavigationTarget { | ||
26 | /// When `focus_range` is specified, returns it. otherwise | ||
27 | /// returns `full_range` | ||
28 | pub fn range(&self) -> TextRange { | ||
29 | self.focus_range.unwrap_or(self.full_range) | ||
30 | } | ||
31 | |||
32 | pub fn name(&self) -> &SmolStr { | ||
33 | &self.name | ||
34 | } | ||
35 | |||
36 | pub fn container_name(&self) -> Option<&SmolStr> { | ||
37 | self.container_name.as_ref() | ||
38 | } | ||
39 | |||
40 | pub fn kind(&self) -> SyntaxKind { | ||
41 | self.kind | ||
42 | } | ||
43 | |||
44 | pub fn file_id(&self) -> FileId { | ||
45 | self.file_id | ||
46 | } | ||
47 | |||
48 | pub fn full_range(&self) -> TextRange { | ||
49 | self.full_range | ||
50 | } | ||
51 | |||
52 | /// A "most interesting" range withing the `full_range`. | ||
53 | /// | ||
54 | /// Typically, `full_range` is the whole syntax node, | ||
55 | /// including doc comments, and `focus_range` is the range of the identifier. | ||
56 | pub fn focus_range(&self) -> Option<TextRange> { | ||
57 | self.focus_range | ||
58 | } | ||
59 | |||
60 | pub(crate) fn from_bind_pat(file_id: FileId, pat: &ast::BindPat) -> NavigationTarget { | ||
61 | NavigationTarget::from_named(file_id, pat) | ||
62 | } | ||
63 | |||
64 | pub(crate) fn from_symbol(symbol: FileSymbol) -> NavigationTarget { | ||
65 | NavigationTarget { | ||
66 | file_id: symbol.file_id, | ||
67 | name: symbol.name.clone(), | ||
68 | kind: symbol.ptr.kind(), | ||
69 | full_range: symbol.ptr.range(), | ||
70 | focus_range: symbol.name_range, | ||
71 | container_name: symbol.container_name.clone(), | ||
72 | } | ||
73 | } | ||
74 | |||
75 | pub(crate) fn from_scope_entry( | ||
76 | file_id: FileId, | ||
77 | name: Name, | ||
78 | ptr: SyntaxNodePtr, | ||
79 | ) -> NavigationTarget { | ||
80 | NavigationTarget { | ||
81 | file_id, | ||
82 | name: name.to_string().into(), | ||
83 | full_range: ptr.range(), | ||
84 | focus_range: None, | ||
85 | kind: NAME, | ||
86 | container_name: None, | ||
87 | } | ||
88 | } | ||
89 | |||
90 | pub(crate) fn from_module(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | ||
91 | let (file_id, source) = module.definition_source(db); | ||
92 | let file_id = file_id.as_original_file(); | ||
93 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | ||
94 | match source { | ||
95 | ModuleSource::SourceFile(node) => { | ||
96 | NavigationTarget::from_syntax(file_id, name, None, node.syntax()) | ||
97 | } | ||
98 | ModuleSource::Module(node) => { | ||
99 | NavigationTarget::from_syntax(file_id, name, None, node.syntax()) | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | |||
104 | pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | ||
105 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | ||
106 | if let Some((file_id, source)) = module.declaration_source(db) { | ||
107 | let file_id = file_id.as_original_file(); | ||
108 | return NavigationTarget::from_syntax(file_id, name, None, source.syntax()); | ||
109 | } | ||
110 | NavigationTarget::from_module(db, module) | ||
111 | } | ||
112 | |||
113 | pub(crate) fn from_function(db: &RootDatabase, func: hir::Function) -> NavigationTarget { | ||
114 | let (file_id, fn_def) = func.source(db); | ||
115 | NavigationTarget::from_named(file_id.original_file(db), &*fn_def) | ||
116 | } | ||
117 | |||
118 | pub(crate) fn from_field(db: &RootDatabase, field: hir::StructField) -> NavigationTarget { | ||
119 | let (file_id, field) = field.source(db); | ||
120 | let file_id = file_id.original_file(db); | ||
121 | match field { | ||
122 | FieldSource::Named(it) => NavigationTarget::from_named(file_id, &*it), | ||
123 | FieldSource::Pos(it) => { | ||
124 | NavigationTarget::from_syntax(file_id, "".into(), None, it.syntax()) | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | pub(crate) fn from_adt_def(db: &RootDatabase, adt_def: hir::AdtDef) -> NavigationTarget { | ||
130 | match adt_def { | ||
131 | hir::AdtDef::Struct(s) => { | ||
132 | let (file_id, node) = s.source(db); | ||
133 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
134 | } | ||
135 | hir::AdtDef::Enum(s) => { | ||
136 | let (file_id, node) = s.source(db); | ||
137 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | pub(crate) fn from_def(db: &RootDatabase, module_def: hir::ModuleDef) -> NavigationTarget { | ||
143 | match module_def { | ||
144 | hir::ModuleDef::Module(module) => NavigationTarget::from_module(db, module), | ||
145 | hir::ModuleDef::Function(func) => NavigationTarget::from_function(db, func), | ||
146 | hir::ModuleDef::Struct(s) => { | ||
147 | let (file_id, node) = s.source(db); | ||
148 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
149 | } | ||
150 | hir::ModuleDef::Const(s) => { | ||
151 | let (file_id, node) = s.source(db); | ||
152 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
153 | } | ||
154 | hir::ModuleDef::Static(s) => { | ||
155 | let (file_id, node) = s.source(db); | ||
156 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
157 | } | ||
158 | hir::ModuleDef::Enum(e) => { | ||
159 | let (file_id, node) = e.source(db); | ||
160 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
161 | } | ||
162 | hir::ModuleDef::EnumVariant(var) => { | ||
163 | let (file_id, node) = var.source(db); | ||
164 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
165 | } | ||
166 | hir::ModuleDef::Trait(e) => { | ||
167 | let (file_id, node) = e.source(db); | ||
168 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
169 | } | ||
170 | hir::ModuleDef::TypeAlias(e) => { | ||
171 | let (file_id, node) = e.source(db); | ||
172 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | |||
177 | pub(crate) fn from_impl_block( | ||
178 | db: &RootDatabase, | ||
179 | impl_block: hir::ImplBlock, | ||
180 | ) -> NavigationTarget { | ||
181 | let (file_id, node) = impl_block.source(db); | ||
182 | NavigationTarget::from_syntax( | ||
183 | file_id.as_original_file(), | ||
184 | "impl".into(), | ||
185 | None, | ||
186 | node.syntax(), | ||
187 | ) | ||
188 | } | ||
189 | |||
190 | pub(crate) fn from_impl_item(db: &RootDatabase, impl_item: hir::ImplItem) -> NavigationTarget { | ||
191 | match impl_item { | ||
192 | ImplItem::Method(f) => NavigationTarget::from_function(db, f), | ||
193 | ImplItem::Const(c) => { | ||
194 | let (file_id, node) = c.source(db); | ||
195 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
196 | } | ||
197 | ImplItem::TypeAlias(a) => { | ||
198 | let (file_id, node) = a.source(db); | ||
199 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
204 | #[cfg(test)] | ||
205 | pub(crate) fn assert_match(&self, expected: &str) { | ||
206 | let actual = self.debug_render(); | ||
207 | test_utils::assert_eq_text!(expected.trim(), actual.trim(),); | ||
208 | } | ||
209 | |||
210 | #[cfg(test)] | ||
211 | pub(crate) fn debug_render(&self) -> String { | ||
212 | let mut buf = format!( | ||
213 | "{} {:?} {:?} {:?}", | ||
214 | self.name(), | ||
215 | self.kind(), | ||
216 | self.file_id(), | ||
217 | self.full_range() | ||
218 | ); | ||
219 | if let Some(focus_range) = self.focus_range() { | ||
220 | buf.push_str(&format!(" {:?}", focus_range)) | ||
221 | } | ||
222 | if let Some(container_name) = self.container_name() { | ||
223 | buf.push_str(&format!(" {}", container_name)) | ||
224 | } | ||
225 | buf | ||
226 | } | ||
227 | |||
228 | /// Allows `NavigationTarget` to be created from a `NameOwner` | ||
229 | pub(crate) fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget { | ||
230 | let name = node.name().map(|it| it.text().clone()).unwrap_or_default(); | ||
231 | let focus_range = node.name().map(|it| it.syntax().range()); | ||
232 | NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax()) | ||
233 | } | ||
234 | |||
235 | fn from_syntax( | ||
236 | file_id: FileId, | ||
237 | name: SmolStr, | ||
238 | focus_range: Option<TextRange>, | ||
239 | node: &SyntaxNode, | ||
240 | ) -> NavigationTarget { | ||
241 | NavigationTarget { | ||
242 | file_id, | ||
243 | name, | ||
244 | kind: node.kind(), | ||
245 | full_range: node.range(), | ||
246 | focus_range, | ||
247 | // ptr: Some(LocalSyntaxPtr::new(node)), | ||
248 | container_name: None, | ||
249 | } | ||
250 | } | ||
251 | } | ||