diff options
Diffstat (limited to 'crates/ra_cargo_watch')
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" | |||
11 | cargo_metadata = "0.9.1" | 11 | cargo_metadata = "0.9.1" |
12 | jod-thread = "0.1.0" | 12 | jod-thread = "0.1.0" |
13 | parking_lot = "0.10.0" | 13 | parking_lot = "0.10.0" |
14 | serde_json = "1.0.45" | ||
14 | 15 | ||
15 | [dev-dependencies] | 16 | [dev-dependencies] |
16 | insta = "0.12.0" | 17 | insta = "0.13.0" |
17 | serde_json = "1.0" \ No newline at end of file | 18 | serde_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. |
3 | use cargo_metadata::diagnostic::{ | 3 | use cargo_metadata::diagnostic::{ |
4 | Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, | 4 | Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, |
5 | DiagnosticSpanMacroExpansion, | ||
6 | }; | 5 | }; |
7 | use lsp_types::{ | 6 | use 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 | }; |
11 | use std::{ | 10 | use 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)] | ||
121 | pub 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 | |||
129 | impl 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 | |||
149 | enum MappedRustChildDiagnostic { | 119 | enum 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( | |||
195 | pub(crate) struct MappedRustDiagnostic { | 173 | pub(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 @@ | |||
4 | use cargo_metadata::Message; | 4 | use cargo_metadata::Message; |
5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | 5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; |
6 | use lsp_types::{ | 6 | use lsp_types::{ |
7 | Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, | 7 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, |
8 | WorkDoneProgressReport, | 8 | WorkDoneProgressEnd, WorkDoneProgressReport, |
9 | }; | 9 | }; |
10 | use parking_lot::RwLock; | ||
11 | use std::{ | 10 | use 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 | ||
20 | mod conv; | 18 | mod conv; |
21 | 19 | ||
22 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix}; | 20 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; |
23 | 21 | ||
24 | pub use crate::conv::url_from_path_with_drive_lowercasing; | 22 | pub use crate::conv::url_from_path_with_drive_lowercasing; |
25 | 23 | ||
@@ -38,7 +36,6 @@ pub struct CheckOptions { | |||
38 | #[derive(Debug)] | 36 | #[derive(Debug)] |
39 | pub struct CheckWatcher { | 37 | pub 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 { | |||
46 | impl CheckWatcher { | 43 | impl 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)] |
91 | pub struct CheckState { | ||
92 | diagnostic_collection: HashMap<Url, Vec<Diagnostic>>, | ||
93 | suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>, | ||
94 | } | ||
95 | |||
96 | impl 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)] | ||
162 | pub enum CheckTask { | 86 | pub 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)] |
293 | pub struct DiagnosticWithFixes { | 226 | pub 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 | |||
400 | fn 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 | } | ||