aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_flycheck/src/lib.rs268
-rw-r--r--crates/ra_hir_def/src/body/lower.rs54
-rw-r--r--crates/ra_ide/src/completion.rs6
-rw-r--r--crates/ra_ide/src/completion/complete_record.rs411
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs241
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs118
-rw-r--r--crates/ra_ide/src/ssr.rs116
-rw-r--r--crates/rust-analyzer/src/main_loop.rs25
-rw-r--r--crates/rust-analyzer/src/world.rs18
9 files changed, 677 insertions, 580 deletions
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index 38940a77b..13494a731 100644
--- a/crates/ra_flycheck/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -1,55 +1,53 @@
1//! cargo_check provides the functionality needed to run `cargo check` or 1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide 2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command. 3//! LSP diagnostics based on the output of the command.
4mod conv;
5
6use std::{
7 env,
8 io::{self, BufRead, BufReader},
9 path::PathBuf,
10 process::{Command, Stdio},
11 time::Instant,
12};
13
4use cargo_metadata::Message; 14use cargo_metadata::Message;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; 15use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6use lsp_types::{ 16use lsp_types::{
7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, 17 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressEnd, WorkDoneProgressReport, 18 WorkDoneProgressEnd, WorkDoneProgressReport,
9}; 19};
10use std::{
11 error, fmt,
12 io::{BufRead, BufReader},
13 path::{Path, PathBuf},
14 process::{Command, Stdio},
15 time::Instant,
16};
17
18mod conv;
19 20
20use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; 21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
21 22
22pub use crate::conv::url_from_path_with_drive_lowercasing; 23pub use crate::conv::url_from_path_with_drive_lowercasing;
23 24
24#[derive(Clone, Debug)] 25#[derive(Clone, Debug)]
25pub struct CheckConfig { 26pub enum FlycheckConfig {
26 pub enable: bool, 27 CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> },
27 pub args: Vec<String>, 28 CustomCommand { command: String, args: Vec<String> },
28 pub command: String,
29 pub all_targets: bool,
30} 29}
31 30
32/// CheckWatcher wraps the shared state and communication machinery used for 31/// Flycheck wraps the shared state and communication machinery used for
33/// running `cargo check` (or other compatible command) and providing 32/// running `cargo check` (or other compatible command) and providing
34/// diagnostics based on the output. 33/// diagnostics based on the output.
35/// The spawned thread is shut down when this struct is dropped. 34/// The spawned thread is shut down when this struct is dropped.
36#[derive(Debug)] 35#[derive(Debug)]
37pub struct CheckWatcher { 36pub struct Flycheck {
38 // XXX: drop order is significant 37 // XXX: drop order is significant
39 cmd_send: Sender<CheckCommand>, 38 cmd_send: Sender<CheckCommand>,
40 handle: Option<jod_thread::JoinHandle<()>>, 39 handle: jod_thread::JoinHandle<()>,
41 pub task_recv: Receiver<CheckTask>, 40 pub task_recv: Receiver<CheckTask>,
42} 41}
43 42
44impl CheckWatcher { 43impl Flycheck {
45 pub fn new(config: CheckConfig, workspace_root: PathBuf) -> CheckWatcher { 44 pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck {
46 let (task_send, task_recv) = unbounded::<CheckTask>(); 45 let (task_send, task_recv) = unbounded::<CheckTask>();
47 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 46 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
48 let handle = jod_thread::spawn(move || { 47 let handle = jod_thread::spawn(move || {
49 let mut check = CheckWatcherThread::new(config, workspace_root); 48 FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv);
50 check.run(&task_send, &cmd_recv);
51 }); 49 });
52 CheckWatcher { task_recv, cmd_send, handle: Some(handle) } 50 Flycheck { task_recv, cmd_send, handle }
53 } 51 }
54 52
55 /// Schedule a re-start of the cargo check worker. 53 /// Schedule a re-start of the cargo check worker.
@@ -75,20 +73,28 @@ pub enum CheckCommand {
75 Update, 73 Update,
76} 74}
77 75
78struct CheckWatcherThread { 76struct FlycheckThread {
79 options: CheckConfig, 77 config: FlycheckConfig,
80 workspace_root: PathBuf, 78 workspace_root: PathBuf,
81 watcher: WatchThread,
82 last_update_req: Option<Instant>, 79 last_update_req: Option<Instant>,
80 // XXX: drop order is significant
81 message_recv: Receiver<CheckEvent>,
82 /// WatchThread exists to wrap around the communication needed to be able to
83 /// run `cargo check` without blocking. Currently the Rust standard library
84 /// doesn't provide a way to read sub-process output without blocking, so we
85 /// have to wrap sub-processes output handling in a thread and pass messages
86 /// back over a channel.
87 check_process: Option<jod_thread::JoinHandle<()>>,
83} 88}
84 89
85impl CheckWatcherThread { 90impl FlycheckThread {
86 fn new(options: CheckConfig, workspace_root: PathBuf) -> CheckWatcherThread { 91 fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread {
87 CheckWatcherThread { 92 FlycheckThread {
88 options, 93 config,
89 workspace_root, 94 workspace_root,
90 watcher: WatchThread::dummy(),
91 last_update_req: None, 95 last_update_req: None,
96 message_recv: never(),
97 check_process: None,
92 } 98 }
93 } 99 }
94 100
@@ -105,25 +111,21 @@ impl CheckWatcherThread {
105 break; 111 break;
106 }, 112 },
107 }, 113 },
108 recv(self.watcher.message_recv) -> msg => match msg { 114 recv(self.message_recv) -> msg => match msg {
109 Ok(msg) => self.handle_message(msg, task_send), 115 Ok(msg) => self.handle_message(msg, task_send),
110 Err(RecvError) => { 116 Err(RecvError) => {
111 // Watcher finished, replace it with a never channel to 117 // Watcher finished, replace it with a never channel to
112 // avoid busy-waiting. 118 // avoid busy-waiting.
113 std::mem::replace(&mut self.watcher.message_recv, never()); 119 self.message_recv = never();
120 self.check_process = None;
114 }, 121 },
115 } 122 }
116 }; 123 };
117 124
118 if self.should_recheck() { 125 if self.should_recheck() {
119 self.last_update_req.take(); 126 self.last_update_req = None;
120 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 127 task_send.send(CheckTask::ClearDiagnostics).unwrap();
121 128 self.restart_check_process();
122 // Replace with a dummy watcher first so we drop the original and wait for completion
123 std::mem::replace(&mut self.watcher, WatchThread::dummy());
124
125 // Then create the actual new watcher
126 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
127 } 129 }
128 } 130 }
129 } 131 }
@@ -206,6 +208,63 @@ impl CheckWatcherThread {
206 CheckEvent::Msg(Message::Unknown) => {} 208 CheckEvent::Msg(Message::Unknown) => {}
207 } 209 }
208 } 210 }
211
212 fn restart_check_process(&mut self) {
213 // First, clear and cancel the old thread
214 self.message_recv = never();
215 self.check_process = None;
216
217 let mut cmd = match &self.config {
218 FlycheckConfig::CargoCommand { command, all_targets, extra_args } => {
219 let mut cmd = Command::new(cargo_binary());
220 cmd.arg(command);
221 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]);
222 cmd.arg(self.workspace_root.join("Cargo.toml"));
223 if *all_targets {
224 cmd.arg("--all-targets");
225 }
226 cmd.args(extra_args);
227 cmd
228 }
229 FlycheckConfig::CustomCommand { command, args } => {
230 let mut cmd = Command::new(command);
231 cmd.args(args);
232 cmd
233 }
234 };
235 cmd.current_dir(&self.workspace_root);
236
237 let (message_send, message_recv) = unbounded();
238 self.message_recv = message_recv;
239 self.check_process = Some(jod_thread::spawn(move || {
240 // If we trigger an error here, we will do so in the loop instead,
241 // which will break out of the loop, and continue the shutdown
242 let _ = message_send.send(CheckEvent::Begin);
243
244 let res = run_cargo(cmd, &mut |message| {
245 // Skip certain kinds of messages to only spend time on what's useful
246 match &message {
247 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
248 Message::BuildScriptExecuted(_) => return true,
249 Message::Unknown => return true,
250 _ => {}
251 }
252
253 // if the send channel was closed, we want to shutdown
254 message_send.send(CheckEvent::Msg(message)).is_ok()
255 });
256
257 if let Err(err) = res {
258 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
259 // to display user-caused misconfiguration errors instead of just logging them here
260 log::error!("Cargo watcher failed {:?}", err);
261 }
262
263 // We can ignore any error here, as we are already in the progress
264 // of shutting down.
265 let _ = message_send.send(CheckEvent::End);
266 }))
267 }
209} 268}
210 269
211#[derive(Debug)] 270#[derive(Debug)]
@@ -214,52 +273,18 @@ pub struct DiagnosticWithFixes {
214 fixes: Vec<CodeAction>, 273 fixes: Vec<CodeAction>,
215} 274}
216 275
217/// WatchThread exists to wrap around the communication needed to be able to
218/// run `cargo check` without blocking. Currently the Rust standard library
219/// doesn't provide a way to read sub-process output without blocking, so we
220/// have to wrap sub-processes output handling in a thread and pass messages
221/// back over a channel.
222/// The correct way to dispose of the thread is to drop it, on which the
223/// sub-process will be killed, and the thread will be joined.
224struct WatchThread {
225 // XXX: drop order is significant
226 message_recv: Receiver<CheckEvent>,
227 _handle: Option<jod_thread::JoinHandle<()>>,
228}
229
230enum CheckEvent { 276enum CheckEvent {
231 Begin, 277 Begin,
232 Msg(cargo_metadata::Message), 278 Msg(cargo_metadata::Message),
233 End, 279 End,
234} 280}
235 281
236#[derive(Debug)]
237pub struct CargoError(String);
238
239impl fmt::Display for CargoError {
240 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241 write!(f, "Cargo failed: {}", self.0)
242 }
243}
244impl error::Error for CargoError {}
245
246fn run_cargo( 282fn run_cargo(
247 args: &[String], 283 mut command: Command,
248 current_dir: Option<&Path>,
249 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, 284 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
250) -> Result<(), CargoError> { 285) -> io::Result<()> {
251 let mut command = Command::new("cargo"); 286 let mut child =
252 if let Some(current_dir) = current_dir { 287 command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()).spawn()?;
253 command.current_dir(current_dir);
254 }
255
256 let mut child = command
257 .args(args)
258 .stdout(Stdio::piped())
259 .stderr(Stdio::null())
260 .stdin(Stdio::null())
261 .spawn()
262 .expect("couldn't launch cargo");
263 288
264 // We manually read a line at a time, instead of using serde's 289 // We manually read a line at a time, instead of using serde's
265 // stream deserializers, because the deserializer cannot recover 290 // stream deserializers, because the deserializer cannot recover
@@ -273,13 +298,7 @@ fn run_cargo(
273 let mut read_at_least_one_message = false; 298 let mut read_at_least_one_message = false;
274 299
275 for line in stdout.lines() { 300 for line in stdout.lines() {
276 let line = match line { 301 let line = line?;
277 Ok(line) => line,
278 Err(err) => {
279 log::error!("Couldn't read line from cargo: {}", err);
280 continue;
281 }
282 };
283 302
284 let message = serde_json::from_str::<cargo_metadata::Message>(&line); 303 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
285 let message = match message { 304 let message = match message {
@@ -300,75 +319,22 @@ fn run_cargo(
300 // It is okay to ignore the result, as it only errors if the process is already dead 319 // It is okay to ignore the result, as it only errors if the process is already dead
301 let _ = child.kill(); 320 let _ = child.kill();
302 321
303 let err_msg = match child.wait() { 322 let exit_status = child.wait()?;
304 Ok(exit_code) if !exit_code.success() && !read_at_least_one_message => { 323 if !exit_status.success() && !read_at_least_one_message {
305 // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: 324 // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
306 // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 325 // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
326 return Err(io::Error::new(
327 io::ErrorKind::Other,
307 format!( 328 format!(
308 "the command produced no valid metadata (exit code: {:?}): cargo {}", 329 "the command produced no valid metadata (exit code: {:?}): {:?}",
309 exit_code, 330 exit_status, command
310 args.join(" ") 331 ),
311 ) 332 ));
312 }
313 Err(err) => format!("io error: {:?}", err),
314 Ok(_) => return Ok(()),
315 };
316
317 Err(CargoError(err_msg))
318}
319
320impl WatchThread {
321 fn dummy() -> WatchThread {
322 WatchThread { message_recv: never(), _handle: None }
323 } 333 }
324 334
325 fn new(options: &CheckConfig, workspace_root: &Path) -> WatchThread { 335 Ok(())
326 let mut args: Vec<String> = vec![ 336}
327 options.command.clone(),
328 "--workspace".to_string(),
329 "--message-format=json".to_string(),
330 "--manifest-path".to_string(),
331 format!("{}/Cargo.toml", workspace_root.display()),
332 ];
333 if options.all_targets {
334 args.push("--all-targets".to_string());
335 }
336 args.extend(options.args.iter().cloned());
337
338 let (message_send, message_recv) = unbounded();
339 let workspace_root = workspace_root.to_owned();
340 let handle = if options.enable {
341 Some(jod_thread::spawn(move || {
342 // If we trigger an error here, we will do so in the loop instead,
343 // which will break out of the loop, and continue the shutdown
344 let _ = message_send.send(CheckEvent::Begin);
345
346 let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
347 // Skip certain kinds of messages to only spend time on what's useful
348 match &message {
349 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
350 Message::BuildScriptExecuted(_) => return true,
351 Message::Unknown => return true,
352 _ => {}
353 }
354
355 // if the send channel was closed, we want to shutdown
356 message_send.send(CheckEvent::Msg(message)).is_ok()
357 });
358
359 if let Err(err) = res {
360 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
361 // to display user-caused misconfiguration errors instead of just logging them here
362 log::error!("Cargo watcher failed {:?}", err);
363 }
364 337
365 // We can ignore any error here, as we are already in the progress 338fn cargo_binary() -> String {
366 // of shutting down. 339 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
367 let _ = message_send.send(CheckEvent::End);
368 }))
369 } else {
370 None
371 };
372 WatchThread { message_recv, _handle: handle }
373 }
374} 340}
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index e8443dde8..7b809cf4f 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -417,26 +417,7 @@ impl ExprCollector<'_> {
417 } 417 }
418 } 418 }
419 419
420 ast::Expr::Literal(e) => { 420 ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr),
421 let lit = match e.kind() {
422 LiteralKind::IntNumber { suffix } => {
423 let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
424
425 Literal::Int(Default::default(), known_name)
426 }
427 LiteralKind::FloatNumber { suffix } => {
428 let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
429
430 Literal::Float(Default::default(), known_name)
431 }
432 LiteralKind::ByteString => Literal::ByteString(Default::default()),
433 LiteralKind::String => Literal::String(Default::default()),
434 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
435 LiteralKind::Bool => Literal::Bool(Default::default()),
436 LiteralKind::Char => Literal::Char(Default::default()),
437 };
438 self.alloc_expr(Expr::Literal(lit), syntax_ptr)
439 }
440 ast::Expr::IndexExpr(e) => { 421 ast::Expr::IndexExpr(e) => {
441 let base = self.collect_expr_opt(e.base()); 422 let base = self.collect_expr_opt(e.base());
442 let index = self.collect_expr_opt(e.index()); 423 let index = self.collect_expr_opt(e.index());
@@ -679,10 +660,19 @@ impl ExprCollector<'_> {
679 suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(), 660 suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(),
680 } 661 }
681 } 662 }
663 ast::Pat::LiteralPat(lit) => {
664 if let Some(ast_lit) = lit.literal() {
665 let expr = Expr::Literal(ast_lit.kind().into());
666 let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit));
667 let expr_id = self.alloc_expr(expr, expr_ptr);
668 Pat::Lit(expr_id)
669 } else {
670 Pat::Missing
671 }
672 }
682 673
683 // FIXME: implement 674 // FIXME: implement
684 ast::Pat::BoxPat(_) => Pat::Missing, 675 ast::Pat::BoxPat(_) => Pat::Missing,
685 ast::Pat::LiteralPat(_) => Pat::Missing,
686 ast::Pat::RangePat(_) => Pat::Missing, 676 ast::Pat::RangePat(_) => Pat::Missing,
687 }; 677 };
688 let ptr = AstPtr::new(&pat); 678 let ptr = AstPtr::new(&pat);
@@ -741,3 +731,25 @@ impl From<ast::BinOp> for BinaryOp {
741 } 731 }
742 } 732 }
743} 733}
734
735impl From<ast::LiteralKind> for Literal {
736 fn from(ast_lit_kind: ast::LiteralKind) -> Self {
737 match ast_lit_kind {
738 LiteralKind::IntNumber { suffix } => {
739 let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
740
741 Literal::Int(Default::default(), known_name)
742 }
743 LiteralKind::FloatNumber { suffix } => {
744 let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
745
746 Literal::Float(Default::default(), known_name)
747 }
748 LiteralKind::ByteString => Literal::ByteString(Default::default()),
749 LiteralKind::String => Literal::String(Default::default()),
750 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
751 LiteralKind::Bool => Literal::Bool(Default::default()),
752 LiteralKind::Char => Literal::Char(Default::default()),
753 }
754 }
755}
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index b683572fb..93157bbba 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -5,8 +5,7 @@ mod completion_context;
5mod presentation; 5mod presentation;
6 6
7mod complete_dot; 7mod complete_dot;
8mod complete_record_literal; 8mod complete_record;
9mod complete_record_pattern;
10mod complete_pattern; 9mod complete_pattern;
11mod complete_fn_param; 10mod complete_fn_param;
12mod complete_keyword; 11mod complete_keyword;
@@ -89,8 +88,7 @@ pub(crate) fn completions(
89 complete_path::complete_path(&mut acc, &ctx); 88 complete_path::complete_path(&mut acc, &ctx);
90 complete_scope::complete_scope(&mut acc, &ctx); 89 complete_scope::complete_scope(&mut acc, &ctx);
91 complete_dot::complete_dot(&mut acc, &ctx); 90 complete_dot::complete_dot(&mut acc, &ctx);
92 complete_record_literal::complete_record_literal(&mut acc, &ctx); 91 complete_record::complete_record(&mut acc, &ctx);
93 complete_record_pattern::complete_record_pattern(&mut acc, &ctx);
94 complete_pattern::complete_pattern(&mut acc, &ctx); 92 complete_pattern::complete_pattern(&mut acc, &ctx);
95 complete_postfix::complete_postfix(&mut acc, &ctx); 93 complete_postfix::complete_postfix(&mut acc, &ctx);
96 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 94 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
diff --git a/crates/ra_ide/src/completion/complete_record.rs b/crates/ra_ide/src/completion/complete_record.rs
new file mode 100644
index 000000000..01dd8c6db
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record.rs
@@ -0,0 +1,411 @@
1//! Complete fields in record literals and patterns.
2use crate::completion::{CompletionContext, Completions};
3use ra_syntax::{ast, ast::NameOwner, SmolStr};
4
5pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
6 let (ty, variant, already_present_fields) =
7 match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) {
8 (None, None) => return None,
9 (Some(_), Some(_)) => panic!("A record cannot be both a literal and a pattern"),
10 (Some(record_pat), _) => (
11 ctx.sema.type_of_pat(&record_pat.clone().into())?,
12 ctx.sema.resolve_record_pattern(record_pat)?,
13 pattern_ascribed_fields(record_pat),
14 ),
15 (_, Some(record_lit)) => (
16 ctx.sema.type_of_expr(&record_lit.clone().into())?,
17 ctx.sema.resolve_record_literal(record_lit)?,
18 literal_ascribed_fields(record_lit),
19 ),
20 };
21
22 for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| {
23 // FIXME: already_present_names better be `Vec<hir::Name>`
24 !already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string()))
25 }) {
26 acc.add_field(ctx, field, &field_ty);
27 }
28 Some(())
29}
30
31fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec<SmolStr> {
32 record_lit
33 .record_field_list()
34 .map(|field_list| field_list.fields())
35 .map(|fields| {
36 fields
37 .into_iter()
38 .filter_map(|field| field.name_ref())
39 .map(|name_ref| name_ref.text().clone())
40 .collect()
41 })
42 .unwrap_or_default()
43}
44
45fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec<SmolStr> {
46 record_pat
47 .record_field_pat_list()
48 .map(|pat_list| {
49 pat_list
50 .record_field_pats()
51 .filter_map(|fild_pat| fild_pat.name())
52 .chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name()))
53 .map(|name| name.text().clone())
54 .collect()
55 })
56 .unwrap_or_default()
57}
58
59#[cfg(test)]
60mod tests {
61 mod record_lit_tests {
62 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
63 use insta::assert_debug_snapshot;
64
65 fn complete(code: &str) -> Vec<CompletionItem> {
66 do_completion(code, CompletionKind::Reference)
67 }
68
69 #[test]
70 fn test_record_pattern_field() {
71 let completions = complete(
72 r"
73 struct S { foo: u32 }
74
75 fn process(f: S) {
76 match f {
77 S { f<|>: 92 } => (),
78 }
79 }
80 ",
81 );
82 assert_debug_snapshot!(completions, @r###"
83 [
84 CompletionItem {
85 label: "foo",
86 source_range: [117; 118),
87 delete: [117; 118),
88 insert: "foo",
89 kind: Field,
90 detail: "u32",
91 },
92 ]
93 "###);
94 }
95
96 #[test]
97 fn test_record_pattern_enum_variant() {
98 let completions = complete(
99 r"
100 enum E {
101 S { foo: u32, bar: () }
102 }
103
104 fn process(e: E) {
105 match e {
106 E::S { <|> } => (),
107 }
108 }
109 ",
110 );
111 assert_debug_snapshot!(completions, @r###"
112 [
113 CompletionItem {
114 label: "bar",
115 source_range: [161; 161),
116 delete: [161; 161),
117 insert: "bar",
118 kind: Field,
119 detail: "()",
120 },
121 CompletionItem {
122 label: "foo",
123 source_range: [161; 161),
124 delete: [161; 161),
125 insert: "foo",
126 kind: Field,
127 detail: "u32",
128 },
129 ]
130 "###);
131 }
132
133 #[test]
134 fn test_record_pattern_field_in_simple_macro() {
135 let completions = complete(
136 r"
137 macro_rules! m { ($e:expr) => { $e } }
138 struct S { foo: u32 }
139
140 fn process(f: S) {
141 m!(match f {
142 S { f<|>: 92 } => (),
143 })
144 }
145 ",
146 );
147 assert_debug_snapshot!(completions, @r###"
148 [
149 CompletionItem {
150 label: "foo",
151 source_range: [171; 172),
152 delete: [171; 172),
153 insert: "foo",
154 kind: Field,
155 detail: "u32",
156 },
157 ]
158 "###);
159 }
160
161 #[test]
162 fn only_missing_fields_are_completed_in_destruct_pats() {
163 let completions = complete(
164 r"
165 struct S {
166 foo1: u32,
167 foo2: u32,
168 bar: u32,
169 baz: u32,
170 }
171
172 fn main() {
173 let s = S {
174 foo1: 1,
175 foo2: 2,
176 bar: 3,
177 baz: 4,
178 };
179 if let S { foo1, foo2: a, <|> } = s {}
180 }
181 ",
182 );
183 assert_debug_snapshot!(completions, @r###"
184 [
185 CompletionItem {
186 label: "bar",
187 source_range: [372; 372),
188 delete: [372; 372),
189 insert: "bar",
190 kind: Field,
191 detail: "u32",
192 },
193 CompletionItem {
194 label: "baz",
195 source_range: [372; 372),
196 delete: [372; 372),
197 insert: "baz",
198 kind: Field,
199 detail: "u32",
200 },
201 ]
202 "###);
203 }
204 }
205
206 mod record_pat_tests {
207 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
208 use insta::assert_debug_snapshot;
209
210 fn complete(code: &str) -> Vec<CompletionItem> {
211 do_completion(code, CompletionKind::Reference)
212 }
213
214 #[test]
215 fn test_record_literal_deprecated_field() {
216 let completions = complete(
217 r"
218 struct A {
219 #[deprecated]
220 the_field: u32,
221 }
222 fn foo() {
223 A { the<|> }
224 }
225 ",
226 );
227 assert_debug_snapshot!(completions, @r###"
228 [
229 CompletionItem {
230 label: "the_field",
231 source_range: [142; 145),
232 delete: [142; 145),
233 insert: "the_field",
234 kind: Field,
235 detail: "u32",
236 deprecated: true,
237 },
238 ]
239 "###);
240 }
241
242 #[test]
243 fn test_record_literal_field() {
244 let completions = complete(
245 r"
246 struct A { the_field: u32 }
247 fn foo() {
248 A { the<|> }
249 }
250 ",
251 );
252 assert_debug_snapshot!(completions, @r###"
253 [
254 CompletionItem {
255 label: "the_field",
256 source_range: [83; 86),
257 delete: [83; 86),
258 insert: "the_field",
259 kind: Field,
260 detail: "u32",
261 },
262 ]
263 "###);
264 }
265
266 #[test]
267 fn test_record_literal_enum_variant() {
268 let completions = complete(
269 r"
270 enum E {
271 A { a: u32 }
272 }
273 fn foo() {
274 let _ = E::A { <|> }
275 }
276 ",
277 );
278 assert_debug_snapshot!(completions, @r###"
279 [
280 CompletionItem {
281 label: "a",
282 source_range: [119; 119),
283 delete: [119; 119),
284 insert: "a",
285 kind: Field,
286 detail: "u32",
287 },
288 ]
289 "###);
290 }
291
292 #[test]
293 fn test_record_literal_two_structs() {
294 let completions = complete(
295 r"
296 struct A { a: u32 }
297 struct B { b: u32 }
298
299 fn foo() {
300 let _: A = B { <|> }
301 }
302 ",
303 );
304 assert_debug_snapshot!(completions, @r###"
305 [
306 CompletionItem {
307 label: "b",
308 source_range: [119; 119),
309 delete: [119; 119),
310 insert: "b",
311 kind: Field,
312 detail: "u32",
313 },
314 ]
315 "###);
316 }
317
318 #[test]
319 fn test_record_literal_generic_struct() {
320 let completions = complete(
321 r"
322 struct A<T> { a: T }
323
324 fn foo() {
325 let _: A<u32> = A { <|> }
326 }
327 ",
328 );
329 assert_debug_snapshot!(completions, @r###"
330 [
331 CompletionItem {
332 label: "a",
333 source_range: [93; 93),
334 delete: [93; 93),
335 insert: "a",
336 kind: Field,
337 detail: "u32",
338 },
339 ]
340 "###);
341 }
342
343 #[test]
344 fn test_record_literal_field_in_simple_macro() {
345 let completions = complete(
346 r"
347 macro_rules! m { ($e:expr) => { $e } }
348 struct A { the_field: u32 }
349 fn foo() {
350 m!(A { the<|> })
351 }
352 ",
353 );
354 assert_debug_snapshot!(completions, @r###"
355 [
356 CompletionItem {
357 label: "the_field",
358 source_range: [137; 140),
359 delete: [137; 140),
360 insert: "the_field",
361 kind: Field,
362 detail: "u32",
363 },
364 ]
365 "###);
366 }
367
368 #[test]
369 fn only_missing_fields_are_completed() {
370 let completions = complete(
371 r"
372 struct S {
373 foo1: u32,
374 foo2: u32,
375 bar: u32,
376 baz: u32,
377 }
378
379 fn main() {
380 let foo1 = 1;
381 let s = S {
382 foo1,
383 foo2: 5,
384 <|>
385 }
386 }
387 ",
388 );
389 assert_debug_snapshot!(completions, @r###"
390 [
391 CompletionItem {
392 label: "bar",
393 source_range: [302; 302),
394 delete: [302; 302),
395 insert: "bar",
396 kind: Field,
397 detail: "u32",
398 },
399 CompletionItem {
400 label: "baz",
401 source_range: [302; 302),
402 delete: [302; 302),
403 insert: "baz",
404 kind: Field,
405 detail: "u32",
406 },
407 ]
408 "###);
409 }
410 }
411}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
deleted file mode 100644
index e4e764f58..000000000
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ /dev/null
@@ -1,241 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4use ra_syntax::SmolStr;
5
6/// Complete fields in fields literals.
7pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
8 let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| {
9 Some((ctx.sema.type_of_expr(&it.clone().into())?, ctx.sema.resolve_record_literal(it)?))
10 }) {
11 Some(it) => it,
12 _ => return,
13 };
14
15 let already_present_names: Vec<SmolStr> = ctx
16 .record_lit_syntax
17 .as_ref()
18 .and_then(|record_literal| record_literal.record_field_list())
19 .map(|field_list| field_list.fields())
20 .map(|fields| {
21 fields
22 .into_iter()
23 .filter_map(|field| field.name_ref())
24 .map(|name_ref| name_ref.text().clone())
25 .collect()
26 })
27 .unwrap_or_default();
28
29 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
30 if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) {
31 acc.add_field(ctx, field, &field_ty);
32 }
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
39 use insta::assert_debug_snapshot;
40
41 fn complete(code: &str) -> Vec<CompletionItem> {
42 do_completion(code, CompletionKind::Reference)
43 }
44
45 #[test]
46 fn test_record_literal_deprecated_field() {
47 let completions = complete(
48 r"
49 struct A {
50 #[deprecated]
51 the_field: u32,
52 }
53 fn foo() {
54 A { the<|> }
55 }
56 ",
57 );
58 assert_debug_snapshot!(completions, @r###"
59 [
60 CompletionItem {
61 label: "the_field",
62 source_range: [142; 145),
63 delete: [142; 145),
64 insert: "the_field",
65 kind: Field,
66 detail: "u32",
67 deprecated: true,
68 },
69 ]
70 "###);
71 }
72
73 #[test]
74 fn test_record_literal_field() {
75 let completions = complete(
76 r"
77 struct A { the_field: u32 }
78 fn foo() {
79 A { the<|> }
80 }
81 ",
82 );
83 assert_debug_snapshot!(completions, @r###"
84 [
85 CompletionItem {
86 label: "the_field",
87 source_range: [83; 86),
88 delete: [83; 86),
89 insert: "the_field",
90 kind: Field,
91 detail: "u32",
92 },
93 ]
94 "###);
95 }
96
97 #[test]
98 fn test_record_literal_enum_variant() {
99 let completions = complete(
100 r"
101 enum E {
102 A { a: u32 }
103 }
104 fn foo() {
105 let _ = E::A { <|> }
106 }
107 ",
108 );
109 assert_debug_snapshot!(completions, @r###"
110 [
111 CompletionItem {
112 label: "a",
113 source_range: [119; 119),
114 delete: [119; 119),
115 insert: "a",
116 kind: Field,
117 detail: "u32",
118 },
119 ]
120 "###);
121 }
122
123 #[test]
124 fn test_record_literal_two_structs() {
125 let completions = complete(
126 r"
127 struct A { a: u32 }
128 struct B { b: u32 }
129
130 fn foo() {
131 let _: A = B { <|> }
132 }
133 ",
134 );
135 assert_debug_snapshot!(completions, @r###"
136 [
137 CompletionItem {
138 label: "b",
139 source_range: [119; 119),
140 delete: [119; 119),
141 insert: "b",
142 kind: Field,
143 detail: "u32",
144 },
145 ]
146 "###);
147 }
148
149 #[test]
150 fn test_record_literal_generic_struct() {
151 let completions = complete(
152 r"
153 struct A<T> { a: T }
154
155 fn foo() {
156 let _: A<u32> = A { <|> }
157 }
158 ",
159 );
160 assert_debug_snapshot!(completions, @r###"
161 [
162 CompletionItem {
163 label: "a",
164 source_range: [93; 93),
165 delete: [93; 93),
166 insert: "a",
167 kind: Field,
168 detail: "u32",
169 },
170 ]
171 "###);
172 }
173
174 #[test]
175 fn test_record_literal_field_in_simple_macro() {
176 let completions = complete(
177 r"
178 macro_rules! m { ($e:expr) => { $e } }
179 struct A { the_field: u32 }
180 fn foo() {
181 m!(A { the<|> })
182 }
183 ",
184 );
185 assert_debug_snapshot!(completions, @r###"
186 [
187 CompletionItem {
188 label: "the_field",
189 source_range: [137; 140),
190 delete: [137; 140),
191 insert: "the_field",
192 kind: Field,
193 detail: "u32",
194 },
195 ]
196 "###);
197 }
198
199 #[test]
200 fn only_missing_fields_are_completed() {
201 let completions = complete(
202 r"
203 struct S {
204 foo1: u32,
205 foo2: u32,
206 bar: u32,
207 baz: u32,
208 }
209
210 fn main() {
211 let foo1 = 1;
212 let s = S {
213 foo1,
214 foo2: 5,
215 <|>
216 }
217 }
218 ",
219 );
220 assert_debug_snapshot!(completions, @r###"
221 [
222 CompletionItem {
223 label: "bar",
224 source_range: [302; 302),
225 delete: [302; 302),
226 insert: "bar",
227 kind: Field,
228 detail: "u32",
229 },
230 CompletionItem {
231 label: "baz",
232 source_range: [302; 302),
233 delete: [302; 302),
234 insert: "baz",
235 kind: Field,
236 detail: "u32",
237 },
238 ]
239 "###);
240 }
241}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
deleted file mode 100644
index 962376428..000000000
--- a/crates/ra_ide/src/completion/complete_record_pattern.rs
+++ /dev/null
@@ -1,118 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) {
6 let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| {
7 Some((ctx.sema.type_of_pat(&it.clone().into())?, ctx.sema.resolve_record_pattern(it)?))
8 }) {
9 Some(it) => it,
10 _ => return,
11 };
12
13 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
14 acc.add_field(ctx, field, &field_ty);
15 }
16}
17
18#[cfg(test)]
19mod tests {
20 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
21 use insta::assert_debug_snapshot;
22
23 fn complete(code: &str) -> Vec<CompletionItem> {
24 do_completion(code, CompletionKind::Reference)
25 }
26
27 #[test]
28 fn test_record_pattern_field() {
29 let completions = complete(
30 r"
31 struct S { foo: u32 }
32
33 fn process(f: S) {
34 match f {
35 S { f<|>: 92 } => (),
36 }
37 }
38 ",
39 );
40 assert_debug_snapshot!(completions, @r###"
41 [
42 CompletionItem {
43 label: "foo",
44 source_range: [117; 118),
45 delete: [117; 118),
46 insert: "foo",
47 kind: Field,
48 detail: "u32",
49 },
50 ]
51 "###);
52 }
53
54 #[test]
55 fn test_record_pattern_enum_variant() {
56 let completions = complete(
57 r"
58 enum E {
59 S { foo: u32, bar: () }
60 }
61
62 fn process(e: E) {
63 match e {
64 E::S { <|> } => (),
65 }
66 }
67 ",
68 );
69 assert_debug_snapshot!(completions, @r###"
70 [
71 CompletionItem {
72 label: "bar",
73 source_range: [161; 161),
74 delete: [161; 161),
75 insert: "bar",
76 kind: Field,
77 detail: "()",
78 },
79 CompletionItem {
80 label: "foo",
81 source_range: [161; 161),
82 delete: [161; 161),
83 insert: "foo",
84 kind: Field,
85 detail: "u32",
86 },
87 ]
88 "###);
89 }
90
91 #[test]
92 fn test_record_pattern_field_in_simple_macro() {
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 struct S { foo: u32 }
97
98 fn process(f: S) {
99 m!(match f {
100 S { f<|>: 92 } => (),
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"
106 [
107 CompletionItem {
108 label: "foo",
109 source_range: [171; 172),
110 delete: [171; 172),
111 insert: "foo",
112 kind: Field,
113 detail: "u32",
114 },
115 ]
116 "###);
117 }
118}
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 1c9710a5d..1abb891c1 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -5,7 +5,7 @@ use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_ide_db::symbol_index::SymbolsDatabase; 5use ra_ide_db::symbol_index::SymbolsDatabase;
6use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
7use ra_syntax::ast::make::try_expr_from_text; 7use ra_syntax::ast::make::try_expr_from_text;
8use ra_syntax::ast::{AstToken, Comment}; 8use ra_syntax::ast::{AstToken, Comment, RecordField, RecordLit};
9use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; 9use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
10use ra_text_edit::{TextEdit, TextEditBuilder}; 10use ra_text_edit::{TextEdit, TextEditBuilder};
11use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
@@ -186,47 +186,102 @@ fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrErr
186} 186}
187 187
188fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { 188fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
189 fn check_record_lit(
190 pattern: RecordLit,
191 code: RecordLit,
192 placeholders: &[Var],
193 match_: Match,
194 ) -> Option<Match> {
195 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
196
197 let mut pattern_fields =
198 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
199 let mut code_fields =
200 code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
201
202 if pattern_fields.len() != code_fields.len() {
203 return None;
204 }
205
206 let by_name = |a: &RecordField, b: &RecordField| {
207 a.name_ref()
208 .map(|x| x.syntax().text().to_string())
209 .cmp(&b.name_ref().map(|x| x.syntax().text().to_string()))
210 };
211 pattern_fields.sort_by(by_name);
212 code_fields.sort_by(by_name);
213
214 pattern_fields.into_iter().zip(code_fields.into_iter()).fold(
215 Some(match_),
216 |accum, (a, b)| {
217 accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_))
218 },
219 )
220 }
221
222 fn check_opt_nodes(
223 pattern: Option<impl AstNode>,
224 code: Option<impl AstNode>,
225 placeholders: &[Var],
226 match_: Match,
227 ) -> Option<Match> {
228 match (pattern, code) {
229 (Some(pattern), Some(code)) => check(
230 &SyntaxElement::from(pattern.syntax().clone()),
231 &SyntaxElement::from(code.syntax().clone()),
232 placeholders,
233 match_,
234 ),
235 (None, None) => Some(match_),
236 _ => None,
237 }
238 }
239
189 fn check( 240 fn check(
190 pattern: &SyntaxElement, 241 pattern: &SyntaxElement,
191 code: &SyntaxElement, 242 code: &SyntaxElement,
192 placeholders: &[Var], 243 placeholders: &[Var],
193 mut match_: Match, 244 mut match_: Match,
194 ) -> Option<Match> { 245 ) -> Option<Match> {
195 match (pattern, code) { 246 match (&pattern, &code) {
196 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => { 247 (SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => {
197 if pattern.text() == code.text() { 248 if pattern.text() == code.text() {
198 Some(match_) 249 Some(match_)
199 } else { 250 } else {
200 None 251 None
201 } 252 }
202 } 253 }
203 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => { 254 (SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => {
204 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { 255 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
205 match_.binding.insert(Var(pattern.text().to_string()), code.clone()); 256 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
206 Some(match_) 257 Some(match_)
207 } else { 258 } else {
208 let mut pattern_children = pattern 259 if let (Some(pattern), Some(code)) =
209 .children_with_tokens() 260 (RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone()))
210 .filter(|element| !element.kind().is_trivia()); 261 {
211 let mut code_children = 262 check_record_lit(pattern, code, placeholders, match_)
212 code.children_with_tokens().filter(|element| !element.kind().is_trivia()); 263 } else {
213 let new_ignored_comments = code.children_with_tokens().filter_map(|element| { 264 let mut pattern_children = pattern
214 element.as_token().and_then(|token| Comment::cast(token.clone())) 265 .children_with_tokens()
215 }); 266 .filter(|element| !element.kind().is_trivia());
216 match_.ignored_comments.extend(new_ignored_comments); 267 let mut code_children = code
217 let match_from_children = pattern_children 268 .children_with_tokens()
218 .by_ref() 269 .filter(|element| !element.kind().is_trivia());
219 .zip(code_children.by_ref()) 270 let new_ignored_comments =
220 .fold(Some(match_), |accum, (a, b)| { 271 code.children_with_tokens().filter_map(|element| {
221 accum.and_then(|match_| check(&a, &b, placeholders, match_)) 272 element.as_token().and_then(|token| Comment::cast(token.clone()))
222 }); 273 });
223 match_from_children.and_then(|match_| { 274 match_.ignored_comments.extend(new_ignored_comments);
224 if pattern_children.count() == 0 && code_children.count() == 0 { 275 pattern_children
225 Some(match_) 276 .by_ref()
226 } else { 277 .zip(code_children.by_ref())
227 None 278 .fold(Some(match_), |accum, (a, b)| {
228 } 279 accum.and_then(|match_| check(&a, &b, placeholders, match_))
229 }) 280 })
281 .filter(|_| {
282 pattern_children.next().is_none() && code_children.next().is_none()
283 })
284 }
230 } 285 }
231 } 286 }
232 _ => None, 287 _ => None,
@@ -434,4 +489,13 @@ mod tests {
434 "fn main() { bar(5)/* using 5 */ }", 489 "fn main() { bar(5)/* using 5 */ }",
435 ) 490 )
436 } 491 }
492
493 #[test]
494 fn ssr_struct_lit() {
495 assert_ssr_transform(
496 "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)",
497 "fn main() { foo{b:2, a:1} }",
498 "fn main() { foo::new(1, 2) }",
499 )
500 }
437} 501}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index bb33fb27d..a89ea86ea 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -21,7 +21,7 @@ use lsp_types::{
21 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, 21 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
22 WorkDoneProgressReport, 22 WorkDoneProgressReport,
23}; 23};
24use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckConfig, CheckTask}; 24use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask, FlycheckConfig};
25use ra_ide::{Canceled, FileId, InlayHintsConfig, LibraryData, SourceRootId}; 25use ra_ide::{Canceled, FileId, InlayHintsConfig, LibraryData, SourceRootId};
26use ra_prof::profile; 26use ra_prof::profile;
27use ra_vfs::{VfsFile, VfsTask, Watch}; 27use ra_vfs::{VfsFile, VfsTask, Watch};
@@ -101,11 +101,14 @@ fn get_config(
101 chaining_hints: config.inlay_hints_chaining, 101 chaining_hints: config.inlay_hints_chaining,
102 max_length: config.inlay_hints_max_length, 102 max_length: config.inlay_hints_max_length,
103 }, 103 },
104 check: CheckConfig { 104 check: if config.cargo_watch_enable {
105 enable: config.cargo_watch_enable, 105 Some(FlycheckConfig::CargoCommand {
106 args: config.cargo_watch_args.clone(), 106 command: config.cargo_watch_command.clone(),
107 command: config.cargo_watch_command.clone(), 107 all_targets: config.cargo_watch_all_targets,
108 all_targets: config.cargo_watch_all_targets, 108 extra_args: config.cargo_watch_args.clone(),
109 })
110 } else {
111 None
109 }, 112 },
110 rustfmt_args: config.rustfmt_args.clone(), 113 rustfmt_args: config.rustfmt_args.clone(),
111 vscode_lldb: config.vscode_lldb, 114 vscode_lldb: config.vscode_lldb,
@@ -240,7 +243,7 @@ pub fn main_loop(
240 Err(RecvError) => return Err("vfs died".into()), 243 Err(RecvError) => return Err("vfs died".into()),
241 }, 244 },
242 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), 245 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
243 recv(world_state.check_watcher.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { 246 recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
244 Ok(task) => Event::CheckWatcher(task), 247 Ok(task) => Event::CheckWatcher(task),
245 Err(RecvError) => return Err("check watcher died".into()), 248 Err(RecvError) => return Err("check watcher died".into()),
246 } 249 }
@@ -481,8 +484,8 @@ fn loop_turn(
481 && loop_state.in_flight_libraries == 0 484 && loop_state.in_flight_libraries == 0
482 { 485 {
483 loop_state.workspace_loaded = true; 486 loop_state.workspace_loaded = true;
484 if let Some(check_watcher) = &world_state.check_watcher { 487 if let Some(flycheck) = &world_state.flycheck {
485 check_watcher.update(); 488 flycheck.update();
486 } 489 }
487 pool.execute({ 490 pool.execute({
488 let subs = loop_state.subscriptions.subscriptions(); 491 let subs = loop_state.subscriptions.subscriptions();
@@ -654,8 +657,8 @@ fn on_notification(
654 }; 657 };
655 let not = match notification_cast::<req::DidSaveTextDocument>(not) { 658 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
656 Ok(_params) => { 659 Ok(_params) => {
657 if let Some(check_watcher) = &state.check_watcher { 660 if let Some(flycheck) = &state.flycheck {
658 check_watcher.update(); 661 flycheck.update();
659 } 662 }
660 return Ok(()); 663 return Ok(());
661 } 664 }
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index acb729bae..2db058eb1 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -11,7 +11,7 @@ use std::{
11use crossbeam_channel::{unbounded, Receiver}; 11use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 12use lsp_types::Url;
13use parking_lot::RwLock; 13use parking_lot::RwLock;
14use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckConfig, CheckWatcher}; 14use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig};
15use ra_ide::{ 15use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsConfig, LibraryData, 16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsConfig, LibraryData,
17 SourceRootId, 17 SourceRootId,
@@ -31,7 +31,9 @@ use crate::{
31use ra_db::ExternSourceId; 31use ra_db::ExternSourceId;
32use rustc_hash::{FxHashMap, FxHashSet}; 32use rustc_hash::{FxHashMap, FxHashSet};
33 33
34fn create_watcher(workspaces: &[ProjectWorkspace], config: &Config) -> Option<CheckWatcher> { 34fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<Flycheck> {
35 let check_config = config.check.as_ref()?;
36
35 // FIXME: Figure out the multi-workspace situation 37 // FIXME: Figure out the multi-workspace situation
36 workspaces 38 workspaces
37 .iter() 39 .iter()
@@ -41,7 +43,7 @@ fn create_watcher(workspaces: &[ProjectWorkspace], config: &Config) -> Option<Ch
41 }) 43 })
42 .map(|cargo| { 44 .map(|cargo| {
43 let cargo_project_root = cargo.workspace_root().to_path_buf(); 45 let cargo_project_root = cargo.workspace_root().to_path_buf();
44 Some(CheckWatcher::new(config.check.clone(), cargo_project_root)) 46 Some(Flycheck::new(check_config.clone(), cargo_project_root))
45 }) 47 })
46 .unwrap_or_else(|| { 48 .unwrap_or_else(|| {
47 log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); 49 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
@@ -56,7 +58,7 @@ pub struct Config {
56 pub line_folding_only: bool, 58 pub line_folding_only: bool,
57 pub inlay_hints: InlayHintsConfig, 59 pub inlay_hints: InlayHintsConfig,
58 pub rustfmt_args: Vec<String>, 60 pub rustfmt_args: Vec<String>,
59 pub check: CheckConfig, 61 pub check: Option<FlycheckConfig>,
60 pub vscode_lldb: bool, 62 pub vscode_lldb: bool,
61 pub proc_macro_srv: Option<String>, 63 pub proc_macro_srv: Option<String>,
62} 64}
@@ -76,7 +78,7 @@ pub struct WorldState {
76 pub vfs: Arc<RwLock<Vfs>>, 78 pub vfs: Arc<RwLock<Vfs>>,
77 pub task_receiver: Receiver<VfsTask>, 79 pub task_receiver: Receiver<VfsTask>,
78 pub latest_requests: Arc<RwLock<LatestRequests>>, 80 pub latest_requests: Arc<RwLock<LatestRequests>>,
79 pub check_watcher: Option<CheckWatcher>, 81 pub flycheck: Option<Flycheck>,
80 pub diagnostics: DiagnosticCollection, 82 pub diagnostics: DiagnosticCollection,
81} 83}
82 84
@@ -201,7 +203,7 @@ impl WorldState {
201 }); 203 });
202 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
203 205
204 let check_watcher = create_watcher(&workspaces, &config); 206 let flycheck = create_flycheck(&workspaces, &config);
205 207
206 let mut analysis_host = AnalysisHost::new(lru_capacity); 208 let mut analysis_host = AnalysisHost::new(lru_capacity);
207 analysis_host.apply_change(change); 209 analysis_host.apply_change(change);
@@ -214,7 +216,7 @@ impl WorldState {
214 vfs: Arc::new(RwLock::new(vfs)), 216 vfs: Arc::new(RwLock::new(vfs)),
215 task_receiver, 217 task_receiver,
216 latest_requests: Default::default(), 218 latest_requests: Default::default(),
217 check_watcher, 219 flycheck,
218 diagnostics: Default::default(), 220 diagnostics: Default::default(),
219 } 221 }
220 } 222 }
@@ -227,7 +229,7 @@ impl WorldState {
227 ) { 229 ) {
228 self.feature_flags = Arc::new(feature_flags); 230 self.feature_flags = Arc::new(feature_flags);
229 self.analysis_host.update_lru_capacity(lru_capacity); 231 self.analysis_host.update_lru_capacity(lru_capacity);
230 self.check_watcher = create_watcher(&self.workspaces, &config); 232 self.flycheck = create_flycheck(&self.workspaces, &config);
231 self.config = config; 233 self.config = config;
232 } 234 }
233 235