Skip to content

Commit 5f52a51

Browse files
committed
Working intra-doc-links
1 parent 8f56e7c commit 5f52a51

File tree

5 files changed

+119
-8
lines changed

5 files changed

+119
-8
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_ide/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ rustc-hash = "1.1.0"
1919
rand = { version = "0.7.3", features = ["small_rng"] }
2020
comrak = "0.7.0"
2121
url = "*"
22+
maplit = "*"
2223

2324
stdx = { path = "../stdx" }
2425

@@ -36,6 +37,7 @@ ra_project_model = { path = "../ra_project_model" }
3637
ra_hir_def = { path = "../ra_hir_def" }
3738
ra_tt = { path = "../ra_tt" }
3839
ra_hir_expand = { path = "../ra_hir_expand" }
40+
ra_parser = { path = "../ra_parser" }
3941

4042
# ra_ide should depend only on the top-level `hir` package. if you need
4143
# something from some `hir_xxx` subpackage, reexport the API via `hir`.

crates/ra_ide/src/hover.rs

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ use std::sync::Arc;
44

55
use hir::{
66
Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
7-
ModuleSource, Semantics, Documentation, AttrDef, Crate
7+
ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene
88
};
99
use itertools::Itertools;
1010
use ra_db::SourceDatabase;
1111
use ra_ide_db::{
1212
defs::{classify_name, classify_name_ref, Definition},
1313
RootDatabase,
1414
};
15-
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
15+
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path};
1616
use ra_project_model::ProjectWorkspace;
17-
use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase};
17+
use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver};
1818
use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf};
1919
use ra_hir_expand::name::AsName;
20+
use ra_parser::FragmentKind;
21+
use maplit::{hashset, hashmap};
2022

2123
use comrak::{parse_document,format_commonmark, ComrakOptions, Arena};
2224
use comrak::nodes::NodeValue;
@@ -412,8 +414,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor
412414
// module-based links (AKA intra-doc links): `super::super::module::MyStruct`
413415
Err(_) => {
414416
let link_str = String::from_utf8(link.url.clone()).unwrap();
417+
let link_text = String::from_utf8(link.title.clone()).unwrap();
415418
let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url)
416-
.or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str));
419+
.or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_text, &link_str));
417420

418421
if let Some(resolved) = resolved {
419422
link.url = resolved.as_bytes().to_vec();
@@ -430,11 +433,113 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor
430433
Some(String::from_utf8(out).unwrap())
431434
}
432435

436+
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
437+
enum Namespace {
438+
Types,
439+
Values,
440+
Macros
441+
}
442+
443+
impl Namespace {
444+
/// Extract the specified namespace from an intra-doc-link if one exists.
445+
fn from_intra_spec(s: &str) -> Option<Self> {
446+
let ns_map = hashmap!{
447+
Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}),
448+
Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}),
449+
Self::Macros => (hashset!{"macro"}, hashset!{"!"})
450+
};
451+
452+
ns_map
453+
.iter()
454+
.filter(|(ns, (prefixes, suffixes))| {
455+
prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) ||
456+
suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond)
457+
})
458+
.map(|(ns, (_, _))| *ns)
459+
.next()
460+
}
461+
}
462+
433463
/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
434464
///
435465
/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
436-
fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str) -> Option<String> {
437-
None
466+
fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link_text: &str, link_target: &str) -> Option<String> {
467+
eprintln!("try_resolve_intra");
468+
469+
// Set link_target for implied shortlinks
470+
let link_target = if link_target.is_empty() {
471+
link_text.trim_matches('`')
472+
} else {
473+
link_target
474+
};
475+
476+
// Parse link as a module path
477+
// This expects a full document, which a single path isn't, but we can just ignore the errors.
478+
let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0);
479+
let path = parsed.descendants().filter_map(Path::cast).next()?;
480+
let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
481+
482+
// Resolve it relative to symbol's location (according to the RFC this should consider small scopes
483+
let resolver = {
484+
use ra_hir_def::*;
485+
use hir::*;
486+
487+
// TODO: This should be replaced by implementing HasResolver/TryHasResolver on ModuleDef and Definition.
488+
match definition {
489+
Definition::ModuleDef(def) => match def {
490+
ModuleDef::Module(m) => Into::<ModuleId>::into(m.clone()).resolver(db),
491+
ModuleDef::Function(f) => Into::<FunctionId>::into(f.clone()).resolver(db),
492+
ModuleDef::Adt(adt) => Into::<AdtId>::into(adt.clone()).resolver(db),
493+
ModuleDef::EnumVariant(ev) => Into::<GenericDefId>::into(Into::<GenericDef>::into(ev.clone())).resolver(db),
494+
ModuleDef::Const(c) => Into::<GenericDefId>::into(Into::<GenericDef>::into(c.clone())).resolver(db),
495+
ModuleDef::Static(s) => Into::<StaticId>::into(s.clone()).resolver(db),
496+
ModuleDef::Trait(t) => Into::<TraitId>::into(t.clone()).resolver(db),
497+
ModuleDef::TypeAlias(t) => Into::<ModuleId>::into(t.module(db)).resolver(db),
498+
// TODO: This should be a resolver relative to `std`
499+
ModuleDef::BuiltinType(t) => Into::<ModuleId>::into(definition.module(db)?).resolver(db)
500+
},
501+
Definition::Field(field) => Into::<VariantId>::into(Into::<VariantDef>::into(field.parent_def(db))).resolver(db),
502+
Definition::Macro(m) => Into::<ModuleId>::into(m.module(db)?).resolver(db),
503+
Definition::SelfType(imp) => Into::<ImplId>::into(imp.clone()).resolver(db),
504+
// it's possible, read probable, that other arms of this are also unreachable
505+
Definition::Local(local) => unreachable!(),
506+
Definition::TypeParam(tp) => Into::<ModuleId>::into(tp.module(db)).resolver(db)
507+
}
508+
};
509+
510+
// Namespace disambiguation
511+
let namespace = Namespace::from_intra_spec(link_target);
512+
513+
let resolved = resolver.resolve_module_path_in_items(db, &modpath);
514+
let (defid, namespace) = match namespace {
515+
// TODO: .or(resolved.macros)
516+
None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
517+
Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
518+
Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
519+
// TODO:
520+
Some(Namespace::Macros) => None?
521+
};
522+
523+
// Get the filepath of the final symbol
524+
let def: ModuleDef = defid.into();
525+
let module = def.module(db)?;
526+
let krate = module.krate();
527+
let ns = match namespace {
528+
Namespace::Types => ItemInNs::Types(defid),
529+
Namespace::Values => ItemInNs::Values(defid),
530+
// TODO:
531+
Namespace::Macros => None?
532+
};
533+
let import_map = db.import_map(krate.into());
534+
let path = import_map.path_of(ns)?;
535+
536+
Some(
537+
get_doc_url(db, &krate)?
538+
.join(&format!("{}/", krate.display_name(db)?)).ok()?
539+
.join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()?
540+
.join(&get_symbol_filename(db, definition)?).ok()?
541+
.into_string()
542+
)
438543
}
439544

440545
enum UrlMode {
@@ -444,6 +549,7 @@ enum UrlMode {
444549

445550
/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
446551
fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> {
552+
eprintln!("try_resolve_path");
447553
let ns = if let Definition::ModuleDef(moddef) = definition {
448554
ItemInNs::Types(moddef.clone().into())
449555
} else {
@@ -481,6 +587,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pat
481587
}
482588

483589
/// Try to get the root URL of the documentation of a crate.
590+
// TODO: Special case standard, core, alloc libraries
484591
fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
485592
// Look for #![doc(html_root_url = "...")]
486593
let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());

crates/ra_syntax/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::syntax_node::GreenNode;
4747
pub use crate::{
4848
algo::InsertPosition,
4949
ast::{AstNode, AstToken},
50-
parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token},
50+
parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token, parse_text},
5151
ptr::{AstPtr, SyntaxNodePtr},
5252
syntax_error::SyntaxError,
5353
syntax_node::{

crates/ra_syntax/src/parsing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use lexer::*;
1515
pub(crate) use self::reparsing::incremental_reparse;
1616
use ra_parser::SyntaxKind;
1717

18-
pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) {
18+
pub fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) {
1919
let (tokens, lexer_errors) = tokenize(&text);
2020

2121
let mut token_source = TextTokenSource::new(text, &tokens);

0 commit comments

Comments
 (0)