aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/display/navigation_target.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/display/navigation_target.rs')
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs411
1 files changed, 411 insertions, 0 deletions
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
new file mode 100644
index 000000000..6ac60722b
--- /dev/null
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -0,0 +1,411 @@
1//! FIXME: write short doc here
2
3use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source};
4use ra_db::{FileId, SourceDatabase};
5use ra_syntax::{
6 ast::{self, DocCommentsOwner, NameOwner},
7 match_ast, AstNode, SmolStr,
8 SyntaxKind::{self, BIND_PAT},
9 TextRange,
10};
11
12use crate::{db::RootDatabase, expand::original_range, FileSymbol};
13
14use super::short_label::ShortLabel;
15
16/// `NavigationTarget` represents and element in the editor's UI which you can
17/// click on to navigate to a particular piece of code.
18///
19/// Typically, a `NavigationTarget` corresponds to some element in the source
20/// code, like a function or a struct, but this is not strictly required.
21#[derive(Debug, Clone)]
22pub struct NavigationTarget {
23 file_id: FileId,
24 name: SmolStr,
25 kind: SyntaxKind,
26 full_range: TextRange,
27 focus_range: Option<TextRange>,
28 container_name: Option<SmolStr>,
29 description: Option<String>,
30 docs: Option<String>,
31}
32
33pub(crate) trait ToNav {
34 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
35}
36
37impl NavigationTarget {
38 /// When `focus_range` is specified, returns it. otherwise
39 /// returns `full_range`
40 pub fn range(&self) -> TextRange {
41 self.focus_range.unwrap_or(self.full_range)
42 }
43
44 pub fn name(&self) -> &SmolStr {
45 &self.name
46 }
47
48 pub fn container_name(&self) -> Option<&SmolStr> {
49 self.container_name.as_ref()
50 }
51
52 pub fn kind(&self) -> SyntaxKind {
53 self.kind
54 }
55
56 pub fn file_id(&self) -> FileId {
57 self.file_id
58 }
59
60 pub fn full_range(&self) -> TextRange {
61 self.full_range
62 }
63
64 pub fn docs(&self) -> Option<&str> {
65 self.docs.as_ref().map(String::as_str)
66 }
67
68 pub fn description(&self) -> Option<&str> {
69 self.description.as_ref().map(String::as_str)
70 }
71
72 /// A "most interesting" range withing the `full_range`.
73 ///
74 /// Typically, `full_range` is the whole syntax node,
75 /// including doc comments, and `focus_range` is the range of the identifier.
76 pub fn focus_range(&self) -> Option<TextRange> {
77 self.focus_range
78 }
79
80 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
81 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
82 if let Some(src) = module.declaration_source(db) {
83 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
84 return NavigationTarget::from_syntax(
85 frange.file_id,
86 name,
87 None,
88 frange.range,
89 src.value.syntax().kind(),
90 src.value.doc_comment_text(),
91 src.value.short_label(),
92 );
93 }
94 module.to_nav(db)
95 }
96
97 pub(crate) fn from_def(
98 db: &RootDatabase,
99 module_def: hir::ModuleDef,
100 ) -> Option<NavigationTarget> {
101 let nav = match module_def {
102 hir::ModuleDef::Module(module) => module.to_nav(db),
103 hir::ModuleDef::Function(it) => it.to_nav(db),
104 hir::ModuleDef::Adt(it) => it.to_nav(db),
105 hir::ModuleDef::Const(it) => it.to_nav(db),
106 hir::ModuleDef::Static(it) => it.to_nav(db),
107 hir::ModuleDef::EnumVariant(it) => it.to_nav(db),
108 hir::ModuleDef::Trait(it) => it.to_nav(db),
109 hir::ModuleDef::TypeAlias(it) => it.to_nav(db),
110 hir::ModuleDef::BuiltinType(..) => {
111 return None;
112 }
113 };
114 Some(nav)
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 if let Some(container_name) = self.container_name() {
136 buf.push_str(&format!(" {}", container_name))
137 }
138 buf
139 }
140
141 /// Allows `NavigationTarget` to be created from a `NameOwner`
142 pub(crate) fn from_named(
143 db: &RootDatabase,
144 node: Source<&dyn ast::NameOwner>,
145 docs: Option<String>,
146 description: Option<String>,
147 ) -> NavigationTarget {
148 //FIXME: use `_` instead of empty string
149 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
150 let focus_range =
151 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
152 let frange = original_range(db, node.map(|it| it.syntax()));
153
154 NavigationTarget::from_syntax(
155 frange.file_id,
156 name,
157 focus_range,
158 frange.range,
159 node.value.syntax().kind(),
160 docs,
161 description,
162 )
163 }
164
165 fn from_syntax(
166 file_id: FileId,
167 name: SmolStr,
168 focus_range: Option<TextRange>,
169 full_range: TextRange,
170 kind: SyntaxKind,
171 docs: Option<String>,
172 description: Option<String>,
173 ) -> NavigationTarget {
174 NavigationTarget {
175 file_id,
176 name,
177 kind,
178 full_range,
179 focus_range,
180 container_name: None,
181 description,
182 docs,
183 }
184 }
185}
186
187impl ToNav for FileSymbol {
188 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
189 NavigationTarget {
190 file_id: self.file_id,
191 name: self.name.clone(),
192 kind: self.ptr.kind(),
193 full_range: self.ptr.range(),
194 focus_range: self.name_range,
195 container_name: self.container_name.clone(),
196 description: description_from_symbol(db, self),
197 docs: docs_from_symbol(db, self),
198 }
199 }
200}
201
202pub(crate) trait ToNavFromAst {}
203impl ToNavFromAst for hir::Function {}
204impl ToNavFromAst for hir::Const {}
205impl ToNavFromAst for hir::Static {}
206impl ToNavFromAst for hir::Struct {}
207impl ToNavFromAst for hir::Enum {}
208impl ToNavFromAst for hir::EnumVariant {}
209impl ToNavFromAst for hir::Union {}
210impl ToNavFromAst for hir::TypeAlias {}
211impl ToNavFromAst for hir::Trait {}
212
213impl<D> ToNav for D
214where
215 D: HasSource + ToNavFromAst + Copy,
216 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
217{
218 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
219 let src = self.source(db);
220 NavigationTarget::from_named(
221 db,
222 src.as_ref().map(|it| it as &dyn ast::NameOwner),
223 src.value.doc_comment_text(),
224 src.value.short_label(),
225 )
226 }
227}
228
229impl ToNav for hir::Module {
230 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
231 let src = self.definition_source(db);
232 let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
233 match &src.value {
234 ModuleSource::SourceFile(node) => {
235 let frange = original_range(db, src.with_value(node.syntax()));
236
237 NavigationTarget::from_syntax(
238 frange.file_id,
239 name,
240 None,
241 frange.range,
242 node.syntax().kind(),
243 None,
244 None,
245 )
246 }
247 ModuleSource::Module(node) => {
248 let frange = original_range(db, src.with_value(node.syntax()));
249
250 NavigationTarget::from_syntax(
251 frange.file_id,
252 name,
253 None,
254 frange.range,
255 node.syntax().kind(),
256 node.doc_comment_text(),
257 node.short_label(),
258 )
259 }
260 }
261 }
262}
263
264impl ToNav for hir::ImplBlock {
265 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
266 let src = self.source(db);
267 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
268
269 NavigationTarget::from_syntax(
270 frange.file_id,
271 "impl".into(),
272 None,
273 frange.range,
274 src.value.syntax().kind(),
275 None,
276 None,
277 )
278 }
279}
280
281impl ToNav for hir::StructField {
282 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
283 let src = self.source(db);
284
285 match &src.value {
286 FieldSource::Named(it) => NavigationTarget::from_named(
287 db,
288 src.with_value(it),
289 it.doc_comment_text(),
290 it.short_label(),
291 ),
292 FieldSource::Pos(it) => {
293 let frange = original_range(db, src.with_value(it.syntax()));
294 NavigationTarget::from_syntax(
295 frange.file_id,
296 "".into(),
297 None,
298 frange.range,
299 it.syntax().kind(),
300 None,
301 None,
302 )
303 }
304 }
305 }
306}
307
308impl ToNav for hir::MacroDef {
309 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
310 let src = self.source(db);
311 log::debug!("nav target {:#?}", src.value.syntax());
312 NavigationTarget::from_named(
313 db,
314 src.as_ref().map(|it| it as &dyn ast::NameOwner),
315 src.value.doc_comment_text(),
316 None,
317 )
318 }
319}
320
321impl ToNav for hir::Adt {
322 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
323 match self {
324 hir::Adt::Struct(it) => it.to_nav(db),
325 hir::Adt::Union(it) => it.to_nav(db),
326 hir::Adt::Enum(it) => it.to_nav(db),
327 }
328 }
329}
330
331impl ToNav for hir::AssocItem {
332 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
333 match self {
334 AssocItem::Function(it) => it.to_nav(db),
335 AssocItem::Const(it) => it.to_nav(db),
336 AssocItem::TypeAlias(it) => it.to_nav(db),
337 }
338 }
339}
340
341impl ToNav for hir::Local {
342 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
343 let src = self.source(db);
344 let (full_range, focus_range) = match src.value {
345 Either::A(it) => {
346 (it.syntax().text_range(), it.name().map(|it| it.syntax().text_range()))
347 }
348 Either::B(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())),
349 };
350 let name = match self.name(db) {
351 Some(it) => it.to_string().into(),
352 None => "".into(),
353 };
354 NavigationTarget {
355 file_id: src.file_id.original_file(db),
356 name,
357 kind: BIND_PAT,
358 full_range,
359 focus_range,
360 container_name: None,
361 description: None,
362 docs: None,
363 }
364 }
365}
366
367pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
368 let parse = db.parse(symbol.file_id);
369 let node = symbol.ptr.to_node(parse.tree().syntax());
370
371 match_ast! {
372 match node {
373 ast::FnDef(it) => { it.doc_comment_text() },
374 ast::StructDef(it) => { it.doc_comment_text() },
375 ast::EnumDef(it) => { it.doc_comment_text() },
376 ast::TraitDef(it) => { it.doc_comment_text() },
377 ast::Module(it) => { it.doc_comment_text() },
378 ast::TypeAliasDef(it) => { it.doc_comment_text() },
379 ast::ConstDef(it) => { it.doc_comment_text() },
380 ast::StaticDef(it) => { it.doc_comment_text() },
381 ast::RecordFieldDef(it) => { it.doc_comment_text() },
382 ast::EnumVariant(it) => { it.doc_comment_text() },
383 ast::MacroCall(it) => { it.doc_comment_text() },
384 _ => None,
385 }
386 }
387}
388
389/// Get a description of a symbol.
390///
391/// e.g. `struct Name`, `enum Name`, `fn Name`
392pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
393 let parse = db.parse(symbol.file_id);
394 let node = symbol.ptr.to_node(parse.tree().syntax());
395
396 match_ast! {
397 match node {
398 ast::FnDef(it) => { it.short_label() },
399 ast::StructDef(it) => { it.short_label() },
400 ast::EnumDef(it) => { it.short_label() },
401 ast::TraitDef(it) => { it.short_label() },
402 ast::Module(it) => { it.short_label() },
403 ast::TypeAliasDef(it) => { it.short_label() },
404 ast::ConstDef(it) => { it.short_label() },
405 ast::StaticDef(it) => { it.short_label() },
406 ast::RecordFieldDef(it) => { it.short_label() },
407 ast::EnumVariant(it) => { it.short_label() },
408 _ => None,
409 }
410 }
411}