Skip to content

Commit a7464f6

Browse files
davidbarskyWilfred
authored andcommitted
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 24b6a34 commit a7464f6

File tree

6 files changed

+55
-17
lines changed

6 files changed

+55
-17
lines changed

crates/flycheck/src/lib.rs

+39-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,33 @@ impl FlycheckActor {
370378
}
371379
}
372380

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

377406
cmd.args(args);
378-
cmd
407+
Some(cmd)
379408
}
380409

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

crates/rust-analyzer/src/config.rs

+4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ config_data! {
185185
/// each of them, with the working directory being the project root
186186
/// (i.e., the folder containing the `Cargo.toml`).
187187
///
188+
/// If `$saved_file` is part of the command, rust-analyzer will pass
189+
/// the absolute path of the saved file to the provided command. This is
190+
/// intended to be used with non-Cargo build systems.
191+
///
188192
/// An example command would be:
189193
///
190194
/// ```bash

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub(crate) fn handle_did_save_text_document(
137137
} else if state.config.check_on_save() {
138138
// No specific flycheck was triggered, so let's trigger all of them.
139139
for flycheck in state.flycheck.iter() {
140-
flycheck.restart();
140+
flycheck.restart(None);
141141
}
142142
}
143143
Ok(())
@@ -273,20 +273,22 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
273273
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
274274
});
275275

276+
let saved_file = vfs_path.as_path().map(|p| p.to_owned());
277+
276278
// Find and trigger corresponding flychecks
277279
for flycheck in world.flycheck.iter() {
278280
for (id, _) in workspace_ids.clone() {
279281
if id == flycheck.id() {
280282
updated = true;
281-
flycheck.restart();
283+
flycheck.restart(saved_file.clone());
282284
continue;
283285
}
284286
}
285287
}
286288
// No specific flycheck was triggered, so let's trigger all of them.
287289
if !updated {
288290
for flycheck in world.flycheck.iter() {
289-
flycheck.restart();
291+
flycheck.restart(saved_file.clone());
290292
}
291293
}
292294
Ok(())
@@ -328,7 +330,7 @@ pub(crate) fn handle_run_flycheck(
328330
}
329331
// No specific flycheck was triggered, so let's trigger all of them.
330332
for flycheck in state.flycheck.iter() {
331-
flycheck.restart();
333+
flycheck.restart(None);
332334
}
333335
Ok(())
334336
}

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 _;
@@ -296,7 +295,7 @@ impl GlobalState {
296295
if became_quiescent {
297296
if self.config.check_on_save() {
298297
// Project has loaded properly, kick off initial flycheck
299-
self.flycheck.iter().for_each(FlycheckHandle::restart);
298+
self.flycheck.iter().for_each(|flycheck| flycheck.restart(None));
300299
}
301300
if self.config.prefill_caches() {
302301
self.prime_caches_queue.request_op("became quiescent".to_string(), ());

docs/user/generated_config.adoc

+4
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ If there are multiple linked projects, this command is invoked for
210210
each of them, with the working directory being the project root
211211
(i.e., the folder containing the `Cargo.toml`).
212212

213+
If `$saved_file` is part of the command, rust-analyzer will pass
214+
the absolute path of the saved file to the provided command. This is
215+
intended to be used with non-Cargo build systems.
216+
213217
An example command would be:
214218

215219
```bash

editors/code/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@
749749
]
750750
},
751751
"rust-analyzer.check.overrideCommand": {
752-
"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, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
752+
"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, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\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.",
753753
"default": null,
754754
"type": [
755755
"null",

0 commit comments

Comments
 (0)