Auto merge of #14662 - Ddystopia:open_locally_built_documentatin_instead_of_docs_dot_rs, r=Ddystopia

Provide links to locally built documentation for `experimental/externalDocs`

This pull request addresses issue #12867, which requested the ability to provide links to locally built documentation when using the "Open docs for symbol" feature. Previously, rust-analyzer always used docs.rs for this purpose. With these changes, the feature will provide both web (docs.rs) and local documentation links without verifying their existence.

Changes in this PR:

   - Added support for local documentation links alongside web documentation links.
   - Added `target_dir` path argument for external_docs and other related methods.
   - Added `sysroot` argument for external_docs.
   - Added `target_directory` path to `CargoWorkspace`.

API Changes:

   - Added an experimental client capability `{ "localDocs": boolean }`. If this capability is set, the `Open External Documentation` request returned from the server will include both web and local documentation links in the `ExternalDocsResponse` object.

Here's the `ExternalDocsResponse` interface:

```typescript
interface ExternalDocsResponse {
    web?: string;
    local?: string;
}
```

By providing links to both web-based and locally built documentation, this update improves the developer experience for those using different versions of crates, git dependencies, or local crates not available on docs.rs. Rust-analyzer will now provide both web (docs.rs) and local documentation links, leaving it to the client to open the desired link. Please note that this update does not perform any checks to ensure the validity of the provided links.
This commit is contained in:
bors 2023-05-02 15:57:19 +00:00
commit c9b4116a5e
8 changed files with 307 additions and 65 deletions

View File

@ -5,6 +5,8 @@ mod tests;
mod intra_doc_links;
use std::ffi::OsStr;
use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions};
use stdx::format_to;
@ -29,8 +31,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.
/// May not lead anywhere.
pub web_url: Option<String>,
/// The URL to the documentation in the local file system.
/// May not lead anywhere.
pub local_url: Option<String>,
}
const MARKDOWN_OPTIONS: Options =
Options::ENABLE_FOOTNOTES.union(Options::ENABLE_TABLES).union(Options::ENABLE_TASKLISTS);
@ -109,7 +119,7 @@ pub(crate) fn remove_links(markdown: &str) -> String {
// Feature: Open Docs
//
// Retrieve a link to documentation for the given symbol.
// Retrieve a links to documentation for the given symbol.
//
// The simplest way to use this feature is via the context menu. Right-click on
// the selected item. The context menu opens. Select **Open Docs**.
@ -122,7 +132,9 @@ pub(crate) fn remove_links(markdown: &str) -> String {
pub(crate) fn external_docs(
db: &RootDatabase,
position: &FilePosition,
) -> Option<DocumentationLink> {
target_dir: Option<&OsStr>,
sysroot: Option<&OsStr>,
) -> Option<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 {
@ -146,11 +158,11 @@ pub(crate) fn external_docs(
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref),
},
_ => return None,
_ => return None
}
};
get_doc_link(db, definition)
Some(get_doc_links(db, definition, target_dir, sysroot))
}
/// Extracts all links from a given markdown text returning the definition text range, link-text
@ -308,19 +320,35 @@ 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,
target_dir: Option<&OsStr>,
sysroot: Option<&OsStr>,
) -> DocumentationLinks {
let join_url = |base_url: Option<Url>, path: &str| -> Option<Url> {
base_url.and_then(|url| url.join(path).ok())
};
let mut url = get_doc_base_url(db, target)?;
let Some((target, file, frag)) = filename_and_frag_for_def(db, def) else { return Default::default(); };
let (mut web_url, mut local_url) = get_doc_base_urls(db, target, target_dir, sysroot);
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);
Some(url.into())
web_url.as_mut().map(|url| url.set_fragment(frag.as_deref()));
local_url.as_mut().map(|url| url.set_fragment(frag.as_deref()));
DocumentationLinks {
web_url: web_url.map(|it| it.into()),
local_url: local_url.map(|it| it.into()),
}
}
fn rewrite_intra_doc_link(
@ -332,7 +360,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, None, None).0?;
let (_, file, frag) = filename_and_frag_for_def(db, resolved)?;
if let Some(path) = mod_path_of_def(db, resolved) {
@ -351,7 +379,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, None, None).0?;
let (def, file, frag) = filename_and_frag_for_def(db, def)?;
if let Some(path) = mod_path_of_def(db, def) {
@ -426,19 +454,38 @@ fn map_links<'e>(
/// ```ignore
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
/// file:///project/root/target/doc/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,
target_dir: Option<&OsStr>,
sysroot: Option<&OsStr>,
) -> (Option<Url>, Option<Url>) {
let local_doc = target_dir
.and_then(|path| path.to_str())
.and_then(|path| Url::parse(&format!("file:///{path}/")).ok())
.and_then(|it| it.join("doc/").ok());
let system_doc = sysroot
.and_then(|it| it.to_str())
.map(|sysroot| format!("file:///{sysroot}/share/doc/rust/html/"))
.and_then(|it| Url::parse(&it).ok());
// 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 web_link = Url::parse("https://doc.rust-lang.org/nightly/core/").ok();
let system_link = system_doc.and_then(|it| it.join("core/").ok());
return (web_link, system_link);
};
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(
@ -448,15 +495,17 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
| LangCrateOrigin::Std
| LangCrateOrigin::Test),
) => {
format!("https://doc.rust-lang.org/{channel}/{origin}")
let system_url = system_doc.and_then(|it| it.join(&format!("{origin}")).ok());
let web_url = format!("https://doc.rust-lang.org/{channel}/{origin}");
(Some(web_url), system_url)
}
CrateOrigin::Lang(_) => return 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 +517,11 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
krate = display_name,
version = version.as_deref().unwrap_or("*")
))
})?
});
(weblink, local_doc)
}
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 +533,16 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
krate = name,
version = version.as_deref().unwrap_or("*")
))
})?
});
(weblink, local_doc)
}
};
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());
let local_base = local_base.and_then(|it| it.join(&format!("{display_name}/")).ok());
(web_base, local_base)
}
/// Get the filename and extension generated for a symbol by rustdoc.

View File

@ -1,3 +1,5 @@
use std::ffi::OsStr;
use expect_test::{expect, Expect};
use hir::{HasAttrs, Semantics};
use ide_db::{
@ -13,11 +15,33 @@ use crate::{
fixture, TryToNav,
};
fn check_external_docs(ra_fixture: &str, expect: Expect) {
fn check_external_docs(
ra_fixture: &str,
target_dir: Option<&OsStr>,
expect_web_url: Option<Expect>,
expect_local_url: Option<Expect>,
sysroot: Option<&OsStr>,
) {
let (analysis, position) = fixture::position(ra_fixture);
let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
let links = analysis.external_docs(position, target_dir, sysroot).unwrap();
expect.assert_eq(&url)
let web_url = links.web_url;
let local_url = links.local_url;
println!("web_url: {:?}", web_url);
println!("local_url: {:?}", local_url);
match (expect_web_url, web_url) {
(Some(expect), Some(url)) => expect.assert_eq(&url),
(None, None) => (),
_ => panic!("Unexpected web url"),
}
match (expect_local_url, local_url) {
(Some(expect), Some(url)) => expect.assert_eq(&url),
(None, None) => (),
_ => panic!("Unexpected local url"),
}
}
fn check_rewrite(ra_fixture: &str, expect: Expect) {
@ -96,6 +120,20 @@ fn node_to_def(
})
}
#[test]
fn external_docs_doc_builtin_type() {
check_external_docs(
r#"
//- /main.rs crate:foo
let x: u3$02 = 0;
"#,
Some(&OsStr::new("/home/user/project")),
Some(expect![[r#"https://doc.rust-lang.org/nightly/core/primitive.u32.html"#]]),
Some(expect![[r#"file:///sysroot/share/doc/rust/html/core/primitive.u32.html"#]]),
Some(&OsStr::new("/sysroot")),
);
}
#[test]
fn external_docs_doc_url_crate() {
check_external_docs(
@ -105,7 +143,10 @@ use foo$0::Foo;
//- /lib.rs crate:foo
pub struct Foo;
"#,
expect![[r#"https://docs.rs/foo/*/foo/index.html"#]],
Some(&OsStr::new("/home/user/project")),
Some(expect![[r#"https://docs.rs/foo/*/foo/index.html"#]]),
Some(expect![[r#"file:///home/user/project/doc/foo/index.html"#]]),
Some(&OsStr::new("/sysroot")),
);
}
@ -116,7 +157,10 @@ fn external_docs_doc_url_std_crate() {
//- /main.rs crate:std
use self$0;
"#,
expect!["https://doc.rust-lang.org/stable/std/index.html"],
Some(&OsStr::new("/home/user/project")),
Some(expect!["https://doc.rust-lang.org/stable/std/index.html"]),
Some(expect!["file:///sysroot/share/doc/rust/html/std/index.html"]),
Some(&OsStr::new("/sysroot")),
);
}
@ -127,7 +171,38 @@ fn external_docs_doc_url_struct() {
//- /main.rs crate:foo
pub struct Fo$0o;
"#,
expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]],
Some(&OsStr::new("/home/user/project")),
Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]),
Some(expect![[r#"file:///home/user/project/doc/foo/struct.Foo.html"#]]),
Some(&OsStr::new("/sysroot")),
);
}
#[test]
fn external_docs_doc_url_windows_backslash_path() {
check_external_docs(
r#"
//- /main.rs crate:foo
pub struct Fo$0o;
"#,
Some(&OsStr::new(r"C:\Users\user\project")),
Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]),
Some(expect![[r#"file:///C:/Users/user/project/doc/foo/struct.Foo.html"#]]),
Some(&OsStr::new("/sysroot")),
);
}
#[test]
fn external_docs_doc_url_windows_slash_path() {
check_external_docs(
r#"
//- /main.rs crate:foo
pub struct Fo$0o;
"#,
Some(&OsStr::new(r"C:/Users/user/project")),
Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]),
Some(expect![[r#"file:///C:/Users/user/project/doc/foo/struct.Foo.html"#]]),
Some(&OsStr::new("/sysroot")),
);
}
@ -140,7 +215,10 @@ pub struct Foo {
field$0: ()
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#structfield.field"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#structfield.field"##]]),
None,
None,
);
}
@ -151,7 +229,10 @@ fn external_docs_doc_url_fn() {
//- /main.rs crate:foo
pub fn fo$0o() {}
"#,
expect![[r#"https://docs.rs/foo/*/foo/fn.foo.html"#]],
None,
Some(expect![[r#"https://docs.rs/foo/*/foo/fn.foo.html"#]]),
None,
None,
);
}
@ -165,7 +246,10 @@ impl Foo {
pub fn method$0() {}
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]]),
None,
None,
);
check_external_docs(
r#"
@ -175,7 +259,10 @@ impl Foo {
const CONST$0: () = ();
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]]),
None,
None,
);
}
@ -192,7 +279,10 @@ impl Trait for Foo {
pub fn method$0() {}
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]]),
None,
None,
);
check_external_docs(
r#"
@ -205,7 +295,10 @@ impl Trait for Foo {
const CONST$0: () = ();
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]]),
None,
None,
);
check_external_docs(
r#"
@ -218,7 +311,10 @@ impl Trait for Foo {
type Type$0 = ();
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedtype.Type"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedtype.Type"##]]),
None,
None,
);
}
@ -231,7 +327,10 @@ pub trait Foo {
fn method$0();
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#tymethod.method"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#tymethod.method"##]]),
None,
None,
);
check_external_docs(
r#"
@ -240,7 +339,10 @@ pub trait Foo {
const CONST$0: ();
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedconstant.CONST"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedconstant.CONST"##]]),
None,
None,
);
check_external_docs(
r#"
@ -249,7 +351,10 @@ pub trait Foo {
type Type$0;
}
"#,
expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedtype.Type"##]],
None,
Some(expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedtype.Type"##]]),
None,
None,
);
}
@ -260,7 +365,10 @@ fn external_docs_trait() {
//- /main.rs crate:foo
trait Trait$0 {}
"#,
expect![[r#"https://docs.rs/foo/*/foo/trait.Trait.html"#]],
None,
Some(expect![[r#"https://docs.rs/foo/*/foo/trait.Trait.html"#]]),
None,
None,
)
}
@ -273,7 +381,10 @@ pub mod foo {
pub mod ba$0r {}
}
"#,
expect![[r#"https://docs.rs/foo/*/foo/foo/bar/index.html"#]],
None,
Some(expect![[r#"https://docs.rs/foo/*/foo/foo/bar/index.html"#]]),
None,
None,
)
}
@ -294,7 +405,10 @@ fn foo() {
let bar: wrapper::It$0em;
}
"#,
expect![[r#"https://docs.rs/foo/*/foo/wrapper/module/struct.Item.html"#]],
None,
Some(expect![[r#"https://docs.rs/foo/*/foo/wrapper/module/struct.Item.html"#]]),
None,
None,
)
}

View File

@ -61,7 +61,7 @@ mod view_item_tree;
mod shuffle_crate_graph;
mod fetch_crates;
use std::sync::Arc;
use std::{ffi::OsStr, sync::Arc};
use cfg::CfgOptions;
use fetch_crates::CrateInfo;
@ -467,12 +467,19 @@ impl Analysis {
self.with_db(|db| moniker::moniker(db, position))
}
/// Return URL(s) for the documentation of the symbol under the cursor.
/// Returns URL(s) for the documentation of the symbol under the cursor.
/// # Arguments
/// * `position` - Position in the file.
/// * `target_dir` - Directory where the build output is storeda.
pub fn external_docs(
&self,
position: FilePosition,
) -> Cancellable<Option<doc_links::DocumentationLink>> {
self.with_db(|db| doc_links::external_docs(db, &position))
target_dir: Option<&OsStr>,
sysroot: Option<&OsStr>,
) -> Cancellable<doc_links::DocumentationLinks> {
self.with_db(|db| {
doc_links::external_docs(db, &position, target_dir, sysroot).unwrap_or_default()
})
}
/// Computes parameter information at the given position.

View File

@ -32,6 +32,7 @@ pub struct CargoWorkspace {
packages: Arena<PackageData>,
targets: Arena<TargetData>,
workspace_root: AbsPathBuf,
target_directory: AbsPathBuf,
}
impl ops::Index<Package> for CargoWorkspace {
@ -414,7 +415,10 @@ impl CargoWorkspace {
let workspace_root =
AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string()));
CargoWorkspace { packages, targets, workspace_root }
let target_directory =
AbsPathBuf::assert(PathBuf::from(meta.target_directory.into_os_string()));
CargoWorkspace { packages, targets, workspace_root, target_directory }
}
pub fn packages(&self) -> impl Iterator<Item = Package> + ExactSizeIterator + '_ {
@ -432,6 +436,10 @@ impl CargoWorkspace {
&self.workspace_root
}
pub fn target_directory(&self) -> &AbsPath {
&self.target_directory
}
pub fn package_flag(&self, package: &PackageData) -> String {
if self.is_unique(&package.name) {
package.name.clone()

View File

@ -1036,6 +1036,10 @@ impl Config {
self.experimental("codeActionGroup")
}
pub fn local_docs(&self) -> bool {
self.experimental("localDocs")
}
pub fn open_server_logs(&self) -> bool {
self.experimental("openServerLogs")
}

View File

@ -40,8 +40,8 @@ use crate::{
global_state::{GlobalState, GlobalStateSnapshot},
line_index::LineEndings,
lsp_ext::{
self, CrateInfoResult, FetchDependencyListParams, FetchDependencyListResult,
PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
self, CrateInfoResult, ExternalDocsPair, ExternalDocsResponse, FetchDependencyListParams,
FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
},
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
to_proto, LspError, Result,
@ -1535,13 +1535,40 @@ 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<ExternalDocsResponse> {
let _p = profile::span("handle_open_docs");
let position = from_proto::file_position(&snap, params)?;
let remote = snap.analysis.external_docs(position)?;
let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match ws {
ProjectWorkspace::Cargo { cargo, sysroot, .. } => Some((cargo, sysroot.as_ref().ok())),
ProjectWorkspace::Json { .. } => None,
ProjectWorkspace::DetachedFiles { .. } => None,
});
Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
let (cargo, sysroot) = match ws_and_sysroot {
Some((ws, sysroot)) => (Some(ws), sysroot),
_ => (None, None),
};
let sysroot = sysroot.map(|p| p.root().as_os_str());
let target_dir = cargo.map(|cargo| cargo.target_directory()).map(|p| p.as_os_str());
let Ok(remote_urls) = snap.analysis.external_docs(position, target_dir, sysroot) else {
return if snap.config.local_docs() {
Ok(ExternalDocsResponse::WithLocal(Default::default()))
} else {
Ok(ExternalDocsResponse::Simple(None))
}
};
let web = remote_urls.web_url.and_then(|it| Url::parse(&it).ok());
let local = remote_urls.local_url.and_then(|it| Url::parse(&it).ok());
if snap.config.local_docs() {
Ok(ExternalDocsResponse::WithLocal(ExternalDocsPair { web, local }))
} else {
Ok(ExternalDocsResponse::Simple(web))
}
}
pub(crate) fn handle_open_cargo_toml(

View File

@ -508,10 +508,24 @@ pub enum ExternalDocs {}
impl Request for ExternalDocs {
type Params = lsp_types::TextDocumentPositionParams;
type Result = Option<lsp_types::Url>;
type Result = ExternalDocsResponse;
const METHOD: &'static str = "experimental/externalDocs";
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum ExternalDocsResponse {
Simple(Option<lsp_types::Url>),
WithLocal(ExternalDocsPair),
}
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExternalDocsPair {
pub web: Option<lsp_types::Url>,
pub local: Option<lsp_types::Url>,
}
pub enum OpenCargoToml {}
impl Request for OpenCargoToml {

View File

@ -1,5 +1,5 @@
<!---
lsp_ext.rs hash: fdf1afd34548abbc
lsp_ext.rs hash: 2d60bbffe70ae198
If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue:
@ -386,14 +386,26 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
## Open External Documentation
This request is sent from client to server to get a URL to documentation for the symbol under the cursor, if available.
This request is sent from the client to the server to obtain web and local URL(s) for documentation related to the symbol under the cursor, if available.
**Method** `experimental/externalDocs`
**Method:** `experimental/externalDocs`
**Request:**: `TextDocumentPositionParams`
**Request:** `TextDocumentPositionParams`
**Response** `string | null`
**Response:** `string | null`
## Local Documentation
**Experimental Client Capability:** `{ "localDocs": boolean }`
If this capability is set, the `Open External Documentation` request returned from the server will have the following structure:
```typescript
interface ExternalDocsResponse {
web?: string;
local?: string;
}
```
## Analyzer Status
@ -863,7 +875,7 @@ export interface Diagnostic {
export interface FetchDependencyListParams {}
```
**Response:**
**Response:**
```typescript
export interface FetchDependencyListResult {
crates: {
@ -873,4 +885,4 @@ export interface FetchDependencyListResult {
}[];
}
```
Returns all crates from this workspace, so it can be used create a viewTree to help navigate the dependency tree.
Returns all crates from this workspace, so it can be used create a viewTree to help navigate the dependency tree.