diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 9332cfc0674..c7a8463a3c4 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -1,12 +1,15 @@ //! Attributes & documentation for hir types. + +use either::Either; use hir_def::{ attr::{AttrsWithOwner, Documentation}, + item_scope::ItemInNs, path::ModPath, per_ns::PerNs, resolver::HasResolver, AttrDefId, GenericParamId, ModuleDefId, }; -use hir_expand::hygiene::Hygiene; +use hir_expand::{hygiene::Hygiene, MacroDefId}; use hir_ty::db::HirDatabase; use syntax::ast; @@ -23,7 +26,7 @@ fn resolve_doc_path( db: &dyn HirDatabase, link: &str, ns: Option, - ) -> Option; + ) -> Option>; } #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] @@ -44,9 +47,9 @@ fn docs(self, db: &dyn HirDatabase) -> Option { let def = AttrDefId::$def_id(self.into()); db.attrs(def).docs() } - fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option) -> Option { + fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option) -> Option> { let def = AttrDefId::$def_id(self.into()); - resolve_doc_path(db, def, link, ns).map(ModuleDef::from) + resolve_doc_path(db, def, link, ns).map(|it| it.map_left(ModuleDef::from).map_right(MacroDef::from)) } } )*}; @@ -76,7 +79,7 @@ fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { fn docs(self, db: &dyn HirDatabase) -> Option { $enum::$variant(self).docs(db) } - fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option) -> Option { + fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option) -> Option> { $enum::$variant(self).resolve_doc_path(db, link, ns) } } @@ -108,7 +111,7 @@ fn resolve_doc_path( db: &dyn HirDatabase, link: &str, ns: Option, - ) -> Option { + ) -> Option> { match self { AssocItem::Function(it) => it.resolve_doc_path(db, link, ns), AssocItem::Const(it) => it.resolve_doc_path(db, link, ns), @@ -122,7 +125,7 @@ fn resolve_doc_path( def: AttrDefId, link: &str, ns: Option, -) -> Option { +) -> Option> { let resolver = match def { AttrDefId::ModuleId(it) => it.resolver(db.upcast()), AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()), @@ -140,6 +143,7 @@ fn resolve_doc_path( GenericParamId::ConstParamId(it) => it.parent, } .resolver(db.upcast()), + // FIXME AttrDefId::MacroDefId(_) => return None, }; let path = ast::Path::parse(link).ok()?; @@ -151,9 +155,13 @@ fn resolve_doc_path( resolved }; match ns { - Some(Namespace::Types) => resolved.take_types(), - Some(Namespace::Values) => resolved.take_values(), - Some(Namespace::Macros) => None, - None => resolved.iter_items().find_map(|it| it.as_module_def_id()), + Some(Namespace::Types) => resolved.take_types().map(Either::Left), + Some(Namespace::Values) => resolved.take_values().map(Either::Left), + Some(Namespace::Macros) => resolved.take_macros().map(Either::Right), + None => resolved.iter_items().next().map(|it| match it { + ItemInNs::Types(it) => Either::Left(it), + ItemInNs::Values(it) => Either::Left(it), + ItemInNs::Macros(it) => Either::Right(it), + }), } } diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index 455b3245691..82c53201408 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs @@ -75,6 +75,15 @@ pub(crate) trait TryToNav { fn try_to_nav(&self, db: &RootDatabase) -> Option; } +impl TryToNav for Either { + fn try_to_nav(&self, db: &RootDatabase) -> Option { + match self { + Either::Left(it) => it.try_to_nav(db), + Either::Right(it) => it.try_to_nav(db), + } + } +} + impl NavigationTarget { pub fn focus_or_full_range(&self) -> TextRange { self.focus_range.unwrap_or(self.full_range) diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 40e570bbbae..36c13fe54ec 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -4,13 +4,15 @@ use std::convert::{TryFrom, TryInto}; +use either::Either; use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; use stdx::format_to; use url::Url; use hir::{ - db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasAttrs, ModuleDef, + db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasAttrs, MacroDef, + ModuleDef, }; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, @@ -47,7 +49,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin return rewritten; } if let Definition::ModuleDef(def) = definition { - if let Some(target) = rewrite_url_link(db, def, target) { + if let Some(target) = rewrite_url_link(db, Either::Left(def), target) { return (target, title.to_string()); } } @@ -169,7 +171,7 @@ pub(crate) fn resolve_doc_path_for_def( def: Definition, link: &str, ns: Option, -) -> Option { +) -> Option> { match def { Definition::ModuleDef(def) => match def { hir::ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), @@ -243,9 +245,9 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option { AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(), }; let frag = get_assoc_item_fragment(db, assoc_item)?; - (def, Some(frag)) + (Either::Left(def), Some(frag)) } else { - (def, None) + (Either::Left(def), None) } } Definition::Field(field) => { @@ -254,10 +256,9 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option { hir::VariantDef::Union(it) => it.into(), hir::VariantDef::Variant(it) => it.into(), }; - (def, Some(format!("structfield.{}", field.name(db)))) + (Either::Left(def), Some(format!("structfield.{}", field.name(db)))) } - // FIXME macros - Definition::Macro(_) => return None, + Definition::Macro(makro) => (Either::Right(makro), None), // FIXME impls Definition::SelfType(_) => return None, Definition::Local(_) | Definition::GenericParam(_) | Definition::Label(_) => return None, @@ -270,7 +271,7 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option { url = url.join(&path).ok()?; } - url = url.join(&get_symbol_filename(db, &target)?).ok()?; + url = url.join(&get_symbol_filename(db, target)?).ok()?; url.set_fragment(frag.as_deref()); Some(url.into()) @@ -292,24 +293,29 @@ fn rewrite_intra_doc_link( url = url.join(&path).ok()?; } - let (resolved, frag) = if let Some(assoc_item) = resolved.as_assoc_item(db) { - let resolved = match assoc_item.container(db) { - AssocItemContainer::Trait(t) => t.into(), - AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(), + let (resolved, frag) = + if let Some(assoc_item) = resolved.left().and_then(|it| it.as_assoc_item(db)) { + let resolved = match assoc_item.container(db) { + AssocItemContainer::Trait(t) => t.into(), + AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(), + }; + let frag = get_assoc_item_fragment(db, assoc_item)?; + (Either::Left(resolved), Some(frag)) + } else { + (resolved, None) }; - let frag = get_assoc_item_fragment(db, assoc_item)?; - (resolved, Some(frag)) - } else { - (resolved, None) - }; - url = url.join(&get_symbol_filename(db, &resolved)?).ok()?; + url = url.join(&get_symbol_filename(db, resolved)?).ok()?; url.set_fragment(frag.as_deref()); Some((url.into(), strip_prefixes_suffixes(title).to_string())) } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). -fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option { +fn rewrite_url_link( + db: &RootDatabase, + def: Either, + target: &str, +) -> Option { if !(target.contains('#') || target.contains(".html")) { return None; } @@ -321,25 +327,35 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option Option { +fn crate_of_def(db: &RootDatabase, def: Either) -> Option { let krate = match def { // Definition::module gives back the parent module, we don't want that as it fails for root modules - ModuleDef::Module(module) => module.krate(), - _ => def.module(db)?.krate(), + Either::Left(ModuleDef::Module(module)) => module.krate(), + Either::Left(def) => def.module(db)?.krate(), + Either::Right(def) => def.module(db)?.krate(), }; Some(krate) } -fn mod_path_of_def(db: &RootDatabase, def: ModuleDef) -> Option { - def.canonical_module_path(db).map(|it| { - let mut path = String::new(); - it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name)); - path - }) +fn mod_path_of_def(db: &RootDatabase, def: Either) -> Option { + match def { + Either::Left(def) => def.canonical_module_path(db).map(|it| { + let mut path = String::new(); + it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name)); + path + }), + Either::Right(def) => { + def.module(db).map(|it| it.path_to_root(db).into_iter().rev()).map(|it| { + let mut path = String::new(); + it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name)); + path + }) + } + } } /// Rewrites a markdown document, applying 'callback' to each link. @@ -405,27 +421,34 @@ fn get_doc_base_url(db: &RootDatabase, krate: &Crate) -> Option { /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next /// ^^^^^^^^^^^^^^^^^^^ /// ``` -fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { - Some(match definition { - ModuleDef::Adt(adt) => match adt { - Adt::Struct(s) => format!("struct.{}.html", s.name(db)), - Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)), +fn get_symbol_filename( + db: &dyn HirDatabase, + definition: Either, +) -> Option { + let res = match definition { + Either::Left(definition) => match definition { + ModuleDef::Adt(adt) => match adt { + Adt::Struct(s) => format!("struct.{}.html", s.name(db)), + Adt::Enum(e) => format!("enum.{}.html", e.name(db)), + Adt::Union(u) => format!("union.{}.html", u.name(db)), + }, + ModuleDef::Module(m) => match m.name(db) { + Some(name) => format!("{}/index.html", name), + None => String::from("index.html"), + }, + ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), + ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.name()), + ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), + ModuleDef::Variant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } + ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), }, - ModuleDef::Module(m) => match m.name(db) { - Some(name) => format!("{}/index.html", name), - None => String::from("index.html"), - }, - ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), - ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), - ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.name()), - ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::Variant(ev) => { - format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) - } - ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), - }) + Either::Right(mac) => format!("macro.{}.html", mac.name(db)?), + }; + Some(res) } /// Get the fragment required to link to a specific field, method, associated type, or associated constant. diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index a325acff982..35ba266f567 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -150,7 +150,10 @@ pub(crate) fn hover( (file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns)) })?; range = Some(idl_range); - resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef) + Some(match resolve_doc_path_for_def(db,def, &link,ns)? { + Either::Left(it) => Definition::ModuleDef(it), + Either::Right(it) => Definition::Macro(it), + }) } else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) { if let res@Some(_) = try_hover_for_lint(&attr, &token) { return res; diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 7c9fd46fc57..7553852659b 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -238,19 +238,22 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option HlTag { +fn module_def_to_hl_tag(def: Either) -> HlTag { let symbol = match def { - hir::ModuleDef::Module(_) => SymbolKind::Module, - hir::ModuleDef::Function(_) => SymbolKind::Function, - hir::ModuleDef::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct, - hir::ModuleDef::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum, - hir::ModuleDef::Adt(hir::Adt::Union(_)) => SymbolKind::Union, - hir::ModuleDef::Variant(_) => SymbolKind::Variant, - hir::ModuleDef::Const(_) => SymbolKind::Const, - hir::ModuleDef::Static(_) => SymbolKind::Static, - hir::ModuleDef::Trait(_) => SymbolKind::Trait, - hir::ModuleDef::TypeAlias(_) => SymbolKind::TypeAlias, - hir::ModuleDef::BuiltinType(_) => return HlTag::BuiltinType, + Either::Left(def) => match def { + hir::ModuleDef::Module(_) => SymbolKind::Module, + hir::ModuleDef::Function(_) => SymbolKind::Function, + hir::ModuleDef::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct, + hir::ModuleDef::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum, + hir::ModuleDef::Adt(hir::Adt::Union(_)) => SymbolKind::Union, + hir::ModuleDef::Variant(_) => SymbolKind::Variant, + hir::ModuleDef::Const(_) => SymbolKind::Const, + hir::ModuleDef::Static(_) => SymbolKind::Static, + hir::ModuleDef::Trait(_) => SymbolKind::Trait, + hir::ModuleDef::TypeAlias(_) => SymbolKind::TypeAlias, + hir::ModuleDef::BuiltinType(_) => return HlTag::BuiltinType, + }, + Either::Right(_) => SymbolKind::Macro, }; HlTag::Symbol(symbol) } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 4ae702ba5d5..7f3362f2d58 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -114,7 +114,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd /// [`Foo`](Foo) is a struct /// This function is > [`all_the_links`](all_the_links) < -/// [`noop`](noop) is a macro below +/// [`noop`](noop) is a macro below /// [`Item`] is a struct in the module [`module`] /// /// [`Item`]: module::Item