aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_cargo_watch
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_cargo_watch')
-rw-r--r--crates/ra_cargo_watch/src/conv.rs66
-rw-r--r--crates/ra_cargo_watch/src/lib.rs110
2 files changed, 38 insertions, 138 deletions
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_cargo_watch/src/conv.rs
index 8fba400ae..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(Clone, 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/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index a718a5e52..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 std::{ 10use std::{
11 collections::HashMap,
12 io::{BufRead, BufReader}, 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<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(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(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.
@@ -87,84 +82,13 @@ impl std::ops::Drop for CheckWatcher {
87 } 82 }
88} 83}
89 84
90#[derive(Clone, 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)] 85#[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),
@@ -279,10 +203,17 @@ impl CheckWatcherThread {
279 None => return, 203 None => return,
280 }; 204 };
281 205
282 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();
283 213
284 let diagnostic = DiagnosticWithFixes { diagnostic, suggested_fixes }; 214 task_send
285 task_send.send(CheckTask::AddDiagnostic(location.uri, diagnostic)).unwrap(); 215 .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
216 .unwrap();
286 } 217 }
287 218
288 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} 219 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
@@ -294,7 +225,7 @@ impl CheckWatcherThread {
294#[derive(Debug)] 225#[derive(Debug)]
295pub struct DiagnosticWithFixes { 226pub struct DiagnosticWithFixes {
296 diagnostic: Diagnostic, 227 diagnostic: Diagnostic,
297 suggested_fixes: Vec<SuggestedFix>, 228 fixes: Vec<CodeAction>,
298} 229}
299 230
300/// 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
@@ -429,10 +360,3 @@ impl std::ops::Drop for WatchThread {
429 } 360 }
430 } 361 }
431} 362}
432
433fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
434 left.source == right.source
435 && left.severity == right.severity
436 && left.range == right.range
437 && left.message == right.message
438}