aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_cargo_watch
diff options
context:
space:
mode:
authorKevin DeLorey <[email protected]>2020-02-09 16:25:47 +0000
committerKevin DeLorey <[email protected]>2020-02-09 16:37:43 +0000
commita957c473fdb79880c39b73dc9e0c923093cf16ac (patch)
treef998b548f530ce604651e0e6af314ed2ec74b3b5 /crates/ra_cargo_watch
parent22caf982b99c54058e2e9200aeea0e61cada284a (diff)
parent1b9b13b4b4a75b5531c3f046ce6bf72d681f2732 (diff)
Merge branch 'master' into kdelorey/complete-trait-impl
Diffstat (limited to 'crates/ra_cargo_watch')
-rw-r--r--crates/ra_cargo_watch/Cargo.toml3
-rw-r--r--crates/ra_cargo_watch/src/conv.rs66
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap)48
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap)48
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap)2
-rw-r--r--crates/ra_cargo_watch/src/lib.rs153
10 files changed, 145 insertions, 183 deletions
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_cargo_watch/Cargo.toml
index e88295539..dd814fc9d 100644
--- a/crates/ra_cargo_watch/Cargo.toml
+++ b/crates/ra_cargo_watch/Cargo.toml
@@ -11,7 +11,8 @@ log = "0.4.3"
11cargo_metadata = "0.9.1" 11cargo_metadata = "0.9.1"
12jod-thread = "0.1.0" 12jod-thread = "0.1.0"
13parking_lot = "0.10.0" 13parking_lot = "0.10.0"
14serde_json = "1.0.45"
14 15
15[dev-dependencies] 16[dev-dependencies]
16insta = "0.12.0" 17insta = "0.13.0"
17serde_json = "1.0" \ No newline at end of file 18serde_json = "1.0" \ No newline at end of file
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_cargo_watch/src/conv.rs
index ac0f1d28a..506370535 100644
--- a/crates/ra_cargo_watch/src/conv.rs
+++ b/crates/ra_cargo_watch/src/conv.rs
@@ -1,12 +1,11 @@
1//! This module provides the functionality needed to convert diagnostics from 1//! This module provides the functionality needed to convert diagnostics from
2//! `cargo check` json format to the LSP diagnostic format. 2//! `cargo check` json format to the LSP diagnostic format.
3use cargo_metadata::diagnostic::{ 3use cargo_metadata::diagnostic::{
4 Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, 4 Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion,
5 DiagnosticSpanMacroExpansion,
6}; 5};
7use lsp_types::{ 6use lsp_types::{
8 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, 7 CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag,
9 NumberOrString, Position, Range, Url, 8 Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit,
10}; 9};
11use std::{ 10use std::{
12 fmt::Write, 11 fmt::Write,
@@ -117,38 +116,9 @@ fn is_deprecated(rd: &RustDiagnostic) -> bool {
117 } 116 }
118} 117}
119 118
120#[derive(Debug)]
121pub struct SuggestedFix {
122 pub title: String,
123 pub location: Location,
124 pub replacement: String,
125 pub applicability: Applicability,
126 pub diagnostics: Vec<Diagnostic>,
127}
128
129impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
130 fn eq(&self, other: &SuggestedFix) -> bool {
131 if self.title == other.title
132 && self.location == other.location
133 && self.replacement == other.replacement
134 {
135 // Applicability doesn't impl PartialEq...
136 match (&self.applicability, &other.applicability) {
137 (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
138 (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
139 (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
140 (Applicability::Unspecified, Applicability::Unspecified) => true,
141 _ => false,
142 }
143 } else {
144 false
145 }
146 }
147}
148
149enum MappedRustChildDiagnostic { 119enum MappedRustChildDiagnostic {
150 Related(DiagnosticRelatedInformation), 120 Related(DiagnosticRelatedInformation),
151 SuggestedFix(SuggestedFix), 121 SuggestedFix(CodeAction),
152 MessageLine(String), 122 MessageLine(String),
153} 123}
154 124
@@ -176,12 +146,20 @@ fn map_rust_child_diagnostic(
176 rd.message.clone() 146 rd.message.clone()
177 }; 147 };
178 148
179 MappedRustChildDiagnostic::SuggestedFix(SuggestedFix { 149 let edit = {
150 let edits = vec![TextEdit::new(location.range, suggested_replacement.clone())];
151 let mut edit_map = std::collections::HashMap::new();
152 edit_map.insert(location.uri, edits);
153 WorkspaceEdit::new(edit_map)
154 };
155
156 MappedRustChildDiagnostic::SuggestedFix(CodeAction {
180 title, 157 title,
181 location, 158 kind: Some("quickfix".to_string()),
182 replacement: suggested_replacement.clone(), 159 diagnostics: None,
183 applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown), 160 edit: Some(edit),
184 diagnostics: vec![], 161 command: None,
162 is_preferred: None,
185 }) 163 })
186 } else { 164 } else {
187 MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { 165 MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
@@ -195,7 +173,7 @@ fn map_rust_child_diagnostic(
195pub(crate) struct MappedRustDiagnostic { 173pub(crate) struct MappedRustDiagnostic {
196 pub location: Location, 174 pub location: Location,
197 pub diagnostic: Diagnostic, 175 pub diagnostic: Diagnostic,
198 pub suggested_fixes: Vec<SuggestedFix>, 176 pub fixes: Vec<CodeAction>,
199} 177}
200 178
201/// Converts a Rust root diagnostic to LSP form 179/// Converts a Rust root diagnostic to LSP form
@@ -250,15 +228,13 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
250 } 228 }
251 } 229 }
252 230
253 let mut suggested_fixes = vec![]; 231 let mut fixes = vec![];
254 let mut message = rd.message.clone(); 232 let mut message = rd.message.clone();
255 for child in &rd.children { 233 for child in &rd.children {
256 let child = map_rust_child_diagnostic(&child, workspace_root); 234 let child = map_rust_child_diagnostic(&child, workspace_root);
257 match child { 235 match child {
258 MappedRustChildDiagnostic::Related(related) => related_information.push(related), 236 MappedRustChildDiagnostic::Related(related) => related_information.push(related),
259 MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => { 237 MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action.into()),
260 suggested_fixes.push(suggested_fix)
261 }
262 MappedRustChildDiagnostic::MessageLine(message_line) => { 238 MappedRustChildDiagnostic::MessageLine(message_line) => {
263 write!(&mut message, "\n{}", message_line).unwrap(); 239 write!(&mut message, "\n{}", message_line).unwrap();
264 240
@@ -295,7 +271,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
295 tags: if !tags.is_empty() { Some(tags) } else { None }, 271 tags: if !tags.is_empty() { Some(tags) } else { None },
296 }; 272 };
297 273
298 Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes }) 274 Some(MappedRustDiagnostic { location, diagnostic, fixes })
299} 275}
300 276
301/// Returns a `Url` object from a given path, will lowercase drive letters if present. 277/// Returns a `Url` object from a given path, will lowercase drive letters if present.
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap
index cb0920914..95ca163dc 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap
@@ -61,25 +61,39 @@ MappedRustDiagnostic {
61 ), 61 ),
62 tags: None, 62 tags: None,
63 }, 63 },
64 suggested_fixes: [ 64 fixes: [
65 SuggestedFix { 65 CodeAction {
66 title: "consider passing by value instead: \'self\'", 66 title: "consider passing by value instead: \'self\'",
67 location: Location { 67 kind: Some(
68 uri: "file:///test/compiler/mir/tagset.rs", 68 "quickfix",
69 range: Range { 69 ),
70 start: Position { 70 diagnostics: None,
71 line: 41, 71 edit: Some(
72 character: 23, 72 WorkspaceEdit {
73 }, 73 changes: Some(
74 end: Position { 74 {
75 line: 41, 75 "file:///test/compiler/mir/tagset.rs": [
76 character: 28, 76 TextEdit {
77 }, 77 range: Range {
78 start: Position {
79 line: 41,
80 character: 23,
81 },
82 end: Position {
83 line: 41,
84 character: 28,
85 },
86 },
87 new_text: "self",
88 },
89 ],
90 },
91 ),
92 document_changes: None,
78 }, 93 },
79 }, 94 ),
80 replacement: "self", 95 command: None,
81 applicability: Unspecified, 96 is_preferred: None,
82 diagnostics: [],
83 }, 97 },
84 ], 98 ],
85} 99}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap
index 19510ecc1..12eb32df4 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap
@@ -42,5 +42,5 @@ MappedRustDiagnostic {
42 related_information: None, 42 related_information: None,
43 tags: None, 43 tags: None,
44 }, 44 },
45 suggested_fixes: [], 45 fixes: [],
46} 46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap
index 92f7eec05..7b83a7cd0 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap
@@ -57,5 +57,5 @@ MappedRustDiagnostic {
57 ), 57 ),
58 tags: None, 58 tags: None,
59 }, 59 },
60 suggested_fixes: [], 60 fixes: [],
61} 61}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap
index cf683e4b6..54679c5db 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap
@@ -42,5 +42,5 @@ MappedRustDiagnostic {
42 related_information: None, 42 related_information: None,
43 tags: None, 43 tags: None,
44 }, 44 },
45 suggested_fixes: [], 45 fixes: [],
46} 46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap
index 8c1483c74..57df4ceaf 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap
@@ -42,5 +42,5 @@ MappedRustDiagnostic {
42 related_information: None, 42 related_information: None,
43 tags: None, 43 tags: None,
44 }, 44 },
45 suggested_fixes: [], 45 fixes: [],
46} 46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap
index eb5a2247b..3e1fe736c 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap
@@ -46,25 +46,39 @@ MappedRustDiagnostic {
46 ], 46 ],
47 ), 47 ),
48 }, 48 },
49 suggested_fixes: [ 49 fixes: [
50 SuggestedFix { 50 CodeAction {
51 title: "consider prefixing with an underscore: \'_foo\'", 51 title: "consider prefixing with an underscore: \'_foo\'",
52 location: Location { 52 kind: Some(
53 uri: "file:///test/driver/subcommand/repl.rs", 53 "quickfix",
54 range: Range { 54 ),
55 start: Position { 55 diagnostics: None,
56 line: 290, 56 edit: Some(
57 character: 8, 57 WorkspaceEdit {
58 }, 58 changes: Some(
59 end: Position { 59 {
60 line: 290, 60 "file:///test/driver/subcommand/repl.rs": [
61 character: 11, 61 TextEdit {
62 }, 62 range: Range {
63 start: Position {
64 line: 290,
65 character: 8,
66 },
67 end: Position {
68 line: 290,
69 character: 11,
70 },
71 },
72 new_text: "_foo",
73 },
74 ],
75 },
76 ),
77 document_changes: None,
63 }, 78 },
64 }, 79 ),
65 replacement: "_foo", 80 command: None,
66 applicability: MachineApplicable, 81 is_preferred: None,
67 diagnostics: [],
68 }, 82 },
69 ], 83 ],
70} 84}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap
index 2f4518931..69301078d 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap
@@ -61,5 +61,5 @@ MappedRustDiagnostic {
61 ), 61 ),
62 tags: None, 62 tags: None,
63 }, 63 },
64 suggested_fixes: [], 64 fixes: [],
65} 65}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index 7f4c9280c..f07c34549 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -4,22 +4,20 @@
4use cargo_metadata::Message; 4use cargo_metadata::Message;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; 5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6use lsp_types::{ 6use lsp_types::{
7 Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, 7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressReport, 8 WorkDoneProgressEnd, WorkDoneProgressReport,
9}; 9};
10use parking_lot::RwLock;
11use std::{ 10use std::{
12 collections::HashMap, 11 io::{BufRead, BufReader},
13 path::PathBuf, 12 path::PathBuf,
14 process::{Command, Stdio}, 13 process::{Command, Stdio},
15 sync::Arc,
16 thread::JoinHandle, 14 thread::JoinHandle,
17 time::Instant, 15 time::Instant,
18}; 16};
19 17
20mod conv; 18mod conv;
21 19
22use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix}; 20use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
23 21
24pub use crate::conv::url_from_path_with_drive_lowercasing; 22pub use crate::conv::url_from_path_with_drive_lowercasing;
25 23
@@ -38,7 +36,6 @@ pub struct CheckOptions {
38#[derive(Debug)] 36#[derive(Debug)]
39pub struct CheckWatcher { 37pub struct CheckWatcher {
40 pub task_recv: Receiver<CheckTask>, 38 pub task_recv: Receiver<CheckTask>,
41 pub state: Arc<RwLock<CheckState>>,
42 cmd_send: Option<Sender<CheckCommand>>, 39 cmd_send: Option<Sender<CheckCommand>>,
43 handle: Option<JoinHandle<()>>, 40 handle: Option<JoinHandle<()>>,
44} 41}
@@ -46,7 +43,6 @@ pub struct CheckWatcher {
46impl CheckWatcher { 43impl CheckWatcher {
47 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher { 44 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
48 let options = options.clone(); 45 let options = options.clone();
49 let state = Arc::new(RwLock::new(CheckState::new()));
50 46
51 let (task_send, task_recv) = unbounded::<CheckTask>(); 47 let (task_send, task_recv) = unbounded::<CheckTask>();
52 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 48 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
@@ -54,13 +50,12 @@ impl CheckWatcher {
54 let mut check = CheckWatcherThread::new(options, workspace_root); 50 let mut check = CheckWatcherThread::new(options, workspace_root);
55 check.run(&task_send, &cmd_recv); 51 check.run(&task_send, &cmd_recv);
56 }); 52 });
57 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), state } 53 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) }
58 } 54 }
59 55
60 /// Returns a CheckWatcher that doesn't actually do anything 56 /// Returns a CheckWatcher that doesn't actually do anything
61 pub fn dummy() -> CheckWatcher { 57 pub fn dummy() -> CheckWatcher {
62 let state = Arc::new(RwLock::new(CheckState::new())); 58 CheckWatcher { task_recv: never(), cmd_send: None, handle: None }
63 CheckWatcher { task_recv: never(), cmd_send: None, handle: None, state }
64 } 59 }
65 60
66 /// Schedule a re-start of the cargo check worker. 61 /// Schedule a re-start of the cargo check worker.
@@ -88,83 +83,12 @@ impl std::ops::Drop for CheckWatcher {
88} 83}
89 84
90#[derive(Debug)] 85#[derive(Debug)]
91pub struct CheckState {
92 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
93 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
94}
95
96impl CheckState {
97 fn new() -> CheckState {
98 CheckState {
99 diagnostic_collection: HashMap::new(),
100 suggested_fix_collection: HashMap::new(),
101 }
102 }
103
104 /// Clear the cached diagnostics, and schedule updating diagnostics by the
105 /// server, to clear stale results.
106 pub fn clear(&mut self) -> Vec<Url> {
107 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
108 self.diagnostic_collection.clear();
109 self.suggested_fix_collection.clear();
110 cleared_files
111 }
112
113 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
114 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
115 }
116
117 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
118 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
119 }
120
121 pub fn add_diagnostic_with_fixes(&mut self, file_uri: Url, diagnostic: DiagnosticWithFixes) {
122 for fix in diagnostic.suggested_fixes {
123 self.add_suggested_fix_for_diagnostic(fix, &diagnostic.diagnostic);
124 }
125 self.add_diagnostic(file_uri, diagnostic.diagnostic);
126 }
127
128 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
129 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
130
131 // If we're building multiple targets it's possible we've already seen this diagnostic
132 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
133 if is_duplicate {
134 return;
135 }
136
137 diagnostics.push(diagnostic);
138 }
139
140 fn add_suggested_fix_for_diagnostic(
141 &mut self,
142 mut suggested_fix: SuggestedFix,
143 diagnostic: &Diagnostic,
144 ) {
145 let file_uri = suggested_fix.location.uri.clone();
146 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
147
148 let existing_suggestion: Option<&mut SuggestedFix> =
149 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
150 if let Some(existing_suggestion) = existing_suggestion {
151 // The existing suggestion also applies to this new diagnostic
152 existing_suggestion.diagnostics.push(diagnostic.clone());
153 } else {
154 // We haven't seen this suggestion before
155 suggested_fix.diagnostics.push(diagnostic.clone());
156 file_suggestions.push(suggested_fix);
157 }
158 }
159}
160
161#[derive(Debug)]
162pub enum CheckTask { 86pub enum CheckTask {
163 /// Request a clearing of all cached diagnostics from the check watcher 87 /// Request a clearing of all cached diagnostics from the check watcher
164 ClearDiagnostics, 88 ClearDiagnostics,
165 89
166 /// Request adding a diagnostic with fixes included to a file 90 /// Request adding a diagnostic with fixes included to a file
167 AddDiagnostic(Url, DiagnosticWithFixes), 91 AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> },
168 92
169 /// Request check progress notification to client 93 /// Request check progress notification to client
170 Status(WorkDoneProgress), 94 Status(WorkDoneProgress),
@@ -216,8 +140,10 @@ impl CheckWatcherThread {
216 self.last_update_req.take(); 140 self.last_update_req.take();
217 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 141 task_send.send(CheckTask::ClearDiagnostics).unwrap();
218 142
219 // By replacing the watcher, we drop the previous one which 143 // Replace with a dummy watcher first so we drop the original and wait for completion
220 // causes it to shut down automatically. 144 std::mem::replace(&mut self.watcher, WatchThread::dummy());
145
146 // Then create the actual new watcher
221 self.watcher = WatchThread::new(&self.options, &self.workspace_root); 147 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
222 } 148 }
223 } 149 }
@@ -277,10 +203,17 @@ impl CheckWatcherThread {
277 None => return, 203 None => return,
278 }; 204 };
279 205
280 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result; 206 let MappedRustDiagnostic { location, diagnostic, fixes } = map_result;
207 let fixes = fixes
208 .into_iter()
209 .map(|fix| {
210 CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into()
211 })
212 .collect();
281 213
282 let diagnostic = DiagnosticWithFixes { diagnostic, suggested_fixes }; 214 task_send
283 task_send.send(CheckTask::AddDiagnostic(location.uri, diagnostic)).unwrap(); 215 .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
216 .unwrap();
284 } 217 }
285 218
286 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} 219 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
@@ -292,7 +225,7 @@ impl CheckWatcherThread {
292#[derive(Debug)] 225#[derive(Debug)]
293pub struct DiagnosticWithFixes { 226pub struct DiagnosticWithFixes {
294 diagnostic: Diagnostic, 227 diagnostic: Diagnostic,
295 suggested_fixes: Vec<SuggestedFix>, 228 fixes: Vec<CodeAction>,
296} 229}
297 230
298/// WatchThread exists to wrap around the communication needed to be able to 231/// WatchThread exists to wrap around the communication needed to be able to
@@ -341,6 +274,7 @@ impl WatchThread {
341 .args(&args) 274 .args(&args)
342 .stdout(Stdio::piped()) 275 .stdout(Stdio::piped())
343 .stderr(Stdio::null()) 276 .stderr(Stdio::null())
277 .stdin(Stdio::null())
344 .spawn() 278 .spawn()
345 .expect("couldn't launch cargo"); 279 .expect("couldn't launch cargo");
346 280
@@ -348,15 +282,45 @@ impl WatchThread {
348 // which will break out of the loop, and continue the shutdown 282 // which will break out of the loop, and continue the shutdown
349 let _ = message_send.send(CheckEvent::Begin); 283 let _ = message_send.send(CheckEvent::Begin);
350 284
351 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { 285 // We manually read a line at a time, instead of using serde's
286 // stream deserializers, because the deserializer cannot recover
287 // from an error, resulting in it getting stuck, because we try to
288 // be resillient against failures.
289 //
290 // Because cargo only outputs one JSON object per line, we can
291 // simply skip a line if it doesn't parse, which just ignores any
292 // erroneus output.
293 let stdout = BufReader::new(command.stdout.take().unwrap());
294 for line in stdout.lines() {
295 let line = match line {
296 Ok(line) => line,
297 Err(err) => {
298 log::error!("Couldn't read line from cargo: {}", err);
299 continue;
300 }
301 };
302
303 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
352 let message = match message { 304 let message = match message {
353 Ok(message) => message, 305 Ok(message) => message,
354 Err(err) => { 306 Err(err) => {
355 log::error!("Invalid json from cargo check, ignoring: {}", err); 307 log::error!(
308 "Invalid json from cargo check, ignoring ({}): {:?} ",
309 err,
310 line
311 );
356 continue; 312 continue;
357 } 313 }
358 }; 314 };
359 315
316 // Skip certain kinds of messages to only spend time on what's useful
317 match &message {
318 Message::CompilerArtifact(artifact) if artifact.fresh => continue,
319 Message::BuildScriptExecuted(_) => continue,
320 Message::Unknown => continue,
321 _ => {}
322 }
323
360 match message_send.send(CheckEvent::Msg(message)) { 324 match message_send.send(CheckEvent::Msg(message)) {
361 Ok(()) => {} 325 Ok(()) => {}
362 Err(_err) => { 326 Err(_err) => {
@@ -396,10 +360,3 @@ impl std::ops::Drop for WatchThread {
396 } 360 }
397 } 361 }
398} 362}
399
400fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
401 left.source == right.source
402 && left.severity == right.severity
403 && left.range == right.range
404 && left.message == right.message
405}