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