lsp: Retrieve links to documentation for the given symbol (#19233)
Closes #18924 Release Notes: - Added an `editor:OpenDocs` action to open links to documentation via rust-analyzer
This commit is contained in:
parent
f9990b42fa
commit
2d3476530e
6 changed files with 213 additions and 2 deletions
|
@ -297,6 +297,7 @@ gpui::actions!(
|
||||||
OpenExcerptsSplit,
|
OpenExcerptsSplit,
|
||||||
OpenProposedChangesEditor,
|
OpenProposedChangesEditor,
|
||||||
OpenFile,
|
OpenFile,
|
||||||
|
OpenDocs,
|
||||||
OpenPermalinkToLine,
|
OpenPermalinkToLine,
|
||||||
OpenUrl,
|
OpenUrl,
|
||||||
Outdent,
|
Outdent,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
||||||
use language::Language;
|
use language::Language;
|
||||||
|
@ -7,7 +9,7 @@ use text::ToPointUtf16;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
|
element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
|
||||||
ExpandMacroRecursively,
|
ExpandMacroRecursively, OpenDocs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||||
|
@ -24,6 +26,7 @@ pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
register_action(editor, cx, expand_macro_recursively);
|
register_action(editor, cx, expand_macro_recursively);
|
||||||
|
register_action(editor, cx, open_docs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,3 +97,64 @@ pub fn expand_macro_recursively(
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<'_, Editor>) {
|
||||||
|
if editor.selections.count() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(project) = &editor.project else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(workspace) = editor.workspace() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((trigger_anchor, _rust_language, server_to_query, buffer)) =
|
||||||
|
find_specific_language_server_in_selection(
|
||||||
|
editor,
|
||||||
|
cx,
|
||||||
|
is_rust_language,
|
||||||
|
RUST_ANALYZER_NAME,
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let project = project.clone();
|
||||||
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||||
|
let open_docs_task = project.update(cx, |project, cx| {
|
||||||
|
project.request_lsp(
|
||||||
|
buffer,
|
||||||
|
project::LanguageServerToQuery::Other(server_to_query),
|
||||||
|
project::lsp_ext_command::OpenDocs { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(|_editor, mut cx| async move {
|
||||||
|
let docs_urls = open_docs_task.await.context("open docs")?;
|
||||||
|
if docs_urls.is_empty() {
|
||||||
|
log::debug!("Empty docs urls for position {position:?}");
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
log::debug!("{:?}", docs_urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.update(&mut cx, |_workspace, cx| {
|
||||||
|
// Check if the local document exists, otherwise fallback to the online document.
|
||||||
|
// Open with the default browser.
|
||||||
|
if let Some(local_url) = docs_urls.local {
|
||||||
|
if fs::metadata(Path::new(&local_url[8..])).is_ok() {
|
||||||
|
cx.open_url(&local_url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(web_url) = docs_urls.web {
|
||||||
|
cx.open_url(&web_url);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
|
@ -762,6 +762,7 @@ impl LanguageServer {
|
||||||
}),
|
}),
|
||||||
experimental: Some(json!({
|
experimental: Some(json!({
|
||||||
"serverStatusNotification": true,
|
"serverStatusNotification": true,
|
||||||
|
"localDocs": true,
|
||||||
})),
|
})),
|
||||||
window: Some(WindowClientCapabilities {
|
window: Some(WindowClientCapabilities {
|
||||||
work_done_progress: Some(true),
|
work_done_progress: Some(true),
|
||||||
|
|
|
@ -134,6 +134,132 @@ impl LspCommand for ExpandMacro {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum LspOpenDocs {}
|
||||||
|
|
||||||
|
impl lsp::request::Request for LspOpenDocs {
|
||||||
|
type Params = OpenDocsParams;
|
||||||
|
type Result = Option<DocsUrls>;
|
||||||
|
const METHOD: &'static str = "experimental/externalDocs";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OpenDocsParams {
|
||||||
|
pub text_document: lsp::TextDocumentIdentifier,
|
||||||
|
pub position: lsp::Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DocsUrls {
|
||||||
|
pub web: Option<String>,
|
||||||
|
pub local: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocsUrls {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.web.is_none() && self.local.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OpenDocs {
|
||||||
|
pub position: PointUtf16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl LspCommand for OpenDocs {
|
||||||
|
type Response = DocsUrls;
|
||||||
|
type LspRequest = LspOpenDocs;
|
||||||
|
type ProtoRequest = proto::LspExtOpenDocs;
|
||||||
|
|
||||||
|
fn to_lsp(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
_: &Buffer,
|
||||||
|
_: &Arc<LanguageServer>,
|
||||||
|
_: &AppContext,
|
||||||
|
) -> OpenDocsParams {
|
||||||
|
OpenDocsParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier {
|
||||||
|
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||||
|
},
|
||||||
|
position: point_to_lsp(self.position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_lsp(
|
||||||
|
self,
|
||||||
|
message: Option<DocsUrls>,
|
||||||
|
_: Model<LspStore>,
|
||||||
|
_: Model<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<DocsUrls> {
|
||||||
|
Ok(message
|
||||||
|
.map(|message| DocsUrls {
|
||||||
|
web: message.web,
|
||||||
|
local: message.local,
|
||||||
|
})
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtOpenDocs {
|
||||||
|
proto::LspExtOpenDocs {
|
||||||
|
project_id,
|
||||||
|
buffer_id: buffer.remote_id().into(),
|
||||||
|
position: Some(language::proto::serialize_anchor(
|
||||||
|
&buffer.anchor_before(self.position),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_proto(
|
||||||
|
message: Self::ProtoRequest,
|
||||||
|
_: Model<LspStore>,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let position = message
|
||||||
|
.position
|
||||||
|
.and_then(deserialize_anchor)
|
||||||
|
.context("invalid position")?;
|
||||||
|
Ok(Self {
|
||||||
|
position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_to_proto(
|
||||||
|
response: DocsUrls,
|
||||||
|
_: &mut LspStore,
|
||||||
|
_: PeerId,
|
||||||
|
_: &clock::Global,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) -> proto::LspExtOpenDocsResponse {
|
||||||
|
proto::LspExtOpenDocsResponse {
|
||||||
|
web: response.web,
|
||||||
|
local: response.local,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_proto(
|
||||||
|
self,
|
||||||
|
message: proto::LspExtOpenDocsResponse,
|
||||||
|
_: Model<LspStore>,
|
||||||
|
_: Model<Buffer>,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<DocsUrls> {
|
||||||
|
Ok(DocsUrls {
|
||||||
|
web: message.web,
|
||||||
|
local: message.local,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_id_from_proto(message: &proto::LspExtOpenDocs) -> Result<BufferId> {
|
||||||
|
BufferId::new(message.buffer_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum LspSwitchSourceHeader {}
|
pub enum LspSwitchSourceHeader {}
|
||||||
|
|
||||||
impl lsp::request::Request for LspSwitchSourceHeader {
|
impl lsp::request::Request for LspSwitchSourceHeader {
|
||||||
|
|
|
@ -276,6 +276,7 @@ message Envelope {
|
||||||
|
|
||||||
LanguageServerPromptRequest language_server_prompt_request = 268;
|
LanguageServerPromptRequest language_server_prompt_request = 268;
|
||||||
LanguageServerPromptResponse language_server_prompt_response = 269;
|
LanguageServerPromptResponse language_server_prompt_response = 269;
|
||||||
|
|
||||||
GitBranches git_branches = 270;
|
GitBranches git_branches = 270;
|
||||||
GitBranchesResponse git_branches_response = 271;
|
GitBranchesResponse git_branches_response = 271;
|
||||||
|
|
||||||
|
@ -293,7 +294,10 @@ message Envelope {
|
||||||
GetPanicFiles get_panic_files = 280;
|
GetPanicFiles get_panic_files = 280;
|
||||||
GetPanicFilesResponse get_panic_files_response = 281;
|
GetPanicFilesResponse get_panic_files_response = 281;
|
||||||
|
|
||||||
CancelLanguageServerWork cancel_language_server_work = 282; // current max
|
CancelLanguageServerWork cancel_language_server_work = 282;
|
||||||
|
|
||||||
|
LspExtOpenDocs lsp_ext_open_docs = 283;
|
||||||
|
LspExtOpenDocsResponse lsp_ext_open_docs_response = 284; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
@ -2024,6 +2028,17 @@ message LspExtExpandMacroResponse {
|
||||||
string expansion = 2;
|
string expansion = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LspExtOpenDocs {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 buffer_id = 2;
|
||||||
|
Anchor position = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspExtOpenDocsResponse {
|
||||||
|
optional string web = 1;
|
||||||
|
optional string local = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message LspExtSwitchSourceHeader {
|
message LspExtSwitchSourceHeader {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
|
|
|
@ -314,6 +314,8 @@ messages!(
|
||||||
(UsersResponse, Foreground),
|
(UsersResponse, Foreground),
|
||||||
(LspExtExpandMacro, Background),
|
(LspExtExpandMacro, Background),
|
||||||
(LspExtExpandMacroResponse, Background),
|
(LspExtExpandMacroResponse, Background),
|
||||||
|
(LspExtOpenDocs, Background),
|
||||||
|
(LspExtOpenDocsResponse, Background),
|
||||||
(SetRoomParticipantRole, Foreground),
|
(SetRoomParticipantRole, Foreground),
|
||||||
(BlameBuffer, Foreground),
|
(BlameBuffer, Foreground),
|
||||||
(BlameBufferResponse, Foreground),
|
(BlameBufferResponse, Foreground),
|
||||||
|
@ -464,6 +466,7 @@ request_messages!(
|
||||||
(UpdateProject, Ack),
|
(UpdateProject, Ack),
|
||||||
(UpdateWorktree, Ack),
|
(UpdateWorktree, Ack),
|
||||||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||||
|
(LspExtOpenDocs, LspExtOpenDocsResponse),
|
||||||
(SetRoomParticipantRole, Ack),
|
(SetRoomParticipantRole, Ack),
|
||||||
(BlameBuffer, BlameBufferResponse),
|
(BlameBuffer, BlameBufferResponse),
|
||||||
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
||||||
|
@ -552,6 +555,7 @@ entity_messages!(
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
UpdateWorktreeSettings,
|
UpdateWorktreeSettings,
|
||||||
LspExtExpandMacro,
|
LspExtExpandMacro,
|
||||||
|
LspExtOpenDocs,
|
||||||
AdvertiseContexts,
|
AdvertiseContexts,
|
||||||
OpenContext,
|
OpenContext,
|
||||||
CreateContext,
|
CreateContext,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue