Skip to content

Commit b5eafbf

Browse files
committed
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 run a check command. If there's a placeholder and we don't know the saved file, do nothing.
1 parent ad73597 commit b5eafbf

File tree

6 files changed

+56
-17
lines changed

6 files changed

+56
-17
lines changed

crates/flycheck/src/lib.rs

+40-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::{
1414

1515
use command_group::{CommandGroup, GroupChild};
1616
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
17-
use paths::AbsPathBuf;
17+
use paths::{AbsPath, AbsPathBuf};
1818
use rustc_hash::FxHashMap;
1919
use serde::Deserialize;
2020
use stdx::process::streaming_output;
@@ -100,8 +100,8 @@ impl FlycheckHandle {
100100
}
101101

102102
/// Schedule a re-start of the cargo check worker.
103-
pub fn restart(&self) {
104-
self.sender.send(StateChange::Restart).unwrap();
103+
pub fn restart(&self, saved_file: Option<AbsPathBuf>) {
104+
self.sender.send(StateChange::Restart { saved_file }).unwrap();
105105
}
106106

107107
/// Stop this cargo check worker.
@@ -152,7 +152,7 @@ pub enum Progress {
152152
}
153153

154154
enum StateChange {
155-
Restart,
155+
Restart { saved_file: Option<AbsPathBuf> },
156156
Cancel,
157157
}
158158

@@ -178,6 +178,8 @@ enum Event {
178178
CheckEvent(Option<CargoMessage>),
179179
}
180180

181+
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
182+
181183
impl FlycheckActor {
182184
fn new(
183185
id: usize,
@@ -212,7 +214,7 @@ impl FlycheckActor {
212214
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
213215
self.cancel_check_process();
214216
}
215-
Event::RequestStateChange(StateChange::Restart) => {
217+
Event::RequestStateChange(StateChange::Restart { saved_file }) => {
216218
// Cancel the previously spawned process
217219
self.cancel_check_process();
218220
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@@ -222,7 +224,10 @@ impl FlycheckActor {
222224
}
223225
}
224226

225-
let command = self.check_command();
227+
let command = match self.check_command(saved_file.as_deref()) {
228+
Some(c) => c,
229+
None => continue,
230+
};
226231
let formatted_command = format!("{:?}", command);
227232

228233
tracing::debug!(?command, "will restart flycheck");
@@ -296,7 +301,10 @@ impl FlycheckActor {
296301
}
297302
}
298303

299-
fn check_command(&self) -> Command {
304+
/// Construct a `Command` object for checking the user's code. If the user
305+
/// has specified a custom command with placeholders that we cannot fill,
306+
/// return None.
307+
fn check_command(&self, saved_file: Option<&AbsPath>) -> Option<Command> {
300308
let (mut cmd, args) = match &self.config {
301309
FlycheckConfig::CargoCommand {
302310
command,
@@ -341,7 +349,7 @@ impl FlycheckActor {
341349
}
342350
}
343351
cmd.envs(extra_env);
344-
(cmd, extra_args)
352+
(cmd, extra_args.clone())
345353
}
346354
FlycheckConfig::CustomCommand {
347355
command,
@@ -370,12 +378,34 @@ impl FlycheckActor {
370378
}
371379
}
372380

373-
(cmd, args)
381+
if args.contains(&SAVED_FILE_PLACEHOLDER.to_owned()) {
382+
// If the custom command has a $saved_file placeholder, and
383+
// we're saving a file, replace the placeholder in the arguments.
384+
if let Some(saved_file) = saved_file {
385+
let args = args
386+
.iter()
387+
.map(|arg| {
388+
if arg == SAVED_FILE_PLACEHOLDER {
389+
saved_file.to_string()
390+
} else {
391+
arg.clone()
392+
}
393+
})
394+
.collect();
395+
(cmd, args)
396+
} else {
397+
// The custom command has a $saved_file placeholder,
398+
// but we had an IDE event that wasn't a file save. Do nothing.
399+
return None;
400+
}
401+
} else {
402+
(cmd, args.clone())
403+
}
374404
}
375405
};
376406

377407
cmd.args(args);
378-
cmd
408+
Some(cmd)
379409
}
380410

381411
fn send(&self, check_task: Message) {

crates/rust-analyzer/src/config.rs

+4
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ config_data! {
194194
/// by changing `#rust-analyzer.cargo.check.invocationStrategy#` and
195195
/// `#rust-analyzer.cargo.check.invocationLocation#`.
196196
///
197+
/// If `$saved_file` is part of the command, rust-analyzer will pass
198+
/// the absolute path of the saved file to the provided command. This is
199+
/// intended to be used with non-Cargo build systems.
200+
///
197201
/// An example command would be:
198202
///
199203
/// ```bash

crates/rust-analyzer/src/handlers/notification.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub(crate) fn handle_did_save_text_document(
146146
} else if state.config.check_on_save() {
147147
// No specific flycheck was triggered, so let's trigger all of them.
148148
for flycheck in state.flycheck.iter() {
149-
flycheck.restart();
149+
flycheck.restart(None);
150150
}
151151
}
152152
Ok(())
@@ -282,20 +282,22 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
282282
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
283283
});
284284

285+
let saved_file = vfs_path.as_path().map(|p| p.to_owned());
286+
285287
// Find and trigger corresponding flychecks
286288
for flycheck in world.flycheck.iter() {
287289
for (id, _) in workspace_ids.clone() {
288290
if id == flycheck.id() {
289291
updated = true;
290-
flycheck.restart();
292+
flycheck.restart(saved_file.clone());
291293
continue;
292294
}
293295
}
294296
}
295297
// No specific flycheck was triggered, so let's trigger all of them.
296298
if !updated {
297299
for flycheck in world.flycheck.iter() {
298-
flycheck.restart();
300+
flycheck.restart(saved_file.clone());
299301
}
300302
}
301303
Ok(())
@@ -337,7 +339,7 @@ pub(crate) fn handle_run_flycheck(
337339
}
338340
// No specific flycheck was triggered, so let's trigger all of them.
339341
for flycheck in state.flycheck.iter() {
340-
flycheck.restart();
342+
flycheck.restart(None);
341343
}
342344
Ok(())
343345
}

crates/rust-analyzer/src/main_loop.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::{
77

88
use always_assert::always;
99
use crossbeam_channel::{select, Receiver};
10-
use flycheck::FlycheckHandle;
1110
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
1211
use lsp_server::{Connection, Notification, Request};
1312
use lsp_types::notification::Notification as _;
@@ -299,7 +298,7 @@ impl GlobalState {
299298
if became_quiescent {
300299
if self.config.check_on_save() {
301300
// Project has loaded properly, kick off initial flycheck
302-
self.flycheck.iter().for_each(FlycheckHandle::restart);
301+
self.flycheck.iter().for_each(|flycheck| flycheck.restart(None));
303302
}
304303
if self.config.prefill_caches() {
305304
self.prime_caches_queue.request_op("became quiescent".to_string(), ());

docs/user/generated_config.adoc

+4
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ each of them, with the working directory being the workspace root
218218
by changing `#rust-analyzer.cargo.check.invocationStrategy#` and
219219
`#rust-analyzer.cargo.check.invocationLocation#`.
220220

221+
If `$saved_file` is part of the command, rust-analyzer will pass
222+
the absolute path of the saved file to the provided command. This is
223+
intended to be used with non-Cargo build systems.
224+
221225
An example command would be:
222226

223227
```bash

editors/code/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@
746746
]
747747
},
748748
"rust-analyzer.check.overrideCommand": {
749-
"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.cargo.check.invocationStrategy#` and\n`#rust-analyzer.cargo.check.invocationLocation#`.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
749+
"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.cargo.check.invocationStrategy#` and\n`#rust-analyzer.cargo.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.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
750750
"default": null,
751751
"type": [
752752
"null",

0 commit comments

Comments
 (0)