Skip to content

Commit 008f797

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 then run a check command. If there's a placeholder and we don't know the saved file, do nothing.
1 parent 0d52934 commit 008f797

File tree

6 files changed

+56
-17
lines changed

6 files changed

+56
-17
lines changed

crates/flycheck/src/lib.rs

Lines changed: 40 additions & 10 deletions
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ config_data! {
208208
/// by changing `#rust-analyzer.check.invocationStrategy#` and
209209
/// `#rust-analyzer.check.invocationLocation#`.
210210
///
211+
/// If `$saved_file` is part of the command, rust-analyzer will pass
212+
/// the absolute path of the saved file to the provided command. This is
213+
/// intended to be used with non-Cargo build systems.
214+
///
211215
/// An example command would be:
212216
///
213217
/// ```bash

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub(crate) fn handle_did_save_text_document(
160160
} else if state.config.check_on_save() {
161161
// No specific flycheck was triggered, so let's trigger all of them.
162162
for flycheck in state.flycheck.iter() {
163-
flycheck.restart();
163+
flycheck.restart(None);
164164
}
165165
}
166166
Ok(())
@@ -296,20 +296,22 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
296296
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
297297
});
298298

299+
let saved_file = vfs_path.as_path().map(|p| p.to_owned());
300+
299301
// Find and trigger corresponding flychecks
300302
for flycheck in world.flycheck.iter() {
301303
for (id, _) in workspace_ids.clone() {
302304
if id == flycheck.id() {
303305
updated = true;
304-
flycheck.restart();
306+
flycheck.restart(saved_file.clone());
305307
continue;
306308
}
307309
}
308310
}
309311
// No specific flycheck was triggered, so let's trigger all of them.
310312
if !updated {
311313
for flycheck in world.flycheck.iter() {
312-
flycheck.restart();
314+
flycheck.restart(saved_file.clone());
313315
}
314316
}
315317
Ok(())
@@ -351,7 +353,7 @@ pub(crate) fn handle_run_flycheck(
351353
}
352354
// No specific flycheck was triggered, so let's trigger all of them.
353355
for flycheck in state.flycheck.iter() {
354-
flycheck.restart();
356+
flycheck.restart(None);
355357
}
356358
Ok(())
357359
}

crates/rust-analyzer/src/main_loop.rs

Lines changed: 1 addition & 2 deletions
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ each of them, with the working directory being the workspace root
234234
by changing `#rust-analyzer.check.invocationStrategy#` and
235235
`#rust-analyzer.check.invocationLocation#`.
236236

237+
If `$saved_file` is part of the command, rust-analyzer will pass
238+
the absolute path of the saved file to the provided command. This is
239+
intended to be used with non-Cargo build systems.
240+
237241
An example command would be:
238242

239243
```bash

editors/code/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@
775775
]
776776
},
777777
"rust-analyzer.check.overrideCommand": {
778-
"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.",
778+
"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.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.",
779779
"default": null,
780780
"type": [
781781
"null",

0 commit comments

Comments
 (0)