1 //! FIXME: write short doc here
4 use hir::{original_range, AssocItem, FieldSource, HasSource, InFile, ModuleSource};
5 use ra_db::{FileId, SourceDatabase};
6 use ra_ide_db::{defs::Definition, RootDatabase};
8 ast::{self, DocCommentsOwner, NameOwner},
9 match_ast, AstNode, SmolStr,
10 SyntaxKind::{self, BIND_PAT, TYPE_PARAM},
14 use crate::{FileRange, FileSymbol};
16 use super::short_label::ShortLabel;
18 /// `NavigationTarget` represents and element in the editor's UI which you can
19 /// click on to navigate to a particular piece of code.
21 /// Typically, a `NavigationTarget` corresponds to some element in the source
22 /// code, like a function or a struct, but this is not strictly required.
23 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
24 pub struct NavigationTarget {
25 // FIXME: use FileRange?
27 full_range: TextRange,
30 focus_range: Option<TextRange>,
31 container_name: Option<SmolStr>,
32 description: Option<String>,
36 pub(crate) trait ToNav {
37 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
40 pub(crate) trait TryToNav {
41 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
44 impl NavigationTarget {
45 /// When `focus_range` is specified, returns it. otherwise
46 /// returns `full_range`
47 pub fn range(&self) -> TextRange {
48 self.focus_range.unwrap_or(self.full_range)
51 pub fn name(&self) -> &SmolStr {
55 pub fn container_name(&self) -> Option<&SmolStr> {
56 self.container_name.as_ref()
59 pub fn kind(&self) -> SyntaxKind {
63 pub fn file_id(&self) -> FileId {
67 pub fn file_range(&self) -> FileRange {
68 FileRange { file_id: self.file_id, range: self.full_range }
71 pub fn full_range(&self) -> TextRange {
75 pub fn docs(&self) -> Option<&str> {
79 pub fn description(&self) -> Option<&str> {
80 self.description.as_deref()
83 /// A "most interesting" range withing the `full_range`.
85 /// Typically, `full_range` is the whole syntax node,
86 /// including doc comments, and `focus_range` is the range of the identifier.
87 pub fn focus_range(&self) -> Option<TextRange> {
91 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
93 if let Some(src) = module.declaration_source(db) {
94 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
95 let mut res = NavigationTarget::from_syntax(
100 src.value.syntax().kind(),
102 res.docs = src.value.doc_comment_text();
103 res.description = src.value.short_label();
110 pub(crate) fn assert_match(&self, expected: &str) {
111 let actual = self.debug_render();
112 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
116 pub(crate) fn debug_render(&self) -> String {
117 let mut buf = format!(
124 if let Some(focus_range) = self.focus_range() {
125 buf.push_str(&format!(" {:?}", focus_range))
127 if let Some(container_name) = self.container_name() {
128 buf.push_str(&format!(" {}", container_name))
133 /// Allows `NavigationTarget` to be created from a `NameOwner`
134 pub(crate) fn from_named(
136 node: InFile<&dyn ast::NameOwner>,
137 ) -> NavigationTarget {
138 //FIXME: use `_` instead of empty string
139 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
141 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
142 let frange = original_range(db, node.map(|it| it.syntax()));
144 NavigationTarget::from_syntax(
149 node.value.syntax().kind(),
156 focus_range: Option<TextRange>,
157 full_range: TextRange,
159 ) -> NavigationTarget {
166 container_name: None,
173 impl ToNav for FileSymbol {
174 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
176 file_id: self.file_id,
177 name: self.name.clone(),
179 full_range: self.range,
180 focus_range: self.name_range,
181 container_name: self.container_name.clone(),
182 description: description_from_symbol(db, self),
183 docs: docs_from_symbol(db, self),
188 impl TryToNav for Definition {
189 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
191 Definition::Macro(it) => Some(it.to_nav(db)),
192 Definition::Field(it) => Some(it.to_nav(db)),
193 Definition::ModuleDef(it) => it.try_to_nav(db),
194 Definition::SelfType(it) => Some(it.to_nav(db)),
195 Definition::Local(it) => Some(it.to_nav(db)),
196 Definition::TypeParam(it) => Some(it.to_nav(db)),
201 impl TryToNav for hir::ModuleDef {
202 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
203 let res = match self {
204 hir::ModuleDef::Module(it) => it.to_nav(db),
205 hir::ModuleDef::Function(it) => it.to_nav(db),
206 hir::ModuleDef::Adt(it) => it.to_nav(db),
207 hir::ModuleDef::EnumVariant(it) => it.to_nav(db),
208 hir::ModuleDef::Const(it) => it.to_nav(db),
209 hir::ModuleDef::Static(it) => it.to_nav(db),
210 hir::ModuleDef::Trait(it) => it.to_nav(db),
211 hir::ModuleDef::TypeAlias(it) => it.to_nav(db),
212 hir::ModuleDef::BuiltinType(_) => return None,
218 pub(crate) trait ToNavFromAst {}
219 impl ToNavFromAst for hir::Function {}
220 impl ToNavFromAst for hir::Const {}
221 impl ToNavFromAst for hir::Static {}
222 impl ToNavFromAst for hir::Struct {}
223 impl ToNavFromAst for hir::Enum {}
224 impl ToNavFromAst for hir::EnumVariant {}
225 impl ToNavFromAst for hir::Union {}
226 impl ToNavFromAst for hir::TypeAlias {}
227 impl ToNavFromAst for hir::Trait {}
231 D: HasSource + ToNavFromAst + Copy,
232 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
234 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
235 let src = self.source(db);
237 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
238 res.docs = src.value.doc_comment_text();
239 res.description = src.value.short_label();
244 impl ToNav for hir::Module {
245 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
246 let src = self.definition_source(db);
247 let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
248 let (syntax, focus) = match &src.value {
249 ModuleSource::SourceFile(node) => (node.syntax(), None),
250 ModuleSource::Module(node) => {
251 (node.syntax(), node.name().map(|it| it.syntax().text_range()))
254 let frange = original_range(db, src.with_value(syntax));
255 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind())
259 impl ToNav for hir::ImplDef {
260 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
261 let src = self.source(db);
262 let frange = if let Some(item) = self.is_builtin_derive(db) {
263 original_range(db, item.syntax())
265 original_range(db, src.as_ref().map(|it| it.syntax()))
268 NavigationTarget::from_syntax(
273 src.value.syntax().kind(),
278 impl ToNav for hir::Field {
279 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
280 let src = self.source(db);
283 FieldSource::Named(it) => {
284 let mut res = NavigationTarget::from_named(db, src.with_value(it));
285 res.docs = it.doc_comment_text();
286 res.description = it.short_label();
289 FieldSource::Pos(it) => {
290 let frange = original_range(db, src.with_value(it.syntax()));
291 NavigationTarget::from_syntax(
303 impl ToNav for hir::MacroDef {
304 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
305 let src = self.source(db);
306 log::debug!("nav target {:#?}", src.value.syntax());
308 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
309 res.docs = src.value.doc_comment_text();
314 impl ToNav for hir::Adt {
315 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
317 hir::Adt::Struct(it) => it.to_nav(db),
318 hir::Adt::Union(it) => it.to_nav(db),
319 hir::Adt::Enum(it) => it.to_nav(db),
324 impl ToNav for hir::AssocItem {
325 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
327 AssocItem::Function(it) => it.to_nav(db),
328 AssocItem::Const(it) => it.to_nav(db),
329 AssocItem::TypeAlias(it) => it.to_nav(db),
334 impl ToNav for hir::Local {
335 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
336 let src = self.source(db);
337 let node = match &src.value {
338 Either::Left(bind_pat) => {
339 bind_pat.name().map_or_else(|| bind_pat.syntax().clone(), |it| it.syntax().clone())
341 Either::Right(it) => it.syntax().clone(),
343 let full_range = original_range(db, src.with_value(&node));
344 let name = match self.name(db) {
345 Some(it) => it.to_string().into(),
349 file_id: full_range.file_id,
352 full_range: full_range.range,
354 container_name: None,
361 impl ToNav for hir::TypeParam {
362 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
363 let src = self.source(db);
364 let full_range = match &src.value {
365 Either::Left(it) => it.syntax().text_range(),
366 Either::Right(it) => it.syntax().text_range(),
368 let focus_range = match &src.value {
369 Either::Left(_) => None,
370 Either::Right(it) => it.name().map(|it| it.syntax().text_range()),
373 file_id: src.file_id.original_file(db),
374 name: self.name(db).to_string().into(),
378 container_name: None,
385 pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
386 let parse = db.parse(symbol.file_id);
387 let node = symbol.ptr.to_node(parse.tree().syntax());
391 ast::FnDef(it) => it.doc_comment_text(),
392 ast::StructDef(it) => it.doc_comment_text(),
393 ast::EnumDef(it) => it.doc_comment_text(),
394 ast::TraitDef(it) => it.doc_comment_text(),
395 ast::Module(it) => it.doc_comment_text(),
396 ast::TypeAliasDef(it) => it.doc_comment_text(),
397 ast::ConstDef(it) => it.doc_comment_text(),
398 ast::StaticDef(it) => it.doc_comment_text(),
399 ast::RecordFieldDef(it) => it.doc_comment_text(),
400 ast::EnumVariant(it) => it.doc_comment_text(),
401 ast::MacroCall(it) => it.doc_comment_text(),
407 /// Get a description of a symbol.
409 /// e.g. `struct Name`, `enum Name`, `fn Name`
410 pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
411 let parse = db.parse(symbol.file_id);
412 let node = symbol.ptr.to_node(parse.tree().syntax());
416 ast::FnDef(it) => it.short_label(),
417 ast::StructDef(it) => it.short_label(),
418 ast::EnumDef(it) => it.short_label(),
419 ast::TraitDef(it) => it.short_label(),
420 ast::Module(it) => it.short_label(),
421 ast::TypeAliasDef(it) => it.short_label(),
422 ast::ConstDef(it) => it.short_label(),
423 ast::StaticDef(it) => it.short_label(),
424 ast::RecordFieldDef(it) => it.short_label(),
425 ast::EnumVariant(it) => it.short_label(),