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,
|
||||
OpenProposedChangesEditor,
|
||||
OpenFile,
|
||||
OpenDocs,
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
Outdent,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
||||
use language::Language;
|
||||
|
@ -7,7 +9,7 @@ use text::ToPointUtf16;
|
|||
|
||||
use crate::{
|
||||
element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
|
||||
ExpandMacroRecursively,
|
||||
ExpandMacroRecursively, OpenDocs,
|
||||
};
|
||||
|
||||
const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
|
@ -24,6 +26,7 @@ pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
|||
.is_some()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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!({
|
||||
"serverStatusNotification": true,
|
||||
"localDocs": true,
|
||||
})),
|
||||
window: Some(WindowClientCapabilities {
|
||||
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 {}
|
||||
|
||||
impl lsp::request::Request for LspSwitchSourceHeader {
|
||||
|
|
|
@ -276,6 +276,7 @@ message Envelope {
|
|||
|
||||
LanguageServerPromptRequest language_server_prompt_request = 268;
|
||||
LanguageServerPromptResponse language_server_prompt_response = 269;
|
||||
|
||||
GitBranches git_branches = 270;
|
||||
GitBranchesResponse git_branches_response = 271;
|
||||
|
||||
|
@ -293,7 +294,10 @@ message Envelope {
|
|||
GetPanicFiles get_panic_files = 280;
|
||||
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;
|
||||
|
@ -2024,6 +2028,17 @@ message LspExtExpandMacroResponse {
|
|||
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 {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
|
|
|
@ -314,6 +314,8 @@ messages!(
|
|||
(UsersResponse, Foreground),
|
||||
(LspExtExpandMacro, Background),
|
||||
(LspExtExpandMacroResponse, Background),
|
||||
(LspExtOpenDocs, Background),
|
||||
(LspExtOpenDocsResponse, Background),
|
||||
(SetRoomParticipantRole, Foreground),
|
||||
(BlameBuffer, Foreground),
|
||||
(BlameBufferResponse, Foreground),
|
||||
|
@ -464,6 +466,7 @@ request_messages!(
|
|||
(UpdateProject, Ack),
|
||||
(UpdateWorktree, Ack),
|
||||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||
(LspExtOpenDocs, LspExtOpenDocsResponse),
|
||||
(SetRoomParticipantRole, Ack),
|
||||
(BlameBuffer, BlameBufferResponse),
|
||||
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
||||
|
@ -552,6 +555,7 @@ entity_messages!(
|
|||
UpdateWorktree,
|
||||
UpdateWorktreeSettings,
|
||||
LspExtExpandMacro,
|
||||
LspExtOpenDocs,
|
||||
AdvertiseContexts,
|
||||
OpenContext,
|
||||
CreateContext,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue