1 //! FIXME: write short doc here
4 use hir::{AssocItem, FieldSource, HasSource, InFile, ModuleSource};
5 use ra_db::{FileId, SourceDatabase};
7 ast::{self, DocCommentsOwner, NameOwner},
8 match_ast, AstNode, SmolStr,
9 SyntaxKind::{self, BIND_PAT},
13 use crate::{db::RootDatabase, expand::original_range, FileSymbol};
15 use super::short_label::ShortLabel;
17 /// `NavigationTarget` represents and element in the editor's UI which you can
18 /// click on to navigate to a particular piece of code.
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 {
27 full_range: TextRange,
28 focus_range: Option<TextRange>,
29 container_name: Option<SmolStr>,
30 description: Option<String>,
34 pub(crate) trait ToNav {
35 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
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)
45 pub fn name(&self) -> &SmolStr {
49 pub fn container_name(&self) -> Option<&SmolStr> {
50 self.container_name.as_ref()
53 pub fn kind(&self) -> SyntaxKind {
57 pub fn file_id(&self) -> FileId {
61 pub fn full_range(&self) -> TextRange {
65 pub fn docs(&self) -> Option<&str> {
66 self.docs.as_ref().map(String::as_str)
69 pub fn description(&self) -> Option<&str> {
70 self.description.as_ref().map(String::as_str)
73 /// A "most interesting" range withing the `full_range`.
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> {
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(
90 src.value.syntax().kind(),
91 src.value.doc_comment_text(),
92 src.value.short_label(),
98 pub(crate) fn from_def(
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(..) => {
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(),);
125 pub(crate) fn debug_render(&self) -> String {
126 let mut buf = format!(
133 if let Some(focus_range) = self.focus_range() {
134 buf.push_str(&format!(" {:?}", focus_range))
136 if let Some(container_name) = self.container_name() {
137 buf.push_str(&format!(" {}", container_name))
142 /// Allows `NavigationTarget` to be created from a `NameOwner`
143 pub(crate) fn from_named(
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();
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()));
155 NavigationTarget::from_syntax(
160 node.value.syntax().kind(),
169 focus_range: Option<TextRange>,
170 full_range: TextRange,
172 docs: Option<String>,
173 description: Option<String>,
174 ) -> NavigationTarget {
181 container_name: None,
188 impl ToNav for FileSymbol {
189 fn to_nav(&self, db: &RootDatabase) -> 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),
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 {}
216 D: HasSource + ToNavFromAst + Copy,
217 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
219 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
220 let src = self.source(db);
221 NavigationTarget::from_named(
223 src.as_ref().map(|it| it as &dyn ast::NameOwner),
224 src.value.doc_comment_text(),
225 src.value.short_label(),
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();
235 ModuleSource::SourceFile(node) => {
236 let frange = original_range(db, src.with_value(node.syntax()));
238 NavigationTarget::from_syntax(
243 node.syntax().kind(),
248 ModuleSource::Module(node) => {
249 let frange = original_range(db, src.with_value(node.syntax()));
251 NavigationTarget::from_syntax(
256 node.syntax().kind(),
257 node.doc_comment_text(),
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()));
270 NavigationTarget::from_syntax(
275 src.value.syntax().kind(),
282 impl ToNav for hir::StructField {
283 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
284 let src = self.source(db);
287 FieldSource::Named(it) => NavigationTarget::from_named(
290 it.doc_comment_text(),
293 FieldSource::Pos(it) => {
294 let frange = original_range(db, src.with_value(it.syntax()));
295 NavigationTarget::from_syntax(
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(
315 src.as_ref().map(|it| it as &dyn ast::NameOwner),
316 src.value.doc_comment_text(),
322 impl ToNav for hir::Adt {
323 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
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),
332 impl ToNav for hir::AssocItem {
333 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
335 AssocItem::Function(it) => it.to_nav(db),
336 AssocItem::Const(it) => it.to_nav(db),
337 AssocItem::TypeAlias(it) => it.to_nav(db),
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()))
349 Either::Right(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())),
351 let name = match self.name(db) {
352 Some(it) => it.to_string().into(),
356 file_id: src.file_id.original_file(db),
361 container_name: None,
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());
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() },
390 /// Get a description of a symbol.
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());
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() },