Skip to content

Commit 9f6b8ec

Browse files
committed
flycheck: initial implementation of $saved_file
1 parent af4ba46 commit 9f6b8ec

File tree

7 files changed

+84
-38
lines changed

7 files changed

+84
-38
lines changed

crates/flycheck/src/lib.rs

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub enum FlycheckConfig {
5555
extra_env: FxHashMap<String, String>,
5656
invocation_strategy: InvocationStrategy,
5757
invocation_location: InvocationLocation,
58+
invoke_with_saved_file: bool,
5859
},
5960
}
6061

@@ -69,6 +70,15 @@ impl fmt::Display for FlycheckConfig {
6970
}
7071
}
7172

73+
impl FlycheckConfig {
74+
pub fn invoke_with_saved_file(&self) -> bool {
75+
match self {
76+
FlycheckConfig::CustomCommand { invoke_with_saved_file, .. } => *invoke_with_saved_file,
77+
_ => false,
78+
}
79+
}
80+
}
81+
7282
/// Flycheck wraps the shared state and communication machinery used for
7383
/// running `cargo check` (or other compatible command) and providing
7484
/// diagnostics based on the output.
@@ -98,8 +108,8 @@ impl FlycheckHandle {
98108
}
99109

100110
/// Schedule a re-start of the cargo check worker.
101-
pub fn restart(&self) {
102-
self.sender.send(StateChange::Restart).unwrap();
111+
pub fn restart(&self, saved_file: Option<AbsPathBuf>) {
112+
self.sender.send(StateChange::Restart { saved_file }).unwrap();
103113
}
104114

105115
/// Stop this cargo check worker.
@@ -150,7 +160,7 @@ pub enum Progress {
150160
}
151161

152162
enum StateChange {
153-
Restart,
163+
Restart { saved_file: Option<AbsPathBuf> },
154164
Cancel,
155165
}
156166

@@ -210,7 +220,7 @@ impl FlycheckActor {
210220
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
211221
self.cancel_check_process();
212222
}
213-
Event::RequestStateChange(StateChange::Restart) => {
223+
Event::RequestStateChange(StateChange::Restart { saved_file }) => {
214224
// Cancel the previously spawned process
215225
self.cancel_check_process();
216226
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@@ -220,12 +230,15 @@ impl FlycheckActor {
220230
}
221231
}
222232

223-
let command = self.check_command();
224-
tracing::debug!(?command, "will restart flycheck");
233+
let command = self.check_command(saved_file);
234+
let command_string = format!("{:?}", command);
235+
236+
tracing::debug!(?command, "restarting flycheck");
237+
225238
match CargoHandle::spawn(command) {
226239
Ok(cargo_handle) => {
227240
tracing::debug!(
228-
command = ?self.check_command(),
241+
command = ?command_string,
229242
"did restart flycheck"
230243
);
231244
self.cargo_handle = Some(cargo_handle);
@@ -234,8 +247,7 @@ impl FlycheckActor {
234247
Err(error) => {
235248
self.report_progress(Progress::DidFailToRestart(format!(
236249
"Failed to run the following command: {:?} error={}",
237-
self.check_command(),
238-
error
250+
command_string, error
239251
)));
240252
}
241253
}
@@ -249,7 +261,7 @@ impl FlycheckActor {
249261
if res.is_err() {
250262
tracing::error!(
251263
"Flycheck failed to run the following command: {:?}",
252-
self.check_command()
264+
self.check_command(None)
253265
);
254266
}
255267
self.report_progress(Progress::DidFinish(res));
@@ -285,16 +297,13 @@ impl FlycheckActor {
285297

286298
fn cancel_check_process(&mut self) {
287299
if let Some(cargo_handle) = self.cargo_handle.take() {
288-
tracing::debug!(
289-
command = ?self.check_command(),
290-
"did cancel flycheck"
291-
);
300+
tracing::debug!(command = ?self.check_command(None), "did cancel flycheck");
292301
cargo_handle.cancel();
293302
self.report_progress(Progress::DidCancel);
294303
}
295304
}
296305

297-
fn check_command(&self) -> Command {
306+
fn check_command(&self, saved_file: Option<AbsPathBuf>) -> Command {
298307
let (mut cmd, args) = match &self.config {
299308
FlycheckConfig::CargoCommand {
300309
command,
@@ -339,14 +348,15 @@ impl FlycheckActor {
339348
}
340349
}
341350
cmd.envs(extra_env);
342-
(cmd, extra_args)
351+
(cmd, extra_args.clone())
343352
}
344353
FlycheckConfig::CustomCommand {
345354
command,
346355
args,
347356
extra_env,
348357
invocation_strategy,
349358
invocation_location,
359+
invoke_with_saved_file,
350360
} => {
351361
let mut cmd = Command::new(command);
352362
cmd.envs(extra_env);
@@ -368,11 +378,23 @@ impl FlycheckActor {
368378
}
369379
}
370380

371-
(cmd, args)
381+
if *invoke_with_saved_file {
382+
match (args.iter().position(|arg| arg == "$saved_file"), saved_file) {
383+
(Some(i), Some(saved_file)) => {
384+
let mut args = args.clone();
385+
args[i] = saved_file.to_string();
386+
(cmd, args)
387+
},
388+
_ => unreachable!("this code should not be reachable. An invariant inside of rust-analyzer has been broken.")
389+
}
390+
} else {
391+
(cmd, args.clone())
392+
}
372393
}
373394
};
374395

375396
cmd.args(args);
397+
376398
cmd
377399
}
378400

@@ -464,23 +486,28 @@ impl CargoActor {
464486
// Try to deserialize a message from Cargo or Rustc.
465487
let mut deserializer = serde_json::Deserializer::from_str(line);
466488
deserializer.disable_recursion_limit();
467-
if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
468-
match message {
469-
// Skip certain kinds of messages to only spend time on what's useful
470-
JsonMessage::Cargo(message) => match message {
471-
cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => {
472-
self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
473-
}
474-
cargo_metadata::Message::CompilerMessage(msg) => {
475-
self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
489+
match JsonMessage::deserialize(&mut deserializer) {
490+
Ok(message) => {
491+
match message {
492+
// Skip certain kinds of messages to only spend time on what's useful
493+
JsonMessage::Cargo(message) => match message {
494+
cargo_metadata::Message::CompilerArtifact(artifact)
495+
if !artifact.fresh =>
496+
{
497+
self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
498+
}
499+
cargo_metadata::Message::CompilerMessage(msg) => {
500+
self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
501+
}
502+
_ => (),
503+
},
504+
JsonMessage::Rustc(message) => {
505+
self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
476506
}
477-
_ => (),
478-
},
479-
JsonMessage::Rustc(message) => {
480-
self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
481507
}
508+
return true;
482509
}
483-
return true;
510+
Err(e) => tracing::error!(?e, "unable to deserialize message"),
484511
}
485512

486513
error.push_str(line);

crates/rust-analyzer/src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ config_data! {
181181
/// each of them, with the working directory being the project root
182182
/// (i.e., the folder containing the `Cargo.toml`).
183183
///
184+
/// If `$saved_file` is part of the command, rust-analyzer will pass
185+
/// the absolute path of the saved file to the provided command. This is
186+
/// intended to be used with non-Cargo build systems.
187+
///
184188
/// An example command would be:
185189
///
186190
/// ```bash
@@ -1264,6 +1268,9 @@ impl Config {
12641268
Some(args) if !args.is_empty() => {
12651269
let mut args = args.clone();
12661270
let command = args.remove(0);
1271+
1272+
let use_saved_file = args.contains(&"$saved_file".to_string());
1273+
12671274
FlycheckConfig::CustomCommand {
12681275
command,
12691276
args,
@@ -1280,6 +1287,7 @@ impl Config {
12801287
}
12811288
InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace,
12821289
},
1290+
invoke_with_saved_file: use_saved_file,
12831291
}
12841292
}
12851293
Some(_) | None => FlycheckConfig::CargoCommand {

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

Lines changed: 9 additions & 4 deletions
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,25 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
273273
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
274274
});
275275

276+
let path = vfs_path
277+
.as_path()
278+
.expect("unable to convert to a path; this is a bug in rust-analyzer")
279+
.to_owned();
280+
276281
// Find and trigger corresponding flychecks
277282
for flycheck in world.flycheck.iter() {
278283
for (id, _) in workspace_ids.clone() {
279284
if id == flycheck.id() {
280285
updated = true;
281-
flycheck.restart();
286+
flycheck.restart(Some(path.clone()));
282287
continue;
283288
}
284289
}
285290
}
286291
// No specific flycheck was triggered, so let's trigger all of them.
287292
if !updated {
288293
for flycheck in world.flycheck.iter() {
289-
flycheck.restart();
294+
flycheck.restart(None);
290295
}
291296
}
292297
Ok(())
@@ -328,7 +333,7 @@ pub(crate) fn handle_run_flycheck(
328333
}
329334
// No specific flycheck was triggered, so let's trigger all of them.
330335
for flycheck in state.flycheck.iter() {
331-
flycheck.restart();
336+
flycheck.restart(None);
332337
}
333338
Ok(())
334339
}

crates/rust-analyzer/src/main_loop.rs

Lines changed: 3 additions & 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 _;
@@ -296,7 +295,9 @@ 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+
if !self.config.flycheck().invoke_with_saved_file() {
299+
self.flycheck.iter().for_each(|flycheck| flycheck.restart(None));
300+
}
300301
}
301302
if self.config.prefill_caches() {
302303
self.prime_caches_queue.request_op("became quiescent".to_string(), ());

crates/rust-analyzer/src/reload.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ impl GlobalState {
577577
fn reload_flycheck(&mut self) {
578578
let _p = profile::span("GlobalState::reload_flycheck");
579579
let config = self.config.flycheck();
580+
580581
let sender = self.flycheck_sender.clone();
581582
let invocation_strategy = match config {
582583
FlycheckConfig::CargoCommand { .. } => flycheck::InvocationStrategy::PerWorkspace,

docs/user/generated_config.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ If there are multiple linked projects, this command is invoked for
203203
each of them, with the working directory being the project root
204204
(i.e., the folder containing the `Cargo.toml`).
205205

206+
If `$saved_file` is part of the command, rust-analyzer will pass
207+
the absolute path of the saved file to the provided command. This is
208+
intended to be used with non-Cargo build systems.
209+
206210
An example command would be:
207211

208212
```bash

editors/code/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@
735735
]
736736
},
737737
"rust-analyzer.check.overrideCommand": {
738-
"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.",
738+
"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.",
739739
"default": null,
740740
"type": [
741741
"null",

0 commit comments

Comments
 (0)