Skip to content

Commit 19c2830

Browse files
committed
move into separate module
1 parent a7a00a8 commit 19c2830

File tree

4 files changed

+308
-298
lines changed

4 files changed

+308
-298
lines changed

crates/ra_ide/src/hover.rs

Lines changed: 4 additions & 298 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
use std::iter::once;
2-
31
use hir::{
4-
db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation,
5-
FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, Module, ModuleDef,
6-
ModuleSource, Semantics,
2+
Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
3+
Module, ModuleDef, ModuleSource, Semantics,
74
};
85
use itertools::Itertools;
9-
use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
10-
use pulldown_cmark_to_cmark::cmark;
116
use ra_db::SourceDatabase;
127
use ra_ide_db::{
138
defs::{classify_name, classify_name_ref, Definition},
149
RootDatabase,
1510
};
16-
use ra_syntax::{ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
17-
use ra_tt::{Ident, Leaf, Literal, TokenTree};
11+
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
1812
use stdx::format_to;
1913
use test_utils::mark;
20-
use url::Url;
2114

2215
use crate::{
2316
display::{macro_label, ShortLabel, ToNav, TryToNav},
17+
link_rewrite::rewrite_links,
2418
markup::Markup,
2519
runnables::runnable,
2620
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
@@ -343,294 +337,6 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
343337
}
344338
}
345339

346-
// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
347-
fn map_links<'e>(
348-
events: impl Iterator<Item = Event<'e>>,
349-
callback: impl Fn(&str, &str) -> (String, String),
350-
) -> impl Iterator<Item = Event<'e>> {
351-
let mut in_link = false;
352-
let mut link_target: Option<CowStr> = None;
353-
354-
events.map(move |evt| match evt {
355-
Event::Start(Tag::Link(_link_type, ref target, _)) => {
356-
in_link = true;
357-
link_target = Some(target.clone());
358-
evt
359-
}
360-
Event::End(Tag::Link(link_type, _target, _)) => {
361-
in_link = false;
362-
Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
363-
}
364-
Event::Text(s) if in_link => {
365-
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
366-
link_target = Some(CowStr::Boxed(link_target_s.into()));
367-
Event::Text(CowStr::Boxed(link_name.into()))
368-
}
369-
Event::Code(s) if in_link => {
370-
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
371-
link_target = Some(CowStr::Boxed(link_target_s.into()));
372-
Event::Code(CowStr::Boxed(link_name.into()))
373-
}
374-
_ => evt,
375-
})
376-
}
377-
378-
/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
379-
fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
380-
let doc = Parser::new_with_broken_link_callback(
381-
markdown,
382-
Options::empty(),
383-
Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
384-
);
385-
386-
let doc = map_links(doc, |target, title: &str| {
387-
// This check is imperfect, there's some overlap between valid intra-doc links
388-
// and valid URLs so we choose to be too eager to try to resolve what might be
389-
// a URL.
390-
if target.contains("://") {
391-
(target.to_string(), title.to_string())
392-
} else {
393-
// Two posibilities:
394-
// * path-based links: `../../module/struct.MyStruct.html`
395-
// * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
396-
let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| {
397-
try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))
398-
});
399-
400-
match resolved {
401-
Some((target, title)) => (target, title),
402-
None => (target.to_string(), title.to_string()),
403-
}
404-
}
405-
});
406-
let mut out = String::new();
407-
cmark(doc, &mut out, None).ok();
408-
out
409-
}
410-
411-
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
412-
enum Namespace {
413-
Types,
414-
Values,
415-
Macros,
416-
}
417-
418-
static TYPES: ([&str; 7], [&str; 0]) =
419-
(["type", "struct", "enum", "mod", "trait", "union", "module"], []);
420-
static VALUES: ([&str; 8], [&str; 1]) =
421-
(["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]);
422-
static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]);
423-
424-
impl Namespace {
425-
/// Extract the specified namespace from an intra-doc-link if one exists.
426-
///
427-
/// # Examples
428-
///
429-
/// * `struct MyStruct` -> `Namespace::Types`
430-
/// * `panic!` -> `Namespace::Macros`
431-
/// * `fn@from_intra_spec` -> `Namespace::Values`
432-
fn from_intra_spec(s: &str) -> Option<Self> {
433-
[
434-
(Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())),
435-
(Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())),
436-
(Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())),
437-
]
438-
.iter()
439-
.filter(|(_ns, (prefixes, suffixes))| {
440-
prefixes
441-
.clone()
442-
.map(|prefix| {
443-
s.starts_with(*prefix)
444-
&& s.chars()
445-
.nth(prefix.len() + 1)
446-
.map(|c| c == '@' || c == ' ')
447-
.unwrap_or(false)
448-
})
449-
.any(|cond| cond)
450-
|| suffixes
451-
.clone()
452-
.map(|suffix| {
453-
s.starts_with(*suffix)
454-
&& s.chars()
455-
.nth(suffix.len() + 1)
456-
.map(|c| c == '@' || c == ' ')
457-
.unwrap_or(false)
458-
})
459-
.any(|cond| cond)
460-
})
461-
.map(|(ns, (_, _))| *ns)
462-
.next()
463-
}
464-
}
465-
466-
// Strip prefixes, suffixes, and inline code marks from the given string.
467-
fn strip_prefixes_suffixes(mut s: &str) -> &str {
468-
s = s.trim_matches('`');
469-
470-
[
471-
(TYPES.0.iter(), TYPES.1.iter()),
472-
(VALUES.0.iter(), VALUES.1.iter()),
473-
(MACROS.0.iter(), MACROS.1.iter()),
474-
]
475-
.iter()
476-
.for_each(|(prefixes, suffixes)| {
477-
prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
478-
suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
479-
});
480-
s.trim_start_matches("@").trim()
481-
}
482-
483-
/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
484-
///
485-
/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
486-
fn try_resolve_intra(
487-
db: &RootDatabase,
488-
definition: &Definition,
489-
link_text: &str,
490-
link_target: &str,
491-
) -> Option<(String, String)> {
492-
eprintln!("resolving intra");
493-
494-
// Set link_target for implied shortlinks
495-
let link_target =
496-
if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };
497-
498-
// Namespace disambiguation
499-
let namespace = Namespace::from_intra_spec(link_target);
500-
501-
// Strip prefixes/suffixes
502-
let link_target = strip_prefixes_suffixes(link_target);
503-
504-
// Parse link as a module path
505-
let path = Path::parse(link_target).ok()?;
506-
let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
507-
508-
// Resolve it relative to symbol's location (according to the RFC this should consider small scopes
509-
let resolver = definition.resolver(db)?;
510-
511-
let resolved = resolver.resolve_module_path_in_items(db, &modpath);
512-
let (defid, namespace) = match namespace {
513-
// FIXME: .or(resolved.macros)
514-
None => resolved
515-
.types
516-
.map(|t| (t.0, Namespace::Types))
517-
.or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
518-
Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
519-
Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
520-
// FIXME:
521-
Some(Namespace::Macros) => None?,
522-
};
523-
524-
// Get the filepath of the final symbol
525-
let def: ModuleDef = defid.into();
526-
let module = def.module(db)?;
527-
let krate = module.krate();
528-
let ns = match namespace {
529-
Namespace::Types => ItemInNs::Types(defid),
530-
Namespace::Values => ItemInNs::Values(defid),
531-
// FIXME:
532-
Namespace::Macros => None?,
533-
};
534-
let import_map = db.import_map(krate.into());
535-
let path = import_map.path_of(ns)?;
536-
537-
Some((
538-
get_doc_url(db, &krate)?
539-
.join(&format!("{}/", krate.display_name(db)?))
540-
.ok()?
541-
.join(&path.segments.iter().map(|name| name.to_string()).join("/"))
542-
.ok()?
543-
.join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
544-
.ok()?
545-
.into_string(),
546-
strip_prefixes_suffixes(link_text).to_string(),
547-
))
548-
}
549-
550-
/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
551-
fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> {
552-
eprintln!("resolving path");
553-
554-
if !link.contains("#") && !link.contains(".html") {
555-
return None;
556-
}
557-
let ns = if let Definition::ModuleDef(moddef) = definition {
558-
ItemInNs::Types(moddef.clone().into())
559-
} else {
560-
return None;
561-
};
562-
let module = definition.module(db)?;
563-
let krate = module.krate();
564-
let import_map = db.import_map(krate.into());
565-
let base = once(format!("{}", krate.display_name(db)?))
566-
.chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name)))
567-
.join("/");
568-
569-
get_doc_url(db, &krate)
570-
.and_then(|url| url.join(&base).ok())
571-
.and_then(|url| {
572-
get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()
573-
})
574-
.and_then(|url| url.join(link).ok())
575-
.map(|url| url.into_string())
576-
}
577-
578-
/// Try to get the root URL of the documentation of a crate.
579-
fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
580-
// Look for #![doc(html_root_url = "...")]
581-
let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());
582-
let doc_attr_q = attrs.by_key("doc");
583-
584-
let doc_url = if doc_attr_q.exists() {
585-
doc_attr_q.tt_values().map(|tt| {
586-
let name = tt.token_trees.iter()
587-
.skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url"))
588-
.skip(2)
589-
.next();
590-
591-
match name {
592-
Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
593-
_ => None
594-
}
595-
}).flat_map(|t| t).next().map(|s| s.to_string())
596-
} else {
597-
// Fallback to docs.rs
598-
// FIXME: Specify an exact version here (from Cargo.lock)
599-
Some(format!("https://docs.rs/{}/*", krate.display_name(db)?))
600-
};
601-
602-
doc_url
603-
.map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/")
604-
.and_then(|s| Url::parse(&s).ok())
605-
}
606-
607-
/// Get the filename and extension generated for a symbol by rustdoc.
608-
///
609-
/// Example: `struct.Shard.html`
610-
fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option<String> {
611-
Some(match definition {
612-
Definition::ModuleDef(def) => match def {
613-
ModuleDef::Adt(adt) => match adt {
614-
Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
615-
Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
616-
Adt::Union(u) => format!("union.{}.html", u.name(db)),
617-
},
618-
ModuleDef::Module(_) => "index.html".to_string(),
619-
ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
620-
ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
621-
ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
622-
ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
623-
ModuleDef::EnumVariant(ev) => {
624-
format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
625-
}
626-
ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
627-
ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
628-
},
629-
Definition::Macro(m) => format!("macro.{}.html", m.name(db)?),
630-
_ => None?,
631-
})
632-
}
633-
634340
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
635341
return tokens.max_by_key(priority);
636342
fn priority(n: &SyntaxToken) -> usize {

crates/ra_ide/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod status;
4444
mod syntax_highlighting;
4545
mod syntax_tree;
4646
mod typing;
47+
mod link_rewrite;
4748

4849
use std::sync::Arc;
4950

0 commit comments

Comments
 (0)