diff options
| author | Ddystopia <alexanderbabak@proton.me> | 2023-04-25 15:14:35 +0200 |
|---|---|---|
| committer | Ddystopia <alexanderbabak@proton.me> | 2023-05-02 17:06:38 +0200 |
| commit | f2d933ecaf5f3d1749c5aa31c3b99c07573c7369 (patch) | |
| tree | d9c7995bfe527ac4d3fc70d0ca2b4a247c4ac055 | |
| parent | fc888b583d3dd06ea89a3558350dda9440c82f61 (diff) | |
| download | rust-f2d933ecaf5f3d1749c5aa31c3b99c07573c7369.tar.gz rust-f2d933ecaf5f3d1749c5aa31c3b99c07573c7369.zip | |
Add support for local documentation links alongside web documentation links, pending for `target_dir` path and tests
| -rw-r--r-- | crates/ide/src/doc_links.rs | 114 | ||||
| -rw-r--r-- | crates/ide/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers/request.rs | 9 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 2 |
4 files changed, 82 insertions, 45 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 8d86c615d44..a291389363b 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -29,8 +29,16 @@ use crate::{ FilePosition, Semantics, }; -/// Weblink to an item's documentation. -pub(crate) type DocumentationLink = String; +/// Web and local links to an item's documentation. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct DocumentationLinks { + /// The URL to the documentation on docs.rs. + /// Could be invalid. + pub web_url: Option<String>, + /// The URL to the documentation in the local file system. + /// Could be invalid. + pub local_url: Option<String>, +} const MARKDOWN_OPTIONS: Options = Options::ENABLE_FOOTNOTES.union(Options::ENABLE_TABLES).union(Options::ENABLE_TASKLISTS); @@ -119,10 +127,7 @@ pub(crate) fn remove_links(markdown: &str) -> String { // // | VS Code | **rust-analyzer: Open Docs** // |=== -pub(crate) fn external_docs( - db: &RootDatabase, - position: &FilePosition, -) -> Option<DocumentationLink> { +pub(crate) fn external_docs(db: &RootDatabase, position: &FilePosition) -> DocumentationLinks { let sema = &Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { @@ -130,27 +135,30 @@ pub(crate) fn external_docs( T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, _ => 1, - })?; + }); + let Some(token) = token else { return Default::default() }; let token = sema.descend_into_macros_single(token); - let node = token.parent()?; + let Some(node) = token.parent() else { return Default::default() }; let definition = match_ast! { match node { - ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref: _, field_ref } => { + ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref) { + Some(NameRefClass::Definition(def)) => def, + Some(NameRefClass::FieldShorthand { local_ref: _, field_ref }) => { Definition::Field(field_ref) } + None => return Default::default(), }, - ast::Name(name) => match NameClass::classify(sema, &name)? { - NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref), + ast::Name(name) => match NameClass::classify(sema, &name) { + Some(NameClass::Definition(it) | NameClass::ConstReference(it)) => it, + Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => Definition::Field(field_ref), + None => return Default::default(), }, - _ => return None, + _ => return Default::default(), } }; - get_doc_link(db, definition) + return get_doc_links(db, definition); } /// Extracts all links from a given markdown text returning the definition text range, link-text @@ -308,19 +316,34 @@ fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>) // // This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented // https://github.com/rust-lang/rfcs/pull/2988 -fn get_doc_link(db: &RootDatabase, def: Definition) -> Option<String> { - let (target, file, frag) = filename_and_frag_for_def(db, def)?; +fn get_doc_links(db: &RootDatabase, def: Definition) -> DocumentationLinks { + let Some((target, file, frag)) = filename_and_frag_for_def(db, def) else { return Default::default(); }; - let mut url = get_doc_base_url(db, target)?; + let (mut web_url, mut local_url) = get_doc_base_urls(db, target); if let Some(path) = mod_path_of_def(db, target) { - url = url.join(&path).ok()?; + web_url = join_url(web_url, &path); + local_url = join_url(local_url, &path); } - url = url.join(&file).ok()?; - url.set_fragment(frag.as_deref()); + web_url = join_url(web_url, &file); + local_url = join_url(local_url, &file); + + set_fragment_for_url(web_url.as_mut(), frag.as_deref()); + set_fragment_for_url(local_url.as_mut(), frag.as_deref()); - Some(url.into()) + return DocumentationLinks { + web_url: web_url.map(|it| it.into()), + local_url: local_url.map(|it| it.into()), + }; + + fn join_url(base_url: Option<Url>, path: &str) -> Option<Url> { + base_url.and_then(|url| url.join(path).ok()) + } + + fn set_fragment_for_url(url: Option<&mut Url>, frag: Option<&str>) { + url.map(|url| url.set_fragment(frag)); + } } fn rewrite_intra_doc_link( @@ -332,7 +355,7 @@ fn rewrite_intra_doc_link( let (link, ns) = parse_intra_doc_link(target); let resolved = resolve_doc_path_for_def(db, def, link, ns)?; - let mut url = get_doc_base_url(db, resolved)?; + let mut url = get_doc_base_urls(db, resolved).0?; let (_, file, frag) = filename_and_frag_for_def(db, resolved)?; if let Some(path) = mod_path_of_def(db, resolved) { @@ -351,7 +374,7 @@ fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option< return None; } - let mut url = get_doc_base_url(db, def)?; + let mut url = get_doc_base_urls(db, def).0?; let (def, file, frag) = filename_and_frag_for_def(db, def)?; if let Some(path) = mod_path_of_def(db, def) { @@ -427,18 +450,26 @@ fn map_links<'e>( /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next /// ^^^^^^^^^^^^^^^^^^^^^^^^^^ /// ``` -fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> { +fn get_doc_base_urls(db: &RootDatabase, def: Definition) -> (Option<Url>, Option<Url>) { + // TODO: get this is from `CargoWorkspace` + // TODO: get `CargoWorkspace` from `db` + let target_path = "file:///project/root/target"; + let target_path = Url::parse(target_path).ok(); + let local_doc_path = target_path.and_then(|url| url.join("doc").ok()); + debug_assert!(local_doc_path.is_some(), "failed to parse local doc path"); + // special case base url of `BuiltinType` to core // https://github.com/rust-lang/rust-analyzer/issues/12250 if let Definition::BuiltinType(..) = def { - return Url::parse("https://doc.rust-lang.org/nightly/core/").ok(); + let weblink = Url::parse("https://doc.rust-lang.org/nightly/core/").ok(); + return (weblink, local_doc_path); }; - let krate = def.krate(db)?; - let display_name = krate.display_name(db)?; + let Some(krate) = def.krate(db) else { return Default::default() }; + let Some(display_name) = krate.display_name(db) else { return Default::default() }; let crate_data = &db.crate_graph()[krate.into()]; let channel = crate_data.channel.map_or("nightly", ReleaseChannel::as_str); - let base = match &crate_data.origin { + let (web_base, local_base) = match &crate_data.origin { // std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself. // FIXME: Use the toolchains channel instead of nightly CrateOrigin::Lang( @@ -447,16 +478,14 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> { | LangCrateOrigin::ProcMacro | LangCrateOrigin::Std | LangCrateOrigin::Test), - ) => { - format!("https://doc.rust-lang.org/{channel}/{origin}") - } - CrateOrigin::Lang(_) => return None, + ) => (Some(format!("https://doc.rust-lang.org/{channel}/{origin}")), None), + CrateOrigin::Lang(_) => return (None, None), CrateOrigin::Rustc { name: _ } => { - format!("https://doc.rust-lang.org/{channel}/nightly-rustc/") + (Some(format!("https://doc.rust-lang.org/{channel}/nightly-rustc/")), None) } CrateOrigin::Local { repo: _, name: _ } => { // FIXME: These should not attempt to link to docs.rs! - krate.get_html_root_url(db).or_else(|| { + let weblink = krate.get_html_root_url(db).or_else(|| { let version = krate.version(db); // Fallback to docs.rs. This uses `display_name` and can never be // correct, but that's what fallbacks are about. @@ -468,10 +497,11 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> { krate = display_name, version = version.as_deref().unwrap_or("*") )) - })? + }); + (weblink, local_doc_path) } CrateOrigin::Library { repo: _, name } => { - krate.get_html_root_url(db).or_else(|| { + let weblink = krate.get_html_root_url(db).or_else(|| { let version = krate.version(db); // Fallback to docs.rs. This uses `display_name` and can never be // correct, but that's what fallbacks are about. @@ -483,10 +513,14 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> { krate = name, version = version.as_deref().unwrap_or("*") )) - })? + }); + (weblink, local_doc_path) } }; - Url::parse(&base).ok()?.join(&format!("{display_name}/")).ok() + let web_base = web_base + .and_then(|it| Url::parse(&it).ok()) + .and_then(|it| it.join(&format!("{display_name}/")).ok()); + (web_base, local_base) } /// Get the filename and extension generated for a symbol by rustdoc. diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 24e2aed65a5..4a4410ad20f 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -471,7 +471,7 @@ impl Analysis { pub fn external_docs( &self, position: FilePosition, - ) -> Cancellable<Option<doc_links::DocumentationLink>> { + ) -> Cancellable<doc_links::DocumentationLinks> { self.with_db(|db| doc_links::external_docs(db, &position)) } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index f25dc74a142..47dd571054b 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1535,13 +1535,16 @@ pub(crate) fn handle_semantic_tokens_range( pub(crate) fn handle_open_docs( snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, -) -> Result<Option<lsp_types::Url>> { +) -> Result<(Option<lsp_types::Url>, Option<lsp_types::Url>)> { let _p = profile::span("handle_open_docs"); let position = from_proto::file_position(&snap, params)?; - let remote = snap.analysis.external_docs(position)?; + let Ok(remote_urls) = snap.analysis.external_docs(position) else { return Ok((None, None)); }; - Ok(remote.and_then(|remote| Url::parse(&remote).ok())) + let web_url = remote_urls.web_url.and_then(|it| Url::parse(&it).ok()); + let local_url = remote_urls.local_url.and_then(|it| Url::parse(&it).ok()); + + Ok((web_url, local_url)) } pub(crate) fn handle_open_cargo_toml( diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 69e7d824680..9c27f6e1c33 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -508,7 +508,7 @@ pub enum ExternalDocs {} impl Request for ExternalDocs { type Params = lsp_types::TextDocumentPositionParams; - type Result = Option<lsp_types::Url>; + type Result = (Option<lsp_types::Url>, Option<lsp_types::Url>); const METHOD: &'static str = "experimental/externalDocs"; } |
