diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index a1df0b6d7dd..a2d539cf6ca 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -138,6 +138,19 @@ impl Request for CancelFlycheck { const METHOD: &'static str = "rust-analyzer/cancelFlycheck"; } +pub enum RunFlycheck {} + +impl Notification for RunFlycheck { + 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<TextDocumentIdentifier>, +} + pub enum MatchingBrace {} impl Request for MatchingBrace { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 274588ce0e0..d979317b218 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -703,6 +703,88 @@ impl GlobalState { /// Handles an incoming notification. fn on_notification(&mut self, not: Notification) -> Result<()> { + // FIXME: Move these implementations out into a module similar to on_request + fn run_flycheck(this: &mut GlobalState, vfs_path: VfsPath) -> bool { + let file_id = this.vfs.read().0.file_id(&vfs_path); + if let Some(file_id) = file_id { + let world = this.snapshot(); + let mut updated = false; + let task = move || -> std::result::Result<(), ide::Cancelled> { + // Trigger flychecks for all workspaces that depend on the saved file + // Crates containing or depending on the saved file + let crate_ids: Vec<_> = world + .analysis + .crates_for(file_id)? + .into_iter() + .flat_map(|id| world.analysis.transitive_rev_deps(id)) + .flatten() + .sorted() + .unique() + .collect(); + + let crate_root_paths: Vec<_> = crate_ids + .iter() + .filter_map(|&crate_id| { + world + .analysis + .crate_root(crate_id) + .map(|file_id| { + world + .file_id_to_file_path(file_id) + .as_path() + .map(ToOwned::to_owned) + }) + .transpose() + }) + .collect::<ide::Cancellable<_>>()?; + let crate_root_paths: Vec<_> = + crate_root_paths.iter().map(Deref::deref).collect(); + + // Find all workspaces that have at least one target containing the saved file + let workspace_ids = + world.workspaces.iter().enumerate().filter(|(_, ws)| match ws { + project_model::ProjectWorkspace::Cargo { cargo, .. } => { + cargo.packages().any(|pkg| { + cargo[pkg].targets.iter().any(|&it| { + crate_root_paths.contains(&cargo[it].root.as_path()) + }) + }) + } + project_model::ProjectWorkspace::Json { project, .. } => project + .crates() + .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)), + project_model::ProjectWorkspace::DetachedFiles { .. } => false, + }); + + // Find and trigger corresponding flychecks + for flycheck in world.flycheck.iter() { + for (id, _) in workspace_ids.clone() { + if id == flycheck.id() { + updated = true; + flycheck.restart(); + continue; + } + } + } + // No specific flycheck was triggered, so let's trigger all of them. + if !updated { + for flycheck in world.flycheck.iter() { + flycheck.restart(); + } + } + Ok(()) + }; + this.task_pool.handle.spawn_with_sender(move |_| { + if let Err(e) = std::panic::catch_unwind(task) { + tracing::error!("flycheck task panicked: {e:?}") + } + }); + true + } else { + false + } + } + NotificationDispatcher { not: Some(not), global_state: self } .on::<lsp_types::notification::Cancel>(|this, params| { let id: lsp_server::RequestId = match params.id { @@ -782,6 +864,20 @@ impl GlobalState { } Ok(()) })? + .on::<lsp_ext::RunFlycheck>(|this, params| { + if let Some(text_document) = params.text_document { + if let Ok(vfs_path) = from_proto::vfs_path(&text_document.uri) { + if run_flycheck(this, vfs_path) { + return Ok(()); + } + } + } + // No specific flycheck was triggered, so let's trigger all of them. + for flycheck in this.flycheck.iter() { + flycheck.restart(); + } + Ok(()) + })? .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| { if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { // Re-fetch workspaces if a workspace related file has changed @@ -792,82 +888,7 @@ impl GlobalState { } } - let file_id = this.vfs.read().0.file_id(&vfs_path); - if let Some(file_id) = file_id { - let world = this.snapshot(); - let mut updated = false; - let task = move || -> std::result::Result<(), ide::Cancelled> { - // Trigger flychecks for all workspaces that depend on the saved file - // Crates containing or depending on the saved file - let crate_ids: Vec<_> = world - .analysis - .crates_for(file_id)? - .into_iter() - .flat_map(|id| world.analysis.transitive_rev_deps(id)) - .flatten() - .sorted() - .unique() - .collect(); - - let crate_root_paths: Vec<_> = crate_ids - .iter() - .filter_map(|&crate_id| { - world - .analysis - .crate_root(crate_id) - .map(|file_id| { - world - .file_id_to_file_path(file_id) - .as_path() - .map(ToOwned::to_owned) - }) - .transpose() - }) - .collect::<ide::Cancellable<_>>()?; - let crate_root_paths: Vec<_> = - crate_root_paths.iter().map(Deref::deref).collect(); - - // Find all workspaces that have at least one target containing the saved file - let workspace_ids = - world.workspaces.iter().enumerate().filter(|(_, ws)| match ws { - project_model::ProjectWorkspace::Cargo { cargo, .. } => { - cargo.packages().any(|pkg| { - cargo[pkg].targets.iter().any(|&it| { - crate_root_paths.contains(&cargo[it].root.as_path()) - }) - }) - } - project_model::ProjectWorkspace::Json { project, .. } => { - project.crates().any(|(c, _)| { - crate_ids.iter().any(|&crate_id| crate_id == c) - }) - } - project_model::ProjectWorkspace::DetachedFiles { .. } => false, - }); - - // Find and trigger corresponding flychecks - for flycheck in world.flycheck.iter() { - for (id, _) in workspace_ids.clone() { - if id == flycheck.id() { - updated = true; - flycheck.restart(); - continue; - } - } - } - // No specific flycheck was triggered, so let's trigger all of them. - if !updated { - for flycheck in world.flycheck.iter() { - flycheck.restart(); - } - } - Ok(()) - }; - this.task_pool.handle.spawn_with_sender(move |_| { - if let Err(e) = std::panic::catch_unwind(task) { - tracing::error!("DidSaveTextDocument flycheck task panicked: {e:?}") - } - }); + if run_flycheck(this, vfs_path) { return Ok(()); } } diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 5aced035d52..308a92bebe0 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp_ext.rs hash: 61fe425627f9baaa +lsp_ext.rs hash: 1cb29d3afa36e743 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: diff --git a/editors/code/package.json b/editors/code/package.json index 5b83701a682..bfaad1edcea 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -246,6 +246,11 @@ "command": "rust-analyzer.cancelFlycheck", "title": "Cancel running flychecks", "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.runFlycheck", + "title": "Run flycheck", + "category": "rust-analyzer" } ], "keybindings": [ diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 312087e4cff..e0b4bb63c31 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -788,8 +788,17 @@ export function openDocs(ctx: CtxInit): Cmd { export function cancelFlycheck(ctx: CtxInit): Cmd { return async () => { + await ctx.client.sendRequest(ra.cancelFlycheck); + }; +} + +export function runFlycheck(ctx: CtxInit): Cmd { + return async () => { + const editor = ctx.activeRustEditor; const client = ctx.client; - await client.sendRequest(ra.cancelFlycheck); + const params = editor ? { uri: editor.document.uri.toString() } : null; + + await client.sendNotification(ra.runFlycheck, { textDocument: params }); }; } @@ -797,12 +806,12 @@ export function resolveCodeAction(ctx: CtxInit): Cmd { return async (params: lc.CodeAction) => { const client = ctx.client; params.command = undefined; - const item = await client?.sendRequest(lc.CodeActionResolveRequest.type, params); + const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params); if (!item?.edit) { return; } const itemEdit = item.edit; - const edit = await client?.protocol2CodeConverter.asWorkspaceEdit(itemEdit); + const edit = await client.protocol2CodeConverter.asWorkspaceEdit(itemEdit); // filter out all text edits and recreate the WorkspaceEdit without them so we can apply // snippet edits on our own const lcFileSystemEdit = { diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 875261c48a6..78da4e959c6 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -81,6 +81,10 @@ export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, Te export const cancelFlycheck = new lc.RequestType0<void, void>("rust-analyzer/cancelFlycheck"); +export const runFlycheck = new lc.NotificationType<{ + textDocument: lc.TextDocumentIdentifier | null; +}>("rust-analyzer/runFlycheck"); + // Experimental extensions export interface SsrParams { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 01a1a2db7fd..c5fc44b4f9f 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -150,6 +150,7 @@ function createCommands(): Record<string, CommandFactory> { moveItemUp: { enabled: commands.moveItemUp }, moveItemDown: { enabled: commands.moveItemDown }, cancelFlycheck: { enabled: commands.cancelFlycheck }, + runFlycheck: { enabled: commands.runFlycheck }, ssr: { enabled: commands.ssr }, serverVersion: { enabled: commands.serverVersion }, // Internal commands which are invoked by the server.