Be more lenient when dealing with rust-analyzer's flycheck commands (#36782)

Flycheck commands are global and makes sense to fall back to looking up
project's rust-analyzer even if the commands are run on a non-rust
buffer. If multiple rust-analyzers are found in the project, avoid
ambiguous commands and bail (as before).

Closes #ISSUE

Release Notes:

- Made it possible to run rust-analyzer's flycheck actions from anywhere
in the project
This commit is contained in:
Kirill Bulatov 2025-08-23 01:55:50 +03:00 committed by GitHub
parent 153724aad3
commit d24cad30f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 114 additions and 64 deletions

View file

@ -438,7 +438,7 @@ impl ProjectDiagnosticsEditor {
for buffer_path in diagnostics_sources.iter().cloned() { for buffer_path in diagnostics_sources.iter().cloned() {
if cx if cx
.update(|cx| { .update(|cx| {
fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx)); fetch_tasks.push(run_flycheck(project.clone(), Some(buffer_path), cx));
}) })
.is_err() .is_err()
{ {
@ -462,7 +462,7 @@ impl ProjectDiagnosticsEditor {
.iter() .iter()
.cloned() .cloned()
{ {
cancel_gasks.push(cancel_flycheck(self.project.clone(), buffer_path, cx)); cancel_gasks.push(cancel_flycheck(self.project.clone(), Some(buffer_path), cx));
} }
self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move { self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move {

View file

@ -26,6 +26,17 @@ fn is_rust_language(language: &Language) -> bool {
} }
pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) { pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) {
if editor.read(cx).project().is_some_and(|project| {
project
.read(cx)
.language_server_statuses(cx)
.any(|(_, status)| status.name == RUST_ANALYZER_NAME)
}) {
register_action(editor, window, cancel_flycheck_action);
register_action(editor, window, run_flycheck_action);
register_action(editor, window, clear_flycheck_action);
}
if editor if editor
.read(cx) .read(cx)
.buffer() .buffer()
@ -38,9 +49,6 @@ pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &
register_action(editor, window, go_to_parent_module); register_action(editor, window, go_to_parent_module);
register_action(editor, window, expand_macro_recursively); register_action(editor, window, expand_macro_recursively);
register_action(editor, window, open_docs); register_action(editor, window, open_docs);
register_action(editor, window, cancel_flycheck_action);
register_action(editor, window, run_flycheck_action);
register_action(editor, window, clear_flycheck_action);
} }
} }
@ -309,7 +317,7 @@ fn cancel_flycheck_action(
let Some(project) = &editor.project else { let Some(project) = &editor.project else {
return; return;
}; };
let Some(buffer_id) = editor let buffer_id = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -321,10 +329,7 @@ fn cancel_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}) });
else {
return;
};
cancel_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); cancel_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
} }
@ -337,7 +342,7 @@ fn run_flycheck_action(
let Some(project) = &editor.project else { let Some(project) = &editor.project else {
return; return;
}; };
let Some(buffer_id) = editor let buffer_id = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -349,10 +354,7 @@ fn run_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}) });
else {
return;
};
run_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); run_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
} }
@ -365,7 +367,7 @@ fn clear_flycheck_action(
let Some(project) = &editor.project else { let Some(project) = &editor.project else {
return; return;
}; };
let Some(buffer_id) = editor let buffer_id = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -377,9 +379,6 @@ fn clear_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}) });
else {
return;
};
clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
} }

View file

@ -9029,13 +9029,22 @@ impl LspStore {
lsp_store.update(&mut cx, |lsp_store, cx| { lsp_store.update(&mut cx, |lsp_store, cx| {
if let Some(server) = lsp_store.language_server_for_id(server_id) { if let Some(server) = lsp_store.language_server_for_id(server_id) {
let text_document = if envelope.payload.current_file_only { let text_document = if envelope.payload.current_file_only {
let buffer_id = BufferId::new(envelope.payload.buffer_id)?; let buffer_id = envelope
lsp_store .payload
.buffer_store() .buffer_id
.read(cx) .map(|id| BufferId::new(id))
.get(buffer_id) .transpose()?;
.and_then(|buffer| Some(buffer.read(cx).file()?.as_local()?.abs_path(cx))) buffer_id
.map(|path| make_text_document_identifier(&path)) .and_then(|buffer_id| {
lsp_store
.buffer_store()
.read(cx)
.get(buffer_id)
.and_then(|buffer| {
Some(buffer.read(cx).file()?.as_local()?.abs_path(cx))
})
.map(|path| make_text_document_identifier(&path))
})
.transpose()? .transpose()?
} else { } else {
None None

View file

@ -1,8 +1,8 @@
use ::serde::{Deserialize, Serialize}; use ::serde::{Deserialize, Serialize};
use anyhow::Context as _; use anyhow::Context as _;
use gpui::{App, Entity, Task, WeakEntity}; use gpui::{App, AsyncApp, Entity, Task, WeakEntity};
use language::ServerHealth; use language::{Buffer, ServerHealth};
use lsp::{LanguageServer, LanguageServerName}; use lsp::{LanguageServer, LanguageServerId, LanguageServerName};
use rpc::proto; use rpc::proto;
use crate::{LspStore, LspStoreEvent, Project, ProjectPath, lsp_store}; use crate::{LspStore, LspStoreEvent, Project, ProjectPath, lsp_store};
@ -83,31 +83,32 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
pub fn cancel_flycheck( pub fn cancel_flycheck(
project: Entity<Project>, project: Entity<Project>,
buffer_path: ProjectPath, buffer_path: Option<ProjectPath>,
cx: &mut App, cx: &mut App,
) -> Task<anyhow::Result<()>> { ) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store(); let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| { let buffer = buffer_path.map(|buffer_path| {
project.buffer_store().update(cx, |buffer_store, cx| { project.update(cx, |project, cx| {
buffer_store.open_buffer(buffer_path, cx) project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
}) })
}); });
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let buffer = buffer.await?; let buffer = match buffer {
let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| { Some(buffer) => Some(buffer.await?),
project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx) None => None,
})? };
let Some(rust_analyzer_server) = find_rust_analyzer_server(&project, buffer.as_ref(), cx)
else { else {
return Ok(()); return Ok(());
}; };
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client { if let Some((client, project_id)) = upstream_client {
let request = proto::LspExtCancelFlycheck { let request = proto::LspExtCancelFlycheck {
project_id, project_id,
buffer_id,
language_server_id: rust_analyzer_server.to_proto(), language_server_id: rust_analyzer_server.to_proto(),
}; };
client client
@ -130,28 +131,33 @@ pub fn cancel_flycheck(
pub fn run_flycheck( pub fn run_flycheck(
project: Entity<Project>, project: Entity<Project>,
buffer_path: ProjectPath, buffer_path: Option<ProjectPath>,
cx: &mut App, cx: &mut App,
) -> Task<anyhow::Result<()>> { ) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store(); let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| { let buffer = buffer_path.map(|buffer_path| {
project.buffer_store().update(cx, |buffer_store, cx| { project.update(cx, |project, cx| {
buffer_store.open_buffer(buffer_path, cx) project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
}) })
}); });
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let buffer = buffer.await?; let buffer = match buffer {
let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| { Some(buffer) => Some(buffer.await?),
project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx) None => None,
})? };
let Some(rust_analyzer_server) = find_rust_analyzer_server(&project, buffer.as_ref(), cx)
else { else {
return Ok(()); return Ok(());
}; };
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client { if let Some((client, project_id)) = upstream_client {
let buffer_id = buffer
.map(|buffer| buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto()))
.transpose()?;
let request = proto::LspExtRunFlycheck { let request = proto::LspExtRunFlycheck {
project_id, project_id,
buffer_id, buffer_id,
@ -182,31 +188,32 @@ pub fn run_flycheck(
pub fn clear_flycheck( pub fn clear_flycheck(
project: Entity<Project>, project: Entity<Project>,
buffer_path: ProjectPath, buffer_path: Option<ProjectPath>,
cx: &mut App, cx: &mut App,
) -> Task<anyhow::Result<()>> { ) -> Task<anyhow::Result<()>> {
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
let lsp_store = project.read(cx).lsp_store(); let lsp_store = project.read(cx).lsp_store();
let buffer = project.update(cx, |project, cx| { let buffer = buffer_path.map(|buffer_path| {
project.buffer_store().update(cx, |buffer_store, cx| { project.update(cx, |project, cx| {
buffer_store.open_buffer(buffer_path, cx) project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.open_buffer(buffer_path, cx)
})
}) })
}); });
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let buffer = buffer.await?; let buffer = match buffer {
let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| { Some(buffer) => Some(buffer.await?),
project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx) None => None,
})? };
let Some(rust_analyzer_server) = find_rust_analyzer_server(&project, buffer.as_ref(), cx)
else { else {
return Ok(()); return Ok(());
}; };
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
if let Some((client, project_id)) = upstream_client { if let Some((client, project_id)) = upstream_client {
let request = proto::LspExtClearFlycheck { let request = proto::LspExtClearFlycheck {
project_id, project_id,
buffer_id,
language_server_id: rust_analyzer_server.to_proto(), language_server_id: rust_analyzer_server.to_proto(),
}; };
client client
@ -226,3 +233,40 @@ pub fn clear_flycheck(
anyhow::Ok(()) anyhow::Ok(())
}) })
} }
fn find_rust_analyzer_server(
project: &Entity<Project>,
buffer: Option<&Entity<Buffer>>,
cx: &mut AsyncApp,
) -> Option<LanguageServerId> {
project
.read_with(cx, |project, cx| {
buffer
.and_then(|buffer| {
project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
})
// If no rust-analyzer found for the current buffer (e.g. `settings.json`), fall back to the project lookup
// and use project's rust-analyzer if it's the only one.
.or_else(|| {
let rust_analyzer_servers = project
.lsp_store()
.read(cx)
.language_server_statuses
.iter()
.filter_map(|(server_id, server_status)| {
if server_status.name == RUST_ANALYZER_NAME {
Some(*server_id)
} else {
None
}
})
.collect::<Vec<_>>();
if rust_analyzer_servers.len() == 1 {
rust_analyzer_servers.first().copied()
} else {
None
}
})
})
.ok()?
}

View file

@ -834,21 +834,19 @@ message LspRunnable {
message LspExtCancelFlycheck { message LspExtCancelFlycheck {
uint64 project_id = 1; uint64 project_id = 1;
uint64 buffer_id = 2; uint64 language_server_id = 2;
uint64 language_server_id = 3;
} }
message LspExtRunFlycheck { message LspExtRunFlycheck {
uint64 project_id = 1; uint64 project_id = 1;
uint64 buffer_id = 2; optional uint64 buffer_id = 2;
uint64 language_server_id = 3; uint64 language_server_id = 3;
bool current_file_only = 4; bool current_file_only = 4;
} }
message LspExtClearFlycheck { message LspExtClearFlycheck {
uint64 project_id = 1; uint64 project_id = 1;
uint64 buffer_id = 2; uint64 language_server_id = 2;
uint64 language_server_id = 3;
} }
message LspDiagnosticRelatedInformation { message LspDiagnosticRelatedInformation {