Use rust-analyzer's flycheck as source of cargo diagnostics (#29779)

Follow-up of https://github.com/zed-industries/zed/pull/29706

Instead of doing `cargo check` manually, use rust-analyzer's flycheck:
at the cost of more sophisticated check command configuration, we keep
much less code in Zed, and get a proper progress report.

User-facing UI does not change except `diagnostics_fetch_command` and
`env` settings removed from the diagnostics settings.

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-05-02 10:07:51 +03:00 committed by GitHub
parent 672a1dd553
commit ba59305510
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 520 additions and 1071 deletions

View file

@ -25,9 +25,9 @@ use std::{
use task::TaskTemplate;
use text::{BufferId, PointUtf16, ToPointUtf16};
pub enum LspExpandMacro {}
pub enum LspExtExpandMacro {}
impl lsp::request::Request for LspExpandMacro {
impl lsp::request::Request for LspExtExpandMacro {
type Params = ExpandMacroParams;
type Result = Option<ExpandedMacro>;
const METHOD: &'static str = "rust-analyzer/expandMacro";
@ -60,7 +60,7 @@ pub struct ExpandMacro {
#[async_trait(?Send)]
impl LspCommand for ExpandMacro {
type Response = ExpandedMacro;
type LspRequest = LspExpandMacro;
type LspRequest = LspExtExpandMacro;
type ProtoRequest = proto::LspExtExpandMacro;
fn display_name(&self) -> &str {
@ -753,3 +753,33 @@ impl LspCommand for GetLspRunnables {
BufferId::new(message.buffer_id)
}
}
#[derive(Debug)]
pub struct LspExtCancelFlycheck {}
#[derive(Debug)]
pub struct LspExtRunFlycheck {}
#[derive(Debug)]
pub struct LspExtClearFlycheck {}
impl lsp::notification::Notification for LspExtCancelFlycheck {
type Params = ();
const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
}
impl lsp::notification::Notification for LspExtRunFlycheck {
type Params = RunFlycheckParams;
const METHOD: &'static str = "rust-analyzer/runFlycheck";
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RunFlycheckParams {
pub text_document: Option<lsp::TextDocumentIdentifier>,
}
impl lsp::notification::Notification for LspExtClearFlycheck {
type Params = ();
const METHOD: &'static str = "rust-analyzer/clearFlycheck";
}

View file

@ -1,8 +1,12 @@
use ::serde::{Deserialize, Serialize};
use gpui::{PromptLevel, WeakEntity};
use anyhow::Context as _;
use gpui::{App, Entity, PromptLevel, Task, WeakEntity};
use lsp::LanguageServer;
use rpc::proto;
use crate::{LanguageServerPromptRequest, LspStore, LspStoreEvent};
use crate::{
LanguageServerPromptRequest, LspStore, LspStoreEvent, Project, ProjectPath, lsp_store,
};
pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
@ -79,3 +83,161 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
})
.detach();
}
pub fn cancel_flycheck(
project: Entity<Project>,
buffer_path: ProjectPath,
cx: &mut App,
) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
});
cx.spawn(async move |cx| {
let buffer = buffer.await?;
let Some(rust_analyzer_server) = project
.update(cx, |project, cx| {
buffer.update(cx, |buffer, cx| {
project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
})
})?
.await
else {
return Ok(());
};
let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client {
let request = proto::LspExtCancelFlycheck {
project_id,
buffer_id,
language_server_id: rust_analyzer_server.to_proto(),
};
client
.request(request)
.await
.context("lsp ext cancel flycheck proto request")?;
} else {
lsp_store
.update(cx, |lsp_store, _| {
if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
server.notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())?;
}
anyhow::Ok(())
})?
.context("lsp ext cancel flycheck")?;
};
anyhow::Ok(())
})
}
pub fn run_flycheck(
project: Entity<Project>,
buffer_path: ProjectPath,
cx: &mut App,
) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
});
cx.spawn(async move |cx| {
let buffer = buffer.await?;
let Some(rust_analyzer_server) = project
.update(cx, |project, cx| {
buffer.update(cx, |buffer, cx| {
project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
})
})?
.await
else {
return Ok(());
};
let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client {
let request = proto::LspExtRunFlycheck {
project_id,
buffer_id,
language_server_id: rust_analyzer_server.to_proto(),
current_file_only: false,
};
client
.request(request)
.await
.context("lsp ext run flycheck proto request")?;
} else {
lsp_store
.update(cx, |lsp_store, _| {
if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
server.notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
&lsp_store::lsp_ext_command::RunFlycheckParams {
text_document: None,
},
)?;
}
anyhow::Ok(())
})?
.context("lsp ext run flycheck")?;
};
anyhow::Ok(())
})
}
pub fn clear_flycheck(
project: Entity<Project>,
buffer_path: ProjectPath,
cx: &mut App,
) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
});
cx.spawn(async move |cx| {
let buffer = buffer.await?;
let Some(rust_analyzer_server) = project
.update(cx, |project, cx| {
buffer.update(cx, |buffer, cx| {
project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
})
})?
.await
else {
return Ok(());
};
let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client {
let request = proto::LspExtClearFlycheck {
project_id,
buffer_id,
language_server_id: rust_analyzer_server.to_proto(),
};
client
.request(request)
.await
.context("lsp ext clear flycheck proto request")?;
} else {
lsp_store
.update(cx, |lsp_store, _| {
if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
server.notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())?;
}
anyhow::Ok(())
})?
.context("lsp ext clear flycheck")?;
};
anyhow::Ok(())
})
}