aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-03 11:27:31 +0000
committerGitHub <[email protected]>2020-02-03 11:27:31 +0000
commitd400fde66c7139aad04e5c5c9875a1d41ef5eae8 (patch)
treef86b6711bc6fb006d99b0be09bf17f0a9a79b3ec
parent5b1b2cac39d88e730d594f951ab6613eab5ee498 (diff)
parent9f70f443a35bc12caf37e2548abf7987926c3875 (diff)
Merge #2959
2959: Rework how we send diagnostics to client r=matklad a=kiljacken The previous way of sending from the thread pool suffered from stale diagnostics due to being canceled before we could clear the old ones. The key change is moving to sending diagnostics from the main loop thread, but doing all the hard work in the thread pool. This should provide the best of both worlds, with little to no of the downsides. This should hopefully fix a lot of issues, but we'll need testing in each individual issue to be sure. Co-authored-by: Emil Lauridsen <[email protected]>
-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.snap48
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap48
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap2
-rw-r--r--crates/ra_cargo_watch/src/lib.rs110
-rw-r--r--crates/ra_lsp_server/src/diagnostics.rs85
-rw-r--r--crates/ra_lsp_server/src/lib.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs76
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs36
-rw-r--r--crates/ra_lsp_server/src/world.rs11
14 files changed, 244 insertions, 247 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/conv/snapshots/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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/ra_cargo_watch__conv__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 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}
diff --git a/crates/ra_lsp_server/src/diagnostics.rs b/crates/ra_lsp_server/src/diagnostics.rs
new file mode 100644
index 000000000..ea08bce24
--- /dev/null
+++ b/crates/ra_lsp_server/src/diagnostics.rs
@@ -0,0 +1,85 @@
1//! Book keeping for keeping diagnostics easily in sync with the client.
2use lsp_types::{CodeActionOrCommand, Diagnostic, Range};
3use ra_ide::FileId;
4use std::{collections::HashMap, sync::Arc};
5
6pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
7
8#[derive(Debug, Default, Clone)]
9pub struct DiagnosticCollection {
10 pub native: HashMap<FileId, Vec<Diagnostic>>,
11 pub check: HashMap<FileId, Vec<Diagnostic>>,
12 pub check_fixes: CheckFixes,
13}
14
15#[derive(Debug, Clone)]
16pub struct Fix {
17 pub range: Range,
18 pub action: CodeActionOrCommand,
19}
20
21#[derive(Debug)]
22pub enum DiagnosticTask {
23 ClearCheck,
24 AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>),
25 SetNative(FileId, Vec<Diagnostic>),
26}
27
28impl DiagnosticCollection {
29 pub fn clear_check(&mut self) -> Vec<FileId> {
30 Arc::make_mut(&mut self.check_fixes).clear();
31 self.check.drain().map(|(key, _value)| key).collect()
32 }
33
34 pub fn add_check_diagnostic(
35 &mut self,
36 file_id: FileId,
37 diagnostic: Diagnostic,
38 fixes: Vec<CodeActionOrCommand>,
39 ) {
40 let diagnostics = self.check.entry(file_id).or_default();
41 for existing_diagnostic in diagnostics.iter() {
42 if are_diagnostics_equal(&existing_diagnostic, &diagnostic) {
43 return;
44 }
45 }
46
47 let check_fixes = Arc::make_mut(&mut self.check_fixes);
48 check_fixes
49 .entry(file_id)
50 .or_default()
51 .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
52 diagnostics.push(diagnostic);
53 }
54
55 pub fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) {
56 self.native.insert(file_id, diagnostics);
57 }
58
59 pub fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> {
60 let native = self.native.get(&file_id).into_iter().flatten();
61 let check = self.check.get(&file_id).into_iter().flatten();
62 native.chain(check)
63 }
64
65 pub fn handle_task(&mut self, task: DiagnosticTask) -> Vec<FileId> {
66 match task {
67 DiagnosticTask::ClearCheck => self.clear_check(),
68 DiagnosticTask::AddCheck(file_id, diagnostic, fixes) => {
69 self.add_check_diagnostic(file_id, diagnostic, fixes);
70 vec![file_id]
71 }
72 DiagnosticTask::SetNative(file_id, diagnostics) => {
73 self.set_native_diagnostics(file_id, diagnostics);
74 vec![file_id]
75 }
76 }
77 }
78}
79
80fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
81 left.source == right.source
82 && left.severity == right.severity
83 && left.range == right.range
84 && left.message == right.message
85}
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs
index 2ca149fd5..1208c1343 100644
--- a/crates/ra_lsp_server/src/lib.rs
+++ b/crates/ra_lsp_server/src/lib.rs
@@ -29,6 +29,7 @@ mod markdown;
29pub mod req; 29pub mod req;
30mod config; 30mod config;
31mod world; 31mod world;
32mod diagnostics;
32 33
33pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; 34pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
34pub use crate::{ 35pub use crate::{
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 508fe08c0..12961ba37 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -17,16 +17,17 @@ use std::{
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ClientCapabilities, NumberOrString}; 19use lsp_types::{ClientCapabilities, NumberOrString};
20use ra_cargo_watch::{CheckOptions, CheckTask}; 20use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask};
21use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; 21use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId};
22use ra_prof::profile; 22use ra_prof::profile;
23use ra_vfs::{VfsTask, Watch}; 23use ra_vfs::{VfsFile, VfsTask, Watch};
24use relative_path::RelativePathBuf; 24use relative_path::RelativePathBuf;
25use rustc_hash::FxHashSet; 25use rustc_hash::FxHashSet;
26use serde::{de::DeserializeOwned, Serialize}; 26use serde::{de::DeserializeOwned, Serialize};
27use threadpool::ThreadPool; 27use threadpool::ThreadPool;
28 28
29use crate::{ 29use crate::{
30 diagnostics::DiagnosticTask,
30 main_loop::{ 31 main_loop::{
31 pending_requests::{PendingRequest, PendingRequests}, 32 pending_requests::{PendingRequest, PendingRequests},
32 subscriptions::Subscriptions, 33 subscriptions::Subscriptions,
@@ -254,6 +255,7 @@ pub fn main_loop(
254enum Task { 255enum Task {
255 Respond(Response), 256 Respond(Response),
256 Notify(Notification), 257 Notify(Notification),
258 Diagnostic(DiagnosticTask),
257} 259}
258 260
259enum Event { 261enum Event {
@@ -359,7 +361,7 @@ fn loop_turn(
359 world_state.maybe_collect_garbage(); 361 world_state.maybe_collect_garbage();
360 loop_state.in_flight_libraries -= 1; 362 loop_state.in_flight_libraries -= 1;
361 } 363 }
362 Event::CheckWatcher(task) => on_check_task(pool, task, world_state, task_sender)?, 364 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?,
363 Event::Msg(msg) => match msg { 365 Event::Msg(msg) => match msg {
364 Message::Request(req) => on_request( 366 Message::Request(req) => on_request(
365 world_state, 367 world_state,
@@ -464,6 +466,7 @@ fn on_task(
464 Task::Notify(n) => { 466 Task::Notify(n) => {
465 msg_sender.send(n.into()).unwrap(); 467 msg_sender.send(n.into()).unwrap();
466 } 468 }
469 Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state),
467 } 470 }
468} 471}
469 472
@@ -621,23 +624,26 @@ fn on_notification(
621} 624}
622 625
623fn on_check_task( 626fn on_check_task(
624 pool: &ThreadPool,
625 task: CheckTask, 627 task: CheckTask,
626 world_state: &mut WorldState, 628 world_state: &mut WorldState,
627 task_sender: &Sender<Task>, 629 task_sender: &Sender<Task>,
628) -> Result<()> { 630) -> Result<()> {
629 let urls = match task { 631 match task {
630 CheckTask::ClearDiagnostics => { 632 CheckTask::ClearDiagnostics => {
631 let state = Arc::get_mut(&mut world_state.check_watcher.state) 633 task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?;
632 .expect("couldn't get check watcher state as mutable");
633 state.clear()
634 } 634 }
635 635
636 CheckTask::AddDiagnostic(url, diagnostic) => { 636 CheckTask::AddDiagnostic { url, diagnostic, fixes } => {
637 let state = Arc::get_mut(&mut world_state.check_watcher.state) 637 let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
638 .expect("couldn't get check watcher state as mutable"); 638 let file_id = world_state
639 state.add_diagnostic_with_fixes(url.clone(), diagnostic); 639 .vfs
640 vec![url] 640 .read()
641 .path2file(&path)
642 .map(|it| FileId(it.0))
643 .ok_or_else(|| format!("unknown file: {}", path.to_string_lossy()))?;
644
645 task_sender
646 .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?;
641 } 647 }
642 648
643 CheckTask::Status(progress) => { 649 CheckTask::Status(progress) => {
@@ -647,31 +653,30 @@ fn on_check_task(
647 }; 653 };
648 let not = notification_new::<req::Progress>(params); 654 let not = notification_new::<req::Progress>(params);
649 task_sender.send(Task::Notify(not)).unwrap(); 655 task_sender.send(Task::Notify(not)).unwrap();
650 Vec::new()
651 } 656 }
652 }; 657 };
653 658
654 let subscriptions = urls 659 Ok(())
655 .into_iter() 660}
656 .map(|url| {
657 let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
658 Ok(world_state.vfs.read().path2file(&path).map(|it| FileId(it.0)))
659 })
660 .filter_map(|res| res.transpose())
661 .collect::<Result<Vec<_>>>()?;
662 661
663 // We manually send a diagnostic update when the watcher asks 662fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut WorldState) {
664 // us to, to avoid the issue of having to change the file to 663 let subscriptions = state.diagnostics.handle_task(task);
665 // receive updated diagnostics.
666 update_file_notifications_on_threadpool(
667 pool,
668 world_state.snapshot(),
669 false,
670 task_sender.clone(),
671 subscriptions,
672 );
673 664
674 Ok(()) 665 for file_id in subscriptions {
666 let path = state.vfs.read().file2path(VfsFile(file_id.0));
667 let uri = match url_from_path_with_drive_lowercasing(&path) {
668 Ok(uri) => uri,
669 Err(err) => {
670 log::error!("Couldn't convert path to url ({}): {:?}", err, path.to_string_lossy());
671 continue;
672 }
673 };
674
675 let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect();
676 let params = req::PublishDiagnosticsParams { uri, diagnostics, version: None };
677 let not = notification_new::<req::PublishDiagnostics>(params);
678 msg_sender.send(not.into()).unwrap();
679 }
675} 680}
676 681
677struct PoolDispatcher<'a> { 682struct PoolDispatcher<'a> {
@@ -819,9 +824,8 @@ fn update_file_notifications_on_threadpool(
819 log::error!("failed to compute diagnostics: {:?}", e); 824 log::error!("failed to compute diagnostics: {:?}", e);
820 } 825 }
821 } 826 }
822 Ok(params) => { 827 Ok(task) => {
823 let not = notification_new::<req::PublishDiagnostics>(params); 828 task_sender.send(Task::Diagnostic(task)).unwrap();
824 task_sender.send(Task::Notify(not)).unwrap();
825 } 829 }
826 } 830 }
827 } 831 }
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 9aa1e7eea..282f6e8fc 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -33,6 +33,7 @@ use crate::{
33 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, 33 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith,
34 TryConvWithToVec, 34 TryConvWithToVec,
35 }, 35 },
36 diagnostics::DiagnosticTask,
36 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, 37 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
37 world::WorldSnapshot, 38 world::WorldSnapshot,
38 LspError, Result, 39 LspError, Result,
@@ -676,28 +677,12 @@ pub fn handle_code_action(
676 res.push(action.into()); 677 res.push(action.into());
677 } 678 }
678 679
679 for fix in world.check_watcher.fixes_for(&params.text_document.uri).into_iter().flatten() { 680 for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
680 let fix_range = fix.location.range.conv_with(&line_index); 681 let fix_range = fix.range.conv_with(&line_index);
681 if fix_range.intersection(&range).is_none() { 682 if fix_range.intersection(&range).is_none() {
682 continue; 683 continue;
683 } 684 }
684 685 res.push(fix.action.clone());
685 let edit = {
686 let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())];
687 let mut edit_map = std::collections::HashMap::new();
688 edit_map.insert(fix.location.uri.clone(), edits);
689 WorkspaceEdit::new(edit_map)
690 };
691
692 let action = CodeAction {
693 title: fix.title.clone(),
694 kind: Some("quickfix".to_string()),
695 diagnostics: Some(fix.diagnostics.clone()),
696 edit: Some(edit),
697 command: None,
698 is_preferred: None,
699 };
700 res.push(action.into());
701 } 686 }
702 687
703 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { 688 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
@@ -875,14 +860,10 @@ pub fn handle_document_highlight(
875 )) 860 ))
876} 861}
877 862
878pub fn publish_diagnostics( 863pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
879 world: &WorldSnapshot,
880 file_id: FileId,
881) -> Result<req::PublishDiagnosticsParams> {
882 let _p = profile("publish_diagnostics"); 864 let _p = profile("publish_diagnostics");
883 let uri = world.file_id_to_uri(file_id)?;
884 let line_index = world.analysis().file_line_index(file_id)?; 865 let line_index = world.analysis().file_line_index(file_id)?;
885 let mut diagnostics: Vec<Diagnostic> = world 866 let diagnostics: Vec<Diagnostic> = world
886 .analysis() 867 .analysis()
887 .diagnostics(file_id)? 868 .diagnostics(file_id)?
888 .into_iter() 869 .into_iter()
@@ -896,10 +877,7 @@ pub fn publish_diagnostics(
896 tags: None, 877 tags: None,
897 }) 878 })
898 .collect(); 879 .collect();
899 if let Some(check_diags) = world.check_watcher.diagnostics_for(&uri) { 880 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
900 diagnostics.extend(check_diags.iter().cloned());
901 }
902 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
903} 881}
904 882
905pub fn publish_decorations( 883pub fn publish_decorations(
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 3059ef9ec..1ee02b47c 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -12,9 +12,7 @@ use crossbeam_channel::{unbounded, Receiver};
12use lsp_server::ErrorCode; 12use lsp_server::ErrorCode;
13use lsp_types::Url; 13use lsp_types::Url;
14use parking_lot::RwLock; 14use parking_lot::RwLock;
15use ra_cargo_watch::{ 15use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher};
16 url_from_path_with_drive_lowercasing, CheckOptions, CheckState, CheckWatcher,
17};
18use ra_ide::{ 16use ra_ide::{
19 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, 17 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
20 SourceRootId, 18 SourceRootId,
@@ -25,6 +23,7 @@ use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
25use relative_path::RelativePathBuf; 23use relative_path::RelativePathBuf;
26 24
27use crate::{ 25use crate::{
26 diagnostics::{CheckFixes, DiagnosticCollection},
28 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 27 main_loop::pending_requests::{CompletedRequest, LatestRequests},
29 LspError, Result, 28 LspError, Result,
30}; 29};
@@ -55,6 +54,7 @@ pub struct WorldState {
55 pub task_receiver: Receiver<VfsTask>, 54 pub task_receiver: Receiver<VfsTask>,
56 pub latest_requests: Arc<RwLock<LatestRequests>>, 55 pub latest_requests: Arc<RwLock<LatestRequests>>,
57 pub check_watcher: CheckWatcher, 56 pub check_watcher: CheckWatcher,
57 pub diagnostics: DiagnosticCollection,
58} 58}
59 59
60/// An immutable snapshot of the world's state at a point in time. 60/// An immutable snapshot of the world's state at a point in time.
@@ -63,7 +63,7 @@ pub struct WorldSnapshot {
63 pub workspaces: Arc<Vec<ProjectWorkspace>>, 63 pub workspaces: Arc<Vec<ProjectWorkspace>>,
64 pub analysis: Analysis, 64 pub analysis: Analysis,
65 pub latest_requests: Arc<RwLock<LatestRequests>>, 65 pub latest_requests: Arc<RwLock<LatestRequests>>,
66 pub check_watcher: CheckState, 66 pub check_fixes: CheckFixes,
67 vfs: Arc<RwLock<Vfs>>, 67 vfs: Arc<RwLock<Vfs>>,
68} 68}
69 69
@@ -159,6 +159,7 @@ impl WorldState {
159 task_receiver, 159 task_receiver,
160 latest_requests: Default::default(), 160 latest_requests: Default::default(),
161 check_watcher, 161 check_watcher,
162 diagnostics: Default::default(),
162 } 163 }
163 } 164 }
164 165
@@ -220,7 +221,7 @@ impl WorldState {
220 analysis: self.analysis_host.analysis(), 221 analysis: self.analysis_host.analysis(),
221 vfs: Arc::clone(&self.vfs), 222 vfs: Arc::clone(&self.vfs),
222 latest_requests: Arc::clone(&self.latest_requests), 223 latest_requests: Arc::clone(&self.latest_requests),
223 check_watcher: (*self.check_watcher.state).clone(), 224 check_fixes: Arc::clone(&self.diagnostics.check_fixes),
224 } 225 }
225 } 226 }
226 227