mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-28 10:39:45 +00:00
Merge pull request #19507 from Hmikihiro/fix_module_doc_links
fix: resolve doc path from parent module if outer comments exist on module
This commit is contained in:
commit
8b624868e4
11 changed files with 549 additions and 150 deletions
|
|
@ -1,4 +1,5 @@
|
|||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||
use std::iter;
|
||||
use std::{borrow::Cow, fmt, ops};
|
||||
|
||||
use base_db::Crate;
|
||||
|
|
@ -122,16 +123,15 @@ impl RawAttrs {
|
|||
(None, entries @ Some(_)) => Self { entries },
|
||||
(Some(entries), None) => Self { entries: Some(entries.clone()) },
|
||||
(Some(a), Some(b)) => {
|
||||
let last_ast_index = a.slice.last().map_or(0, |it| it.id.ast_index() + 1) as u32;
|
||||
let last_ast_index = a.slice.last().map_or(0, |it| it.id.ast_index() + 1);
|
||||
let items = a
|
||||
.slice
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(b.slice.iter().map(|it| {
|
||||
let mut it = it.clone();
|
||||
it.id.id = (it.id.ast_index() as u32 + last_ast_index)
|
||||
| ((it.id.cfg_attr_index().unwrap_or(0) as u32)
|
||||
<< AttrId::AST_INDEX_BITS);
|
||||
let id = it.id.ast_index() + last_ast_index;
|
||||
it.id = AttrId::new(id, it.id.is_inner_attr());
|
||||
it
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -175,25 +175,20 @@ pub struct AttrId {
|
|||
// FIXME: This only handles a single level of cfg_attr nesting
|
||||
// that is `#[cfg_attr(all(), cfg_attr(all(), cfg(any())))]` breaks again
|
||||
impl AttrId {
|
||||
const CFG_ATTR_BITS: usize = 7;
|
||||
const AST_INDEX_MASK: usize = 0x00FF_FFFF;
|
||||
const AST_INDEX_BITS: usize = Self::AST_INDEX_MASK.count_ones() as usize;
|
||||
const CFG_ATTR_SET_BITS: u32 = 1 << 31;
|
||||
const INNER_ATTR_SET_BIT: u32 = 1 << 31;
|
||||
|
||||
pub fn new(id: usize, is_inner: bool) -> Self {
|
||||
assert!(id <= !Self::INNER_ATTR_SET_BIT as usize);
|
||||
let id = id as u32;
|
||||
Self { id: if is_inner { id | Self::INNER_ATTR_SET_BIT } else { id } }
|
||||
}
|
||||
|
||||
pub fn ast_index(&self) -> usize {
|
||||
self.id as usize & Self::AST_INDEX_MASK
|
||||
(self.id & !Self::INNER_ATTR_SET_BIT) as usize
|
||||
}
|
||||
|
||||
pub fn cfg_attr_index(&self) -> Option<usize> {
|
||||
if self.id & Self::CFG_ATTR_SET_BITS == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.id as usize >> Self::AST_INDEX_BITS)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cfg_attr(self, idx: usize) -> AttrId {
|
||||
AttrId { id: self.id | ((idx as u32) << Self::AST_INDEX_BITS) | Self::CFG_ATTR_SET_BITS }
|
||||
pub fn is_inner_attr(&self) -> bool {
|
||||
self.id & Self::INNER_ATTR_SET_BIT != 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,10 +328,7 @@ impl Attr {
|
|||
None => return smallvec![self.clone()],
|
||||
};
|
||||
let index = self.id;
|
||||
let attrs = parts
|
||||
.enumerate()
|
||||
.take(1 << AttrId::CFG_ATTR_BITS)
|
||||
.filter_map(|(idx, attr)| Attr::from_tt(db, attr, index.with_cfg_attr(idx)));
|
||||
let attrs = parts.filter_map(|attr| Attr::from_tt(db, attr, index));
|
||||
|
||||
let cfg = TopSubtree::from_token_trees(subtree.top_subtree().delimiter, cfg);
|
||||
let cfg = CfgExpr::parse(&cfg);
|
||||
|
|
@ -467,13 +459,18 @@ fn unescape(s: &str) -> Option<Cow<'_, str>> {
|
|||
pub fn collect_attrs(
|
||||
owner: &dyn ast::HasAttrs,
|
||||
) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
|
||||
let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten();
|
||||
let outer_attrs =
|
||||
ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el {
|
||||
let inner_attrs =
|
||||
inner_attributes(owner.syntax()).into_iter().flatten().zip(iter::repeat(true));
|
||||
let outer_attrs = ast::AttrDocCommentIter::from_syntax_node(owner.syntax())
|
||||
.filter(|el| match el {
|
||||
Either::Left(attr) => attr.kind().is_outer(),
|
||||
Either::Right(comment) => comment.is_outer(),
|
||||
});
|
||||
outer_attrs.chain(inner_attrs).enumerate().map(|(id, attr)| (AttrId { id: id as u32 }, attr))
|
||||
})
|
||||
.zip(iter::repeat(false));
|
||||
outer_attrs
|
||||
.chain(inner_attrs)
|
||||
.enumerate()
|
||||
.map(|(id, (attr, is_inner))| (AttrId::new(id, is_inner), attr))
|
||||
}
|
||||
|
||||
fn inner_attributes(
|
||||
|
|
|
|||
|
|
@ -105,11 +105,12 @@ impl HasAttrs for crate::Crate {
|
|||
/// Resolves the item `link` points to in the scope of `def`.
|
||||
pub fn resolve_doc_path_on(
|
||||
db: &dyn HirDatabase,
|
||||
def: impl HasAttrs,
|
||||
def: impl HasAttrs + Copy,
|
||||
link: &str,
|
||||
ns: Option<Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<DocLinkDef> {
|
||||
resolve_doc_path_on_(db, link, def.attr_id(), ns)
|
||||
resolve_doc_path_on_(db, link, def.attr_id(), ns, is_inner_doc)
|
||||
}
|
||||
|
||||
fn resolve_doc_path_on_(
|
||||
|
|
@ -117,9 +118,18 @@ fn resolve_doc_path_on_(
|
|||
link: &str,
|
||||
attr_id: AttrDefId,
|
||||
ns: Option<Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<DocLinkDef> {
|
||||
let resolver = match attr_id {
|
||||
AttrDefId::ModuleId(it) => it.resolver(db),
|
||||
AttrDefId::ModuleId(it) => {
|
||||
if is_inner_doc {
|
||||
it.resolver(db)
|
||||
} else if let Some(parent) = Module::from(it).parent(db) {
|
||||
parent.id.resolver(db)
|
||||
} else {
|
||||
it.resolver(db)
|
||||
}
|
||||
}
|
||||
AttrDefId::FieldId(it) => it.parent.resolver(db),
|
||||
AttrDefId::AdtId(it) => it.resolver(db),
|
||||
AttrDefId::FunctionId(it) => it.resolver(db),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
|
||||
|
||||
use crate::RootDatabase;
|
||||
use crate::documentation::{Documentation, HasDocs};
|
||||
use crate::documentation::{DocsRangeMap, Documentation, HasDocs};
|
||||
use crate::famous_defs::FamousDefs;
|
||||
use arrayvec::ArrayVec;
|
||||
use either::Either;
|
||||
|
|
@ -21,7 +21,7 @@ use hir::{
|
|||
use span::Edition;
|
||||
use stdx::{format_to, impl_from};
|
||||
use syntax::{
|
||||
SyntaxKind, SyntaxNode, SyntaxToken,
|
||||
SyntaxKind, SyntaxNode, SyntaxToken, TextSize,
|
||||
ast::{self, AstNode},
|
||||
match_ast,
|
||||
};
|
||||
|
|
@ -210,29 +210,40 @@ impl Definition {
|
|||
famous_defs: Option<&FamousDefs<'_, '_>>,
|
||||
display_target: DisplayTarget,
|
||||
) -> Option<Documentation> {
|
||||
self.docs_with_rangemap(db, famous_defs, display_target).map(|(docs, _)| docs)
|
||||
}
|
||||
|
||||
pub fn docs_with_rangemap(
|
||||
&self,
|
||||
db: &RootDatabase,
|
||||
famous_defs: Option<&FamousDefs<'_, '_>>,
|
||||
display_target: DisplayTarget,
|
||||
) -> Option<(Documentation, Option<DocsRangeMap>)> {
|
||||
let docs = match self {
|
||||
Definition::Macro(it) => it.docs(db),
|
||||
Definition::Field(it) => it.docs(db),
|
||||
Definition::Module(it) => it.docs(db),
|
||||
Definition::Crate(it) => it.docs(db),
|
||||
Definition::Function(it) => it.docs(db),
|
||||
Definition::Adt(it) => it.docs(db),
|
||||
Definition::Variant(it) => it.docs(db),
|
||||
Definition::Const(it) => it.docs(db),
|
||||
Definition::Static(it) => it.docs(db),
|
||||
Definition::Trait(it) => it.docs(db),
|
||||
Definition::TraitAlias(it) => it.docs(db),
|
||||
Definition::Macro(it) => it.docs_with_rangemap(db),
|
||||
Definition::Field(it) => it.docs_with_rangemap(db),
|
||||
Definition::Module(it) => it.docs_with_rangemap(db),
|
||||
Definition::Crate(it) => it.docs_with_rangemap(db),
|
||||
Definition::Function(it) => it.docs_with_rangemap(db),
|
||||
Definition::Adt(it) => it.docs_with_rangemap(db),
|
||||
Definition::Variant(it) => it.docs_with_rangemap(db),
|
||||
Definition::Const(it) => it.docs_with_rangemap(db),
|
||||
Definition::Static(it) => it.docs_with_rangemap(db),
|
||||
Definition::Trait(it) => it.docs_with_rangemap(db),
|
||||
Definition::TraitAlias(it) => it.docs_with_rangemap(db),
|
||||
Definition::TypeAlias(it) => {
|
||||
it.docs(db).or_else(|| {
|
||||
it.docs_with_rangemap(db).or_else(|| {
|
||||
// docs are missing, try to fall back to the docs of the aliased item.
|
||||
let adt = it.ty(db).as_adt()?;
|
||||
let docs = adt.docs(db)?;
|
||||
let docs = format!(
|
||||
"*This is the documentation for* `{}`\n\n{}",
|
||||
adt.display(db, display_target),
|
||||
docs.as_str()
|
||||
let (docs, range_map) = adt.docs_with_rangemap(db)?;
|
||||
let header_docs = format!(
|
||||
"*This is the documentation for* `{}`\n\n",
|
||||
adt.display(db, display_target)
|
||||
);
|
||||
Some(Documentation::new(docs))
|
||||
let offset = TextSize::new(header_docs.len() as u32);
|
||||
let range_map = range_map.shift_docstring_line_range(offset);
|
||||
let docs = header_docs + docs.as_str();
|
||||
Some((Documentation::new(docs), range_map))
|
||||
})
|
||||
}
|
||||
Definition::BuiltinType(it) => {
|
||||
|
|
@ -241,17 +252,17 @@ impl Definition {
|
|||
let primitive_mod =
|
||||
format!("prim_{}", it.name().display(fd.0.db, display_target.edition));
|
||||
let doc_owner = find_std_module(fd, &primitive_mod, display_target.edition)?;
|
||||
doc_owner.docs(fd.0.db)
|
||||
doc_owner.docs_with_rangemap(fd.0.db)
|
||||
})
|
||||
}
|
||||
Definition::BuiltinLifetime(StaticLifetime) => None,
|
||||
Definition::Local(_) => None,
|
||||
Definition::SelfType(impl_def) => {
|
||||
impl_def.self_ty(db).as_adt().map(|adt| adt.docs(db))?
|
||||
impl_def.self_ty(db).as_adt().map(|adt| adt.docs_with_rangemap(db))?
|
||||
}
|
||||
Definition::GenericParam(_) => None,
|
||||
Definition::Label(_) => None,
|
||||
Definition::ExternCrateDecl(it) => it.docs(db),
|
||||
Definition::ExternCrateDecl(it) => it.docs_with_rangemap(db),
|
||||
|
||||
Definition::BuiltinAttr(it) => {
|
||||
let name = it.name(db);
|
||||
|
|
@ -276,7 +287,8 @@ impl Definition {
|
|||
name_value_str
|
||||
);
|
||||
}
|
||||
Some(Documentation::new(docs.replace('*', "\\*")))
|
||||
|
||||
return Some((Documentation::new(docs.replace('*', "\\*")), None));
|
||||
}
|
||||
Definition::ToolModule(_) => None,
|
||||
Definition::DeriveHelper(_) => None,
|
||||
|
|
@ -291,8 +303,9 @@ impl Definition {
|
|||
let trait_ = assoc.implemented_trait(db)?;
|
||||
let name = Some(assoc.name(db)?);
|
||||
let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
|
||||
item.docs(db)
|
||||
item.docs_with_rangemap(db)
|
||||
})
|
||||
.map(|(docs, range_map)| (docs, Some(range_map)))
|
||||
}
|
||||
|
||||
pub fn label(&self, db: &RootDatabase, display_target: DisplayTarget) -> String {
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ impl From<Documentation> for String {
|
|||
|
||||
pub trait HasDocs: HasAttrs {
|
||||
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation>;
|
||||
fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)>;
|
||||
fn resolve_doc_path(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<hir::DocLinkDef>;
|
||||
}
|
||||
/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
|
||||
|
|
@ -53,7 +55,7 @@ pub struct DocsRangeMap {
|
|||
|
||||
impl DocsRangeMap {
|
||||
/// Maps a [`TextRange`] relative to the documentation string back to its AST range
|
||||
pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
|
||||
pub fn map(&self, range: TextRange) -> Option<(InFile<TextRange>, AttrId)> {
|
||||
let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
|
||||
let (line_docs_range, idx, original_line_src_range) = self.mapping[found];
|
||||
if !line_docs_range.contains_range(range) {
|
||||
|
|
@ -71,7 +73,7 @@ impl DocsRangeMap {
|
|||
text_range.end() + original_line_src_range.start() + relative_range.start(),
|
||||
string.syntax().text_range().len().min(range.len()),
|
||||
);
|
||||
Some(InFile { file_id, value: range })
|
||||
Some((InFile { file_id, value: range }, idx))
|
||||
}
|
||||
Either::Right(comment) => {
|
||||
let text_range = comment.syntax().text_range();
|
||||
|
|
@ -82,10 +84,22 @@ impl DocsRangeMap {
|
|||
+ relative_range.start(),
|
||||
text_range.len().min(range.len()),
|
||||
);
|
||||
Some(InFile { file_id, value: range })
|
||||
Some((InFile { file_id, value: range }, idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shift_docstring_line_range(self, offset: TextSize) -> DocsRangeMap {
|
||||
let mapping = self
|
||||
.mapping
|
||||
.into_iter()
|
||||
.map(|(buf_offset, id, base_offset)| {
|
||||
let buf_offset = buf_offset.checked_add(offset).unwrap();
|
||||
(buf_offset, id, base_offset)
|
||||
})
|
||||
.collect_vec();
|
||||
DocsRangeMap { source_map: self.source_map, mapping }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docs_with_rangemap(
|
||||
|
|
@ -161,13 +175,20 @@ macro_rules! impl_has_docs {
|
|||
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
|
||||
docs_from_attrs(&self.attrs(db)).map(Documentation)
|
||||
}
|
||||
fn docs_with_rangemap(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Option<(Documentation, DocsRangeMap)> {
|
||||
docs_with_rangemap(db, &self.attrs(db))
|
||||
}
|
||||
fn resolve_doc_path(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<hir::DocLinkDef> {
|
||||
resolve_doc_path_on(db, self, link, ns)
|
||||
resolve_doc_path_on(db, self, link, ns, is_inner_doc)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
|
@ -184,13 +205,21 @@ macro_rules! impl_has_docs_enum {
|
|||
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
|
||||
hir::$enum::$variant(self).docs(db)
|
||||
}
|
||||
|
||||
fn docs_with_rangemap(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Option<(Documentation, DocsRangeMap)> {
|
||||
hir::$enum::$variant(self).docs_with_rangemap(db)
|
||||
}
|
||||
fn resolve_doc_path(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<hir::DocLinkDef> {
|
||||
hir::$enum::$variant(self).resolve_doc_path(db, link, ns)
|
||||
hir::$enum::$variant(self).resolve_doc_path(db, link, ns, is_inner_doc)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
|
@ -207,16 +236,25 @@ impl HasDocs for hir::AssocItem {
|
|||
}
|
||||
}
|
||||
|
||||
fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> {
|
||||
match self {
|
||||
hir::AssocItem::Function(it) => it.docs_with_rangemap(db),
|
||||
hir::AssocItem::Const(it) => it.docs_with_rangemap(db),
|
||||
hir::AssocItem::TypeAlias(it) => it.docs_with_rangemap(db),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_doc_path(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<hir::DocLinkDef> {
|
||||
match self {
|
||||
hir::AssocItem::Function(it) => it.resolve_doc_path(db, link, ns),
|
||||
hir::AssocItem::Const(it) => it.resolve_doc_path(db, link, ns),
|
||||
hir::AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
|
||||
hir::AssocItem::Function(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
hir::AssocItem::Const(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
hir::AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,13 +276,36 @@ impl HasDocs for hir::ExternCrateDecl {
|
|||
}
|
||||
.map(Documentation::new)
|
||||
}
|
||||
|
||||
fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> {
|
||||
let crate_docs = docs_with_rangemap(db, &self.resolved_crate(db)?.root_module().attrs(db));
|
||||
let decl_docs = docs_with_rangemap(db, &self.attrs(db));
|
||||
match (decl_docs, crate_docs) {
|
||||
(None, None) => None,
|
||||
(Some(decl_docs), None) => Some(decl_docs),
|
||||
(None, Some(crate_docs)) => Some(crate_docs),
|
||||
(
|
||||
Some((Documentation(mut decl_docs), mut decl_range_map)),
|
||||
Some((Documentation(crate_docs), crate_range_map)),
|
||||
) => {
|
||||
decl_docs.push('\n');
|
||||
decl_docs.push('\n');
|
||||
let offset = TextSize::new(decl_docs.len() as u32);
|
||||
decl_docs += &crate_docs;
|
||||
let crate_range_map = crate_range_map.shift_docstring_line_range(offset);
|
||||
decl_range_map.mapping.extend(crate_range_map.mapping);
|
||||
Some((Documentation(decl_docs), decl_range_map))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn resolve_doc_path(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<hir::DocLinkDef> {
|
||||
resolve_doc_path_on(db, self, link, ns)
|
||||
resolve_doc_path_on(db, self, link, ns, is_inner_doc)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,21 @@ mod tests;
|
|||
|
||||
mod intra_doc_links;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
|
||||
use pulldown_cmark_to_cmark::{Options as CMarkOptions, cmark_resume_with_options};
|
||||
use stdx::format_to;
|
||||
use url::Url;
|
||||
|
||||
use hir::{Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs, db::HirDatabase, sym};
|
||||
use hir::{
|
||||
Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrsWithOwner, HasAttrs, db::HirDatabase, sym,
|
||||
};
|
||||
use ide_db::{
|
||||
RootDatabase,
|
||||
base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, RootQueryDb},
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
documentation::{Documentation, HasDocs, docs_with_rangemap},
|
||||
documentation::{DocsRangeMap, Documentation, HasDocs, docs_with_rangemap},
|
||||
helpers::pick_best_token,
|
||||
};
|
||||
use syntax::{
|
||||
|
|
@ -46,11 +50,17 @@ const MARKDOWN_OPTIONS: Options =
|
|||
Options::ENABLE_FOOTNOTES.union(Options::ENABLE_TABLES).union(Options::ENABLE_TASKLISTS);
|
||||
|
||||
/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
|
||||
pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Definition) -> String {
|
||||
pub(crate) fn rewrite_links(
|
||||
db: &RootDatabase,
|
||||
markdown: &str,
|
||||
definition: Definition,
|
||||
range_map: Option<DocsRangeMap>,
|
||||
) -> String {
|
||||
let mut cb = broken_link_clone_cb;
|
||||
let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb));
|
||||
let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb))
|
||||
.into_offset_iter();
|
||||
|
||||
let doc = map_links(doc, |target, title| {
|
||||
let doc = map_links(doc, |target, title, range| {
|
||||
// This check is imperfect, there's some overlap between valid intra-doc links
|
||||
// and valid URLs so we choose to be too eager to try to resolve what might be
|
||||
// a URL.
|
||||
|
|
@ -60,7 +70,16 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin
|
|||
// Two possibilities:
|
||||
// * path-based links: `../../module/struct.MyStruct.html`
|
||||
// * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
|
||||
if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title) {
|
||||
let text_range =
|
||||
TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
|
||||
let is_inner_doc = range_map
|
||||
.as_ref()
|
||||
.and_then(|range_map| range_map.map(text_range))
|
||||
.map(|(_, attr_id)| attr_id.is_inner_attr())
|
||||
.unwrap_or(false);
|
||||
if let Some((target, title)) =
|
||||
rewrite_intra_doc_link(db, definition, target, title, is_inner_doc)
|
||||
{
|
||||
(None, target, title)
|
||||
} else if let Some(target) = rewrite_url_link(db, definition, target) {
|
||||
(Some(LinkType::Inline), target, title.to_owned())
|
||||
|
|
@ -195,22 +214,23 @@ pub(crate) fn resolve_doc_path_for_def(
|
|||
def: Definition,
|
||||
link: &str,
|
||||
ns: Option<hir::Namespace>,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<Definition> {
|
||||
match def {
|
||||
Definition::Module(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Crate(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Function(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Adt(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Variant(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Const(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Static(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Trait(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::TraitAlias(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Field(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::SelfType(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::ExternCrateDecl(it) => it.resolve_doc_path(db, link, ns),
|
||||
Definition::Module(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Crate(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Function(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Adt(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Variant(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Const(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Static(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Trait(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::TraitAlias(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Macro(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::Field(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::SelfType(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::ExternCrateDecl(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
|
||||
Definition::BuiltinAttr(_)
|
||||
| Definition::BuiltinType(_)
|
||||
| Definition::BuiltinLifetime(_)
|
||||
|
|
@ -289,31 +309,58 @@ impl DocCommentToken {
|
|||
let relative_comment_offset = offset - original_start - prefix_len;
|
||||
|
||||
sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
|
||||
let (node, descended_prefix_len) = match_ast! {
|
||||
let (node, descended_prefix_len, is_inner) = match_ast!{
|
||||
match t {
|
||||
ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
|
||||
ast::String(string) => (t.parent_ancestors().skip_while(|n| n.kind() != ATTR).nth(1)?, string.open_quote_text_range()?.len()),
|
||||
ast::Comment(comment) => {
|
||||
(t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?, comment.is_inner())
|
||||
},
|
||||
ast::String(string) => {
|
||||
let attr = t.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||
let attr_is_inner = attr.excl_token().map(|excl| excl.kind() == BANG).unwrap_or(false);
|
||||
(attr.syntax().parent()?, string.open_quote_text_range()?.len(), attr_is_inner)
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
let token_start = t.text_range().start();
|
||||
let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len;
|
||||
|
||||
let (attributes, def) = doc_attributes(sema, &node)?;
|
||||
let (attributes, def) = Self::doc_attributes(sema, &node, is_inner)?;
|
||||
let (docs, doc_mapping) = docs_with_rangemap(sema.db, &attributes)?;
|
||||
let (in_expansion_range, link, ns) =
|
||||
let (in_expansion_range, link, ns, is_inner) =
|
||||
extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
|
||||
let mapped = doc_mapping.map(range)?;
|
||||
(mapped.value.contains(abs_in_expansion_offset)).then_some((mapped.value, link, ns))
|
||||
let (mapped, idx) = doc_mapping.map(range)?;
|
||||
(mapped.value.contains(abs_in_expansion_offset)).then_some((mapped.value, link, ns, idx.is_inner_attr()))
|
||||
})?;
|
||||
// get the relative range to the doc/attribute in the expansion
|
||||
let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
|
||||
// Apply relative range to the original input comment
|
||||
let absolute_range = in_expansion_relative_range + original_start + prefix_len;
|
||||
let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?;
|
||||
let def = resolve_doc_path_for_def(sema.db, def, &link, ns, is_inner)?;
|
||||
cb(def, node, absolute_range)
|
||||
})
|
||||
}
|
||||
|
||||
/// When we hover a inner doc item, this find a attached definition.
|
||||
/// ```
|
||||
/// // node == ITEM_LIST
|
||||
/// // node.parent == EXPR_BLOCK
|
||||
/// // node.parent().parent() == FN
|
||||
/// fn f() {
|
||||
/// //! [`S$0`]
|
||||
/// }
|
||||
/// ```
|
||||
fn doc_attributes(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
node: &SyntaxNode,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<(AttrsWithOwner, Definition)> {
|
||||
if is_inner_doc && node.kind() != SOURCE_FILE {
|
||||
let parent = node.parent()?;
|
||||
doc_attributes(sema, &parent).or(doc_attributes(sema, &parent.parent()?))
|
||||
} else {
|
||||
doc_attributes(sema, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
|
||||
|
|
@ -369,6 +416,7 @@ fn rewrite_intra_doc_link(
|
|||
def: Definition,
|
||||
target: &str,
|
||||
title: &str,
|
||||
is_inner_doc: bool,
|
||||
) -> Option<(String, String)> {
|
||||
let (link, ns) = parse_intra_doc_link(target);
|
||||
|
||||
|
|
@ -377,7 +425,7 @@ fn rewrite_intra_doc_link(
|
|||
None => (link, None),
|
||||
};
|
||||
|
||||
let resolved = resolve_doc_path_for_def(db, def, link, ns)?;
|
||||
let resolved = resolve_doc_path_for_def(db, def, link, ns, is_inner_doc)?;
|
||||
let mut url = get_doc_base_urls(db, resolved, None, None).0?;
|
||||
|
||||
let (_, file, frag) = filename_and_frag_for_def(db, resolved)?;
|
||||
|
|
@ -421,8 +469,8 @@ fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option<String> {
|
|||
|
||||
/// Rewrites a markdown document, applying 'callback' to each link.
|
||||
fn map_links<'e>(
|
||||
events: impl Iterator<Item = Event<'e>>,
|
||||
callback: impl Fn(&str, &str) -> (Option<LinkType>, String, String),
|
||||
events: impl Iterator<Item = (Event<'e>, Range<usize>)>,
|
||||
callback: impl Fn(&str, &str, Range<usize>) -> (Option<LinkType>, String, String),
|
||||
) -> impl Iterator<Item = Event<'e>> {
|
||||
let mut in_link = false;
|
||||
// holds the origin link target on start event and the rewritten one on end event
|
||||
|
|
@ -432,7 +480,7 @@ fn map_links<'e>(
|
|||
// `Shortcut` type parsed from Start/End tags doesn't make sense for url links
|
||||
let mut end_link_type: Option<LinkType> = None;
|
||||
|
||||
events.map(move |evt| match evt {
|
||||
events.map(move |(evt, range)| match evt {
|
||||
Event::Start(Tag::Link(link_type, ref target, _)) => {
|
||||
in_link = true;
|
||||
end_link_target = Some(target.clone());
|
||||
|
|
@ -449,7 +497,7 @@ fn map_links<'e>(
|
|||
}
|
||||
Event::Text(s) if in_link => {
|
||||
let (link_type, link_target_s, link_name) =
|
||||
callback(&end_link_target.take().unwrap(), &s);
|
||||
callback(&end_link_target.take().unwrap(), &s, range);
|
||||
end_link_target = Some(CowStr::Boxed(link_target_s.into()));
|
||||
if !matches!(end_link_type, Some(LinkType::Autolink)) {
|
||||
end_link_type = link_type;
|
||||
|
|
@ -458,7 +506,7 @@ fn map_links<'e>(
|
|||
}
|
||||
Event::Code(s) if in_link => {
|
||||
let (link_type, link_target_s, link_name) =
|
||||
callback(&end_link_target.take().unwrap(), &s);
|
||||
callback(&end_link_target.take().unwrap(), &s, range);
|
||||
end_link_target = Some(CowStr::Boxed(link_target_s.into()));
|
||||
if !matches!(end_link_type, Some(LinkType::Autolink)) {
|
||||
end_link_type = link_type;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use hir::Semantics;
|
|||
use ide_db::{
|
||||
FilePosition, FileRange, RootDatabase,
|
||||
defs::Definition,
|
||||
documentation::{Documentation, HasDocs},
|
||||
documentation::{DocsRangeMap, Documentation, HasDocs},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use syntax::{AstNode, SyntaxNode, ast, match_ast};
|
||||
|
|
@ -45,8 +45,8 @@ fn check_external_docs(
|
|||
fn check_rewrite(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
|
||||
let (analysis, position) = fixture::position(ra_fixture);
|
||||
let sema = &Semantics::new(&analysis.db);
|
||||
let (cursor_def, docs) = def_under_cursor(sema, &position);
|
||||
let res = rewrite_links(sema.db, docs.as_str(), cursor_def);
|
||||
let (cursor_def, docs, range) = def_under_cursor(sema, &position);
|
||||
let res = rewrite_links(sema.db, docs.as_str(), cursor_def, Some(range));
|
||||
expect.assert_eq(&res)
|
||||
}
|
||||
|
||||
|
|
@ -56,12 +56,14 @@ fn check_doc_links(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
|
|||
let (analysis, position, mut expected) = fixture::annotations(ra_fixture);
|
||||
expected.sort_by_key(key_fn);
|
||||
let sema = &Semantics::new(&analysis.db);
|
||||
let (cursor_def, docs) = def_under_cursor(sema, &position);
|
||||
let (cursor_def, docs, range) = def_under_cursor(sema, &position);
|
||||
let defs = extract_definitions_from_docs(&docs);
|
||||
let actual: Vec<_> = defs
|
||||
.into_iter()
|
||||
.flat_map(|(_, link, ns)| {
|
||||
let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns)
|
||||
.flat_map(|(text_range, link, ns)| {
|
||||
let attr = range.map(text_range);
|
||||
let is_inner_attr = attr.map(|(_file, attr)| attr.is_inner_attr()).unwrap_or(false);
|
||||
let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns, is_inner_attr)
|
||||
.unwrap_or_else(|| panic!("Failed to resolve {link}"));
|
||||
def.try_to_nav(sema.db).unwrap().into_iter().zip(iter::repeat(link))
|
||||
})
|
||||
|
|
@ -78,7 +80,7 @@ fn check_doc_links(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
|
|||
fn def_under_cursor(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
position: &FilePosition,
|
||||
) -> (Definition, Documentation) {
|
||||
) -> (Definition, Documentation, DocsRangeMap) {
|
||||
let (docs, def) = sema
|
||||
.parse_guess_edition(position.file_id)
|
||||
.syntax()
|
||||
|
|
@ -89,31 +91,31 @@ fn def_under_cursor(
|
|||
.find_map(|it| node_to_def(sema, &it))
|
||||
.expect("no def found")
|
||||
.unwrap();
|
||||
let docs = docs.expect("no docs found for cursor def");
|
||||
(def, docs)
|
||||
let (docs, range) = docs.expect("no docs found for cursor def");
|
||||
(def, docs, range)
|
||||
}
|
||||
|
||||
fn node_to_def(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
node: &SyntaxNode,
|
||||
) -> Option<Option<(Option<Documentation>, Definition)>> {
|
||||
) -> Option<Option<(Option<(Documentation, DocsRangeMap)>, Definition)>> {
|
||||
Some(match_ast! {
|
||||
match node {
|
||||
ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
|
||||
ast::Module(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
|
||||
ast::Fn(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Function(def))),
|
||||
ast::Struct(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
|
||||
ast::Union(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
|
||||
ast::Enum(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
|
||||
ast::Variant(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Variant(def))),
|
||||
ast::Trait(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Trait(def))),
|
||||
ast::Static(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Static(def))),
|
||||
ast::Const(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Const(def))),
|
||||
ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::TypeAlias(def))),
|
||||
ast::Impl(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::SelfType(def))),
|
||||
ast::RecordField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
|
||||
ast::TupleField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
|
||||
ast::Macro(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Macro(def))),
|
||||
ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Module(def))),
|
||||
ast::Module(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Module(def))),
|
||||
ast::Fn(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Function(def))),
|
||||
ast::Struct(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
|
||||
ast::Union(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Adt(hir::Adt::Union(def)))),
|
||||
ast::Enum(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
|
||||
ast::Variant(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Variant(def))),
|
||||
ast::Trait(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Trait(def))),
|
||||
ast::Static(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Static(def))),
|
||||
ast::Const(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Const(def))),
|
||||
ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::TypeAlias(def))),
|
||||
ast::Impl(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::SelfType(def))),
|
||||
ast::RecordField(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Field(def))),
|
||||
ast::TupleField(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Field(def))),
|
||||
ast::Macro(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Macro(def))),
|
||||
// ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
|
||||
_ => return None,
|
||||
}
|
||||
|
|
@ -575,6 +577,40 @@ struct S$0(i32);
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doc_links_module() {
|
||||
check_doc_links(
|
||||
r#"
|
||||
/// [`M`]
|
||||
/// [`M::f`]
|
||||
mod M$0 {
|
||||
//^ M
|
||||
#![doc = "inner_item[`S`]"]
|
||||
|
||||
pub fn f() {}
|
||||
//^ M::f
|
||||
pub struct S;
|
||||
//^ S
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_doc_links(
|
||||
r#"
|
||||
mod M$0 {
|
||||
//^ super::M
|
||||
//! [`super::M`]
|
||||
//! [`super::M::f`]
|
||||
//! [`super::M::S`]
|
||||
pub fn f() {}
|
||||
//^ super::M::f
|
||||
pub struct S;
|
||||
//^ super::M::S
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_html_root_url() {
|
||||
check_rewrite(
|
||||
|
|
@ -690,6 +726,29 @@ fn rewrite_intra_doc_link_with_anchor() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_module() {
|
||||
check_rewrite(
|
||||
r#"
|
||||
//- /main.rs crate:foo
|
||||
/// [Foo]
|
||||
pub mod $0Foo{
|
||||
};
|
||||
"#,
|
||||
expect"#]],
|
||||
);
|
||||
|
||||
check_rewrite(
|
||||
r#"
|
||||
//- /main.rs crate:foo
|
||||
pub mod $0Foo{
|
||||
//! [super::Foo]
|
||||
};
|
||||
"#,
|
||||
expect"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_intra_doc_link_to_associated_item() {
|
||||
check_rewrite(
|
||||
|
|
|
|||
|
|
@ -1922,6 +1922,74 @@ pub fn foo() { }
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_intra_doc_link_outer_same_file() {
|
||||
check(
|
||||
r#"
|
||||
/// [`S$0`]
|
||||
mod m {
|
||||
//! [`super::S`]
|
||||
}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
/// [`S$0`]
|
||||
mod m {}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
/// [`S$0`]
|
||||
fn f() {
|
||||
//! [`S`]
|
||||
}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_intra_doc_link_inner_same_file() {
|
||||
check(
|
||||
r#"
|
||||
/// [`S`]
|
||||
mod m {
|
||||
//! [`super::S$0`]
|
||||
}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
mod m {
|
||||
//! [`super::S$0`]
|
||||
}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
fn f() {
|
||||
//! [`S$0`]
|
||||
}
|
||||
struct S;
|
||||
//^
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_intra_doc_link_inner() {
|
||||
check(
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ pub(crate) fn hover_for_definition(
|
|||
let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
|
||||
let subst_types = subst.map(|subst| subst.types(db));
|
||||
|
||||
let markup = render::definition(
|
||||
let (markup, range_map) = render::definition(
|
||||
sema.db,
|
||||
def,
|
||||
famous_defs.as_ref(),
|
||||
|
|
@ -469,7 +469,7 @@ pub(crate) fn hover_for_definition(
|
|||
display_target,
|
||||
);
|
||||
HoverResult {
|
||||
markup: render::process_markup(sema.db, def, &markup, config),
|
||||
markup: render::process_markup(sema.db, def, &markup, range_map, config),
|
||||
actions: [
|
||||
show_fn_references_action(sema.db, def),
|
||||
show_implementations_action(sema.db, def),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use hir::{
|
|||
use ide_db::{
|
||||
RootDatabase,
|
||||
defs::Definition,
|
||||
documentation::HasDocs,
|
||||
documentation::{DocsRangeMap, HasDocs},
|
||||
famous_defs::FamousDefs,
|
||||
generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
|
||||
syntax_helpers::prettify_macro_expansion,
|
||||
|
|
@ -21,7 +21,7 @@ use rustc_apfloat::{
|
|||
Float,
|
||||
ieee::{Half as f16, Quad as f128},
|
||||
};
|
||||
use span::Edition;
|
||||
use span::{Edition, TextSize};
|
||||
use stdx::format_to;
|
||||
use syntax::{AstNode, AstToken, Direction, SyntaxToken, T, algo, ast, match_ast};
|
||||
|
||||
|
|
@ -276,13 +276,10 @@ pub(super) fn keyword(
|
|||
keyword_hints(sema, token, parent, edition, display_target);
|
||||
|
||||
let doc_owner = find_std_module(&famous_defs, &keyword_mod, edition)?;
|
||||
let docs = doc_owner.docs(sema.db)?;
|
||||
let markup = process_markup(
|
||||
sema.db,
|
||||
Definition::Module(doc_owner),
|
||||
&markup(Some(docs.into()), description, None, None, String::new()),
|
||||
config,
|
||||
);
|
||||
let (docs, range_map) = doc_owner.docs_with_rangemap(sema.db)?;
|
||||
let (markup, range_map) =
|
||||
markup(Some(docs.into()), Some(range_map), description, None, None, String::new());
|
||||
let markup = process_markup(sema.db, Definition::Module(doc_owner), &markup, range_map, config);
|
||||
Some(HoverResult { markup, actions })
|
||||
}
|
||||
|
||||
|
|
@ -371,11 +368,15 @@ pub(super) fn process_markup(
|
|||
db: &RootDatabase,
|
||||
def: Definition,
|
||||
markup: &Markup,
|
||||
markup_range_map: Option<DocsRangeMap>,
|
||||
config: &HoverConfig,
|
||||
) -> Markup {
|
||||
let markup = markup.as_str();
|
||||
let markup =
|
||||
if config.links_in_hover { rewrite_links(db, markup, def) } else { remove_links(markup) };
|
||||
let markup = if config.links_in_hover {
|
||||
rewrite_links(db, markup, def, markup_range_map)
|
||||
} else {
|
||||
remove_links(markup)
|
||||
};
|
||||
Markup::from(markup)
|
||||
}
|
||||
|
||||
|
|
@ -482,7 +483,7 @@ pub(super) fn definition(
|
|||
config: &HoverConfig,
|
||||
edition: Edition,
|
||||
display_target: DisplayTarget,
|
||||
) -> Markup {
|
||||
) -> (Markup, Option<DocsRangeMap>) {
|
||||
let mod_path = definition_path(db, &def, edition);
|
||||
let label = match def {
|
||||
Definition::Trait(trait_) => trait_
|
||||
|
|
@ -518,7 +519,12 @@ pub(super) fn definition(
|
|||
}
|
||||
_ => def.label(db, display_target),
|
||||
};
|
||||
let docs = def.docs(db, famous_defs, display_target);
|
||||
let (docs, range_map) =
|
||||
if let Some((docs, doc_range)) = def.docs_with_rangemap(db, famous_defs, display_target) {
|
||||
(Some(docs), doc_range)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let value = || match def {
|
||||
Definition::Variant(it) => {
|
||||
if !it.parent_enum(db).is_data_carrying(db) {
|
||||
|
|
@ -807,6 +813,7 @@ pub(super) fn definition(
|
|||
|
||||
markup(
|
||||
docs.map(Into::into),
|
||||
range_map,
|
||||
desc,
|
||||
extra.is_empty().not().then_some(extra),
|
||||
mod_path,
|
||||
|
|
@ -1083,11 +1090,12 @@ fn definition_path(db: &RootDatabase, &def: &Definition, edition: Edition) -> Op
|
|||
|
||||
fn markup(
|
||||
docs: Option<String>,
|
||||
range_map: Option<DocsRangeMap>,
|
||||
rust: String,
|
||||
extra: Option<String>,
|
||||
mod_path: Option<String>,
|
||||
subst_types: String,
|
||||
) -> Markup {
|
||||
) -> (Markup, Option<DocsRangeMap>) {
|
||||
let mut buf = String::new();
|
||||
|
||||
if let Some(mod_path) = mod_path {
|
||||
|
|
@ -1106,9 +1114,15 @@ fn markup(
|
|||
}
|
||||
|
||||
if let Some(doc) = docs {
|
||||
format_to!(buf, "\n___\n\n{}", doc);
|
||||
format_to!(buf, "\n___\n\n");
|
||||
let offset = TextSize::new(buf.len() as u32);
|
||||
let buf_range_map = range_map.map(|range_map| range_map.shift_docstring_line_range(offset));
|
||||
format_to!(buf, "{}", doc);
|
||||
|
||||
(buf.into(), buf_range_map)
|
||||
} else {
|
||||
(buf.into(), None)
|
||||
}
|
||||
buf.into()
|
||||
}
|
||||
|
||||
fn find_std_module(
|
||||
|
|
|
|||
|
|
@ -7374,6 +7374,128 @@ pub struct Foo(i32);
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_intra_inner_attr() {
|
||||
check(
|
||||
r#"
|
||||
/// outer comment for [`Foo`]
|
||||
#[doc = "Doc outer comment for [`Foo`]"]
|
||||
pub fn Foo {
|
||||
//! inner comment for [`Foo$0`]
|
||||
#![doc = "Doc inner comment for [`Foo`]"]
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
*[`Foo`]*
|
||||
|
||||
```rust
|
||||
ra_test_fixture
|
||||
```
|
||||
|
||||
```rust
|
||||
pub fn Foo()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
Doc outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
inner comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
Doc inner comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
/// outer comment for [`Foo`]
|
||||
#[doc = "Doc outer comment for [`Foo`]"]
|
||||
pub mod Foo {
|
||||
//! inner comment for [`super::Foo$0`]
|
||||
#![doc = "Doc inner comment for [`super::Foo`]"]
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
*[`super::Foo`]*
|
||||
|
||||
```rust
|
||||
ra_test_fixture
|
||||
```
|
||||
|
||||
```rust
|
||||
pub mod Foo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
Doc outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
inner comment for [`super::Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
Doc inner comment for [`super::Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_intra_outer_attr() {
|
||||
check(
|
||||
r#"
|
||||
/// outer comment for [`Foo$0`]
|
||||
#[doc = "Doc outer comment for [`Foo`]"]
|
||||
pub fn Foo() {
|
||||
//! inner comment for [`Foo`]
|
||||
#![doc = "Doc inner comment for [`Foo`]"]
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
*[`Foo`]*
|
||||
|
||||
```rust
|
||||
ra_test_fixture
|
||||
```
|
||||
|
||||
```rust
|
||||
pub fn Foo()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
Doc outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
inner comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
Doc inner comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/fn.Foo.html)
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
/// outer comment for [`Foo$0`]
|
||||
#[doc = "Doc outer comment for [`Foo`]"]
|
||||
pub mod Foo {
|
||||
//! inner comment for [`super::Foo`]
|
||||
#![doc = "Doc inner comment for [`super::Foo`]"]
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
*[`Foo`]*
|
||||
|
||||
```rust
|
||||
ra_test_fixture
|
||||
```
|
||||
|
||||
```rust
|
||||
pub mod Foo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
Doc outer comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
inner comment for [`super::Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
Doc inner comment for [`super::Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/Foo/index.html)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_intra_generics() {
|
||||
check(
|
||||
|
|
|
|||
|
|
@ -129,11 +129,18 @@ pub(super) fn doc_comment(
|
|||
extract_definitions_from_docs(&docs)
|
||||
.into_iter()
|
||||
.filter_map(|(range, link, ns)| {
|
||||
doc_mapping.map(range).filter(|mapping| mapping.file_id == src_file_id).and_then(
|
||||
|InFile { value: mapped_range, .. }| {
|
||||
Some(mapped_range).zip(resolve_doc_path_for_def(sema.db, def, &link, ns))
|
||||
},
|
||||
)
|
||||
doc_mapping
|
||||
.map(range)
|
||||
.filter(|(mapping, _)| mapping.file_id == src_file_id)
|
||||
.and_then(|(InFile { value: mapped_range, .. }, attr_id)| {
|
||||
Some(mapped_range).zip(resolve_doc_path_for_def(
|
||||
sema.db,
|
||||
def,
|
||||
&link,
|
||||
ns,
|
||||
attr_id.is_inner_attr(),
|
||||
))
|
||||
})
|
||||
})
|
||||
.for_each(|(range, def)| {
|
||||
hl.add(HlRange {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue