diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index e8c63d410a..c3976e6b7a 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -6,6 +6,7 @@ use std::{ fmt, io, + path::Path, process::{ChildStderr, ChildStdout, Command, Stdio}, time::Duration, }; @@ -21,6 +22,14 @@ pub use cargo_metadata::diagnostic::{ DiagnosticSpanMacroExpansion, }; +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum InvocationStrategy { + OnceInRoot, + PerWorkspaceWithManifestPath, + #[default] + PerWorkspace, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { CargoCommand { @@ -32,11 +41,13 @@ pub enum FlycheckConfig { features: Vec, extra_args: Vec, extra_env: FxHashMap, + invocation_strategy: InvocationStrategy, }, CustomCommand { command: String, args: Vec, extra_env: FxHashMap, + invocation_strategy: InvocationStrategy, }, } @@ -136,7 +147,9 @@ enum Restart { No, } +/// A [`FlycheckActor`] is a single check instance of a workspace. struct FlycheckActor { + /// The workspace id of this flycheck instance. id: usize, sender: Box, config: FlycheckConfig, @@ -164,9 +177,11 @@ impl FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); FlycheckActor { id, sender, config, workspace_root, cargo_handle: None } } - fn progress(&self, progress: Progress) { + + fn report_progress(&self, progress: Progress) { self.send(Message::Progress { id: self.id, progress }); } + fn next_event(&self, inbox: &Receiver) -> Option { let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); if let Ok(msg) = inbox.try_recv() { @@ -178,6 +193,7 @@ impl FlycheckActor { recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), } } + fn run(mut self, inbox: Receiver) { 'event: while let Some(event) = self.next_event(&inbox) { match event { @@ -194,7 +210,20 @@ impl FlycheckActor { } } - let command = self.check_command(); + let mut command = self.check_command(); + let invocation_strategy = self.invocation_strategy(); + match invocation_strategy { + InvocationStrategy::OnceInRoot => (), + InvocationStrategy::PerWorkspaceWithManifestPath => { + command.arg("--manifest-path"); + command.arg(<_ as AsRef>::as_ref( + &self.workspace_root.join("Cargo.toml"), + )); + } + InvocationStrategy::PerWorkspace => { + command.current_dir(&self.workspace_root); + } + } tracing::debug!(?command, "will restart flycheck"); match CargoHandle::spawn(command) { Ok(cargo_handle) => { @@ -203,10 +232,10 @@ impl FlycheckActor { "did restart flycheck" ); self.cargo_handle = Some(cargo_handle); - self.progress(Progress::DidStart); + self.report_progress(Progress::DidStart); } Err(error) => { - self.progress(Progress::DidFailToRestart(format!( + self.report_progress(Progress::DidFailToRestart(format!( "Failed to run the following command: {:?} error={}", self.check_command(), error @@ -226,11 +255,11 @@ impl FlycheckActor { self.check_command() ); } - self.progress(Progress::DidFinish(res)); + self.report_progress(Progress::DidFinish(res)); } Event::CheckEvent(Some(message)) => match message { CargoMessage::CompilerArtifact(msg) => { - self.progress(Progress::DidCheckCrate(msg.target.name)); + self.report_progress(Progress::DidCheckCrate(msg.target.name)); } CargoMessage::Diagnostic(msg) => { @@ -254,7 +283,14 @@ impl FlycheckActor { "did cancel flycheck" ); cargo_handle.cancel(); - self.progress(Progress::DidCancel); + self.report_progress(Progress::DidCancel); + } + } + + fn invocation_strategy(&self) -> InvocationStrategy { + match self.config { + FlycheckConfig::CargoCommand { invocation_strategy, .. } + | FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy, } } @@ -269,6 +305,7 @@ impl FlycheckActor { extra_args, features, extra_env, + invocation_strategy: _, } => { let mut cmd = Command::new(toolchain::cargo()); cmd.arg(command); @@ -297,7 +334,7 @@ impl FlycheckActor { cmd.envs(extra_env); cmd } - FlycheckConfig::CustomCommand { command, args, extra_env } => { + FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy: _ } => { let mut cmd = Command::new(command); cmd.args(args); cmd.envs(extra_env); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 79f6ded489..a61e38706e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -130,6 +130,14 @@ config_data! { /// /// Set to `"all"` to pass `--all-features` to Cargo. checkOnSave_features: Option = "null", + /// Specifies the invocation strategy to use when running the checkOnSave command. + /// If `per_workspace_with_manifest_path` is set, the command will be executed for each + /// workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and + /// the command will be executed from the project root. + /// If `per_workspace` is set, the command will be executed for each workspace and the + /// command will be executed from the corresponding workspace root. + /// If `once_in_root` is set, the command will be executed once in the project root. + checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", /// Whether to pass `--no-default-features` to Cargo. Defaults to /// `#rust-analyzer.cargo.noDefaultFeatures#`. checkOnSave_noDefaultFeatures: Option = "null", @@ -1094,6 +1102,13 @@ impl Config { if !self.data.checkOnSave_enable { return None; } + let invocation_strategy = match self.data.cargo_buildScripts_invocationStrategy { + InvocationStrategy::OnceInRoot => flycheck::InvocationStrategy::OnceInRoot, + InvocationStrategy::PerWorkspaceWithManifestPath => { + flycheck::InvocationStrategy::PerWorkspaceWithManifestPath + } + InvocationStrategy::PerWorkspace => flycheck::InvocationStrategy::PerWorkspace, + }; let flycheck_config = match &self.data.checkOnSave_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); @@ -1102,6 +1117,7 @@ impl Config { command, args, extra_env: self.check_on_save_extra_env(), + invocation_strategy, } } Some(_) | None => FlycheckConfig::CargoCommand { @@ -1131,6 +1147,7 @@ impl Config { }, extra_args: self.data.checkOnSave_extraArgs.clone(), extra_env: self.check_on_save_extra_env(), + invocation_strategy, }, }; Some(flycheck_config) diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index bd5741f615..5382790f6e 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -473,32 +473,45 @@ impl GlobalState { }; let sender = self.flycheck_sender.clone(); - self.flycheck = self - .workspaces - .iter() - .enumerate() - .filter_map(|(id, w)| match w { - ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())), - ProjectWorkspace::Json { project, .. } => { - // Enable flychecks for json projects if a custom flycheck command was supplied - // in the workspace configuration. - match config { - FlycheckConfig::CustomCommand { .. } => Some((id, project.path())), - _ => None, - } - } - ProjectWorkspace::DetachedFiles { .. } => None, - }) - .map(|(id, root)| { - let sender = sender.clone(); - FlycheckHandle::spawn( - id, - Box::new(move |msg| sender.send(msg).unwrap()), - config.clone(), - root.to_path_buf(), - ) - }) - .collect(); + let (FlycheckConfig::CargoCommand { invocation_strategy, .. } + | FlycheckConfig::CustomCommand { invocation_strategy, .. }) = config; + + self.flycheck = match invocation_strategy { + flycheck::InvocationStrategy::OnceInRoot => vec![FlycheckHandle::spawn( + 0, + Box::new(move |msg| sender.send(msg).unwrap()), + config.clone(), + self.config.root_path().clone(), + )], + flycheck::InvocationStrategy::PerWorkspaceWithManifestPath + | flycheck::InvocationStrategy::PerWorkspace => { + self.workspaces + .iter() + .enumerate() + .filter_map(|(id, w)| match w { + ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())), + ProjectWorkspace::Json { project, .. } => { + // Enable flychecks for json projects if a custom flycheck command was supplied + // in the workspace configuration. + match config { + FlycheckConfig::CustomCommand { .. } => Some((id, project.path())), + _ => None, + } + } + ProjectWorkspace::DetachedFiles { .. } => None, + }) + .map(|(id, root)| { + let sender = sender.clone(); + FlycheckHandle::spawn( + id, + Box::new(move |msg| sender.send(msg).unwrap()), + config.clone(), + root.to_path_buf(), + ) + }) + .collect() + } + }; } } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a5307b6315..3ced42ef72 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -129,6 +129,17 @@ List of features to activate. Defaults to Set to `"all"` to pass `--all-features` to Cargo. -- +[[rust-analyzer.checkOnSave.invocationStrategy]]rust-analyzer.checkOnSave.invocationStrategy (default: `"per_workspace"`):: ++ +-- +Specifies the invocation strategy to use when running the checkOnSave command. +If `per_workspace_with_manifest_path` is set, the command will be executed for each +workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and +the command will be executed from the project root. +If `per_workspace` is set, the command will be executed for each workspace and the +command will be executed from the corresponding workspace root. +If `once_in_root` is set, the command will be executed once in the project root. +-- [[rust-analyzer.checkOnSave.noDefaultFeatures]]rust-analyzer.checkOnSave.noDefaultFeatures (default: `null`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index b1b565106a..3af32685fd 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -561,6 +561,21 @@ } ] }, + "rust-analyzer.checkOnSave.invocationStrategy": { + "markdownDescription": "Specifies the invocation strategy to use when running the checkOnSave command.\nIf `per_workspace_with_manifest_path` is set, the command will be executed for each\nworkspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and\nthe command will be executed from the project root.\nIf `per_workspace` is set, the command will be executed for each workspace and the\ncommand will be executed from the corresponding workspace root.\nIf `once_in_root` is set, the command will be executed once in the project root.", + "default": "per_workspace", + "type": "string", + "enum": [ + "per_workspace", + "per_workspace_with_manifest_path", + "once_in_root" + ], + "enumDescriptions": [ + "The command will be executed for each workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and the command will be executed from the project root.", + "The command will be executed for each workspace and the command will be executed from the corresponding workspace root.", + "The command will be executed once in the project root." + ] + }, "rust-analyzer.checkOnSave.noDefaultFeatures": { "markdownDescription": "Whether to pass `--no-default-features` to Cargo. Defaults to\n`#rust-analyzer.cargo.noDefaultFeatures#`.", "default": null,