Skip to content

Commit ebea717

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 255eed4 commit ebea717

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;
@@ -101,8 +101,8 @@ impl FlycheckHandle {
101101
}
102102

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

108108
/// Stop this cargo check worker.
@@ -153,7 +153,7 @@ pub enum Progress {
153153
}
154154

155155
enum StateChange {
156-
Restart,
156+
Restart { saved_file: Option<AbsPathBuf> },
157157
Cancel,
158158
}
159159

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

182+
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
183+
182184
impl FlycheckActor {
183185
fn new(
184186
id: usize,
@@ -213,7 +215,7 @@ impl FlycheckActor {
213215
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
214216
self.cancel_check_process();
215217
}
216-
Event::RequestStateChange(StateChange::Restart) => {
218+
Event::RequestStateChange(StateChange::Restart { saved_file }) => {
217219
// Cancel the previously spawned process
218220
self.cancel_check_process();
219221
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@@ -223,7 +225,10 @@ impl FlycheckActor {
223225
}
224226
}
225227

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

229234
tracing::debug!(?command, "will restart flycheck");
@@ -297,7 +302,10 @@ impl FlycheckActor {
297302
}
298303
}
299304

300-
fn check_command(&self) -> Command {
305+
/// Construct a `Command` object for checking the user's code. If the user
306+
/// has specified a custom command with placeholders that we cannot fill,
307+
/// return None.
308+
fn check_command(&self, saved_file: Option<&AbsPath>) -> Option<Command> {
301309
let (mut cmd, args) = match &self.config {
302310
FlycheckConfig::CargoCommand {
303311
command,
@@ -346,7 +354,7 @@ impl FlycheckActor {
346354
cmd.arg("--target-dir").arg(target_dir);
347355
}
348356
cmd.envs(extra_env);
349-
(cmd, extra_args)
357+
(cmd, extra_args.clone())
350358
}
351359
FlycheckConfig::CustomCommand {
352360
command,
@@ -375,12 +383,34 @@ impl FlycheckActor {
375383
}
376384
}
377385

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

382412
cmd.args(args);
383-
cmd
413+
Some(cmd)
384414
}
385415

386416
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
@@ -760,7 +760,7 @@
760760
]
761761
},
762762
"rust-analyzer.check.overrideCommand": {
763-
"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.",
763+
"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.",
764764
"default": null,
765765
"type": [
766766
"null",

0 commit comments

Comments
 (0)