mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
flycheck: initial implementation of $saved_file
If the custom command has a $saved_file placeholder, and we know the file being saved, replace the placeholder and then run a check command. If there's a placeholder and we don't know the saved file, do nothing.
This commit is contained in:
parent
47b4dd7273
commit
cdbf54f4bd
6 changed files with 66 additions and 18 deletions
|
@ -14,7 +14,7 @@ use std::{
|
||||||
|
|
||||||
use command_group::{CommandGroup, GroupChild};
|
use command_group::{CommandGroup, GroupChild};
|
||||||
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
||||||
use paths::AbsPathBuf;
|
use paths::{AbsPath, AbsPathBuf};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use stdx::process::streaming_output;
|
use stdx::process::streaming_output;
|
||||||
|
@ -102,13 +102,15 @@ impl FlycheckHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
|
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
|
||||||
pub fn restart_workspace(&self) {
|
pub fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
|
||||||
self.sender.send(StateChange::Restart(None)).unwrap();
|
self.sender.send(StateChange::Restart { package: None, saved_file }).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule a re-start of the cargo check worker to do a package wide check.
|
/// Schedule a re-start of the cargo check worker to do a package wide check.
|
||||||
pub fn restart_for_package(&self, package: String) {
|
pub fn restart_for_package(&self, package: String) {
|
||||||
self.sender.send(StateChange::Restart(Some(package))).unwrap();
|
self.sender
|
||||||
|
.send(StateChange::Restart { package: Some(package), saved_file: None })
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop this cargo check worker.
|
/// Stop this cargo check worker.
|
||||||
|
@ -159,7 +161,7 @@ pub enum Progress {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StateChange {
|
enum StateChange {
|
||||||
Restart(Option<String>),
|
Restart { package: Option<String>, saved_file: Option<AbsPathBuf> },
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +188,8 @@ enum Event {
|
||||||
CheckEvent(Option<CargoMessage>),
|
CheckEvent(Option<CargoMessage>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
|
||||||
|
|
||||||
impl FlycheckActor {
|
impl FlycheckActor {
|
||||||
fn new(
|
fn new(
|
||||||
id: usize,
|
id: usize,
|
||||||
|
@ -221,7 +225,7 @@ impl FlycheckActor {
|
||||||
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
|
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
|
||||||
self.cancel_check_process();
|
self.cancel_check_process();
|
||||||
}
|
}
|
||||||
Event::RequestStateChange(StateChange::Restart(package)) => {
|
Event::RequestStateChange(StateChange::Restart { package, saved_file }) => {
|
||||||
// Cancel the previously spawned process
|
// Cancel the previously spawned process
|
||||||
self.cancel_check_process();
|
self.cancel_check_process();
|
||||||
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
|
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
|
||||||
|
@ -231,7 +235,11 @@ impl FlycheckActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = self.check_command(package.as_deref());
|
let command =
|
||||||
|
match self.check_command(package.as_deref(), saved_file.as_deref()) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
let formatted_command = format!("{:?}", command);
|
let formatted_command = format!("{:?}", command);
|
||||||
|
|
||||||
tracing::debug!(?command, "will restart flycheck");
|
tracing::debug!(?command, "will restart flycheck");
|
||||||
|
@ -305,7 +313,14 @@ impl FlycheckActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_command(&self, package: Option<&str>) -> Command {
|
/// Construct a `Command` object for checking the user's code. If the user
|
||||||
|
/// has specified a custom command with placeholders that we cannot fill,
|
||||||
|
/// return None.
|
||||||
|
fn check_command(
|
||||||
|
&self,
|
||||||
|
package: Option<&str>,
|
||||||
|
saved_file: Option<&AbsPath>,
|
||||||
|
) -> Option<Command> {
|
||||||
let (mut cmd, args) = match &self.config {
|
let (mut cmd, args) = match &self.config {
|
||||||
FlycheckConfig::CargoCommand {
|
FlycheckConfig::CargoCommand {
|
||||||
command,
|
command,
|
||||||
|
@ -358,7 +373,7 @@ impl FlycheckActor {
|
||||||
cmd.arg("--target-dir").arg(target_dir);
|
cmd.arg("--target-dir").arg(target_dir);
|
||||||
}
|
}
|
||||||
cmd.envs(extra_env);
|
cmd.envs(extra_env);
|
||||||
(cmd, extra_args)
|
(cmd, extra_args.clone())
|
||||||
}
|
}
|
||||||
FlycheckConfig::CustomCommand {
|
FlycheckConfig::CustomCommand {
|
||||||
command,
|
command,
|
||||||
|
@ -387,12 +402,34 @@ impl FlycheckActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(cmd, args)
|
if args.contains(&SAVED_FILE_PLACEHOLDER.to_owned()) {
|
||||||
|
// If the custom command has a $saved_file placeholder, and
|
||||||
|
// we're saving a file, replace the placeholder in the arguments.
|
||||||
|
if let Some(saved_file) = saved_file {
|
||||||
|
let args = args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
if arg == SAVED_FILE_PLACEHOLDER {
|
||||||
|
saved_file.to_string()
|
||||||
|
} else {
|
||||||
|
arg.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(cmd, args)
|
||||||
|
} else {
|
||||||
|
// The custom command has a $saved_file placeholder,
|
||||||
|
// but we had an IDE event that wasn't a file save. Do nothing.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(cmd, args.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
cmd
|
Some(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&self, check_task: Message) {
|
fn send(&self, check_task: Message) {
|
||||||
|
|
|
@ -209,6 +209,11 @@ config_data! {
|
||||||
/// by changing `#rust-analyzer.check.invocationStrategy#` and
|
/// by changing `#rust-analyzer.check.invocationStrategy#` and
|
||||||
/// `#rust-analyzer.check.invocationLocation#`.
|
/// `#rust-analyzer.check.invocationLocation#`.
|
||||||
///
|
///
|
||||||
|
/// If `$saved_file` is part of the command, rust-analyzer will pass
|
||||||
|
/// the absolute path of the saved file to the provided command. This is
|
||||||
|
/// intended to be used with non-Cargo build systems.
|
||||||
|
/// Note that `$saved_file` is experimental and may be removed in the futureg.
|
||||||
|
///
|
||||||
/// An example command would be:
|
/// An example command would be:
|
||||||
///
|
///
|
||||||
/// ```bash
|
/// ```bash
|
||||||
|
|
|
@ -168,7 +168,7 @@ pub(crate) fn handle_did_save_text_document(
|
||||||
} else if state.config.check_on_save() {
|
} else if state.config.check_on_save() {
|
||||||
// No specific flycheck was triggered, so let's trigger all of them.
|
// No specific flycheck was triggered, so let's trigger all of them.
|
||||||
for flycheck in state.flycheck.iter() {
|
for flycheck in state.flycheck.iter() {
|
||||||
flycheck.restart_workspace();
|
flycheck.restart_workspace(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -314,6 +314,8 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
|
||||||
Some((idx, package))
|
Some((idx, package))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let saved_file = vfs_path.as_path().map(|p| p.to_owned());
|
||||||
|
|
||||||
// Find and trigger corresponding flychecks
|
// Find and trigger corresponding flychecks
|
||||||
for flycheck in world.flycheck.iter() {
|
for flycheck in world.flycheck.iter() {
|
||||||
for (id, package) in workspace_ids.clone() {
|
for (id, package) in workspace_ids.clone() {
|
||||||
|
@ -321,7 +323,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
|
||||||
updated = true;
|
updated = true;
|
||||||
match package.filter(|_| !world.config.flycheck_workspace()) {
|
match package.filter(|_| !world.config.flycheck_workspace()) {
|
||||||
Some(package) => flycheck.restart_for_package(package),
|
Some(package) => flycheck.restart_for_package(package),
|
||||||
None => flycheck.restart_workspace(),
|
None => flycheck.restart_workspace(saved_file.clone()),
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -330,7 +332,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
|
||||||
// No specific flycheck was triggered, so let's trigger all of them.
|
// No specific flycheck was triggered, so let's trigger all of them.
|
||||||
if !updated {
|
if !updated {
|
||||||
for flycheck in world.flycheck.iter() {
|
for flycheck in world.flycheck.iter() {
|
||||||
flycheck.restart_workspace();
|
flycheck.restart_workspace(saved_file.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -372,7 +374,7 @@ pub(crate) fn handle_run_flycheck(
|
||||||
}
|
}
|
||||||
// No specific flycheck was triggered, so let's trigger all of them.
|
// No specific flycheck was triggered, so let's trigger all of them.
|
||||||
for flycheck in state.flycheck.iter() {
|
for flycheck in state.flycheck.iter() {
|
||||||
flycheck.restart_workspace();
|
flycheck.restart_workspace(None);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ use std::{
|
||||||
|
|
||||||
use always_assert::always;
|
use always_assert::always;
|
||||||
use crossbeam_channel::{select, Receiver};
|
use crossbeam_channel::{select, Receiver};
|
||||||
use flycheck::FlycheckHandle;
|
|
||||||
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
|
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
|
||||||
use lsp_server::{Connection, Notification, Request};
|
use lsp_server::{Connection, Notification, Request};
|
||||||
use lsp_types::notification::Notification as _;
|
use lsp_types::notification::Notification as _;
|
||||||
|
@ -337,7 +336,7 @@ impl GlobalState {
|
||||||
if became_quiescent {
|
if became_quiescent {
|
||||||
if self.config.check_on_save() {
|
if self.config.check_on_save() {
|
||||||
// Project has loaded properly, kick off initial flycheck
|
// Project has loaded properly, kick off initial flycheck
|
||||||
self.flycheck.iter().for_each(FlycheckHandle::restart_workspace);
|
self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
|
||||||
}
|
}
|
||||||
if self.config.prefill_caches() {
|
if self.config.prefill_caches() {
|
||||||
self.prime_caches_queue.request_op("became quiescent".to_owned(), ());
|
self.prime_caches_queue.request_op("became quiescent".to_owned(), ());
|
||||||
|
|
|
@ -234,6 +234,11 @@ each of them, with the working directory being the workspace root
|
||||||
by changing `#rust-analyzer.check.invocationStrategy#` and
|
by changing `#rust-analyzer.check.invocationStrategy#` and
|
||||||
`#rust-analyzer.check.invocationLocation#`.
|
`#rust-analyzer.check.invocationLocation#`.
|
||||||
|
|
||||||
|
If `$saved_file` is part of the command, rust-analyzer will pass
|
||||||
|
the absolute path of the saved file to the provided command. This is
|
||||||
|
intended to be used with non-Cargo build systems.
|
||||||
|
Note that `$saved_file` is experimental and may be removed in the futureg.
|
||||||
|
|
||||||
An example command would be:
|
An example command would be:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -775,7 +775,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rust-analyzer.check.overrideCommand": {
|
"rust-analyzer.check.overrideCommand": {
|
||||||
"markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#` and\n`#rust-analyzer.check.invocationLocation#`.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
|
"markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#` and\n`#rust-analyzer.check.invocationLocation#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the futureg.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
|
||||||
"default": null,
|
"default": null,
|
||||||
"type": [
|
"type": [
|
||||||
"null",
|
"null",
|
||||||
|
|
Loading…
Reference in a new issue