aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-25 21:46:08 +0100
committerGitHub <[email protected]>2020-06-25 21:46:08 +0100
commit3615347fcebebaa58a5cbf675ae062aae149d9c3 (patch)
tree77c2232aa7286e2880a28678e343286b853bfebc
parentb5aa92bab94880a289372c66c99117ae5e6e9e1f (diff)
parentdf769e5bb4e26a2f150ce2c37b9b9364ee10bab8 (diff)
Merge #5065
5065: Simplify diagnostics handling r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs54
-rw-r--r--crates/rust-analyzer/src/dispatch.rs44
-rw-r--r--crates/rust-analyzer/src/global_state.rs14
-rw-r--r--crates/rust-analyzer/src/handlers.rs5
-rw-r--r--crates/rust-analyzer/src/main_loop.rs389
5 files changed, 239 insertions, 267 deletions
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 290609e7f..f3cdb842b 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -1,14 +1,15 @@
1//! Book keeping for keeping diagnostics easily in sync with the client. 1//! Book keeping for keeping diagnostics easily in sync with the client.
2pub(crate) mod to_proto; 2pub(crate) mod to_proto;
3 3
4use std::{collections::HashMap, sync::Arc}; 4use std::{collections::HashMap, mem, sync::Arc};
5 5
6use lsp_types::{Diagnostic, Range}; 6use lsp_types::{Diagnostic, Range};
7use ra_ide::FileId; 7use ra_ide::FileId;
8use rustc_hash::FxHashSet;
8 9
9use crate::lsp_ext; 10use crate::lsp_ext;
10 11
11pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; 12pub(crate) type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
12 13
13#[derive(Debug, Default, Clone)] 14#[derive(Debug, Default, Clone)]
14pub struct DiagnosticsConfig { 15pub struct DiagnosticsConfig {
@@ -17,32 +18,26 @@ pub struct DiagnosticsConfig {
17} 18}
18 19
19#[derive(Debug, Default, Clone)] 20#[derive(Debug, Default, Clone)]
20pub struct DiagnosticCollection { 21pub(crate) struct DiagnosticCollection {
21 pub native: HashMap<FileId, Vec<Diagnostic>>, 22 pub(crate) native: HashMap<FileId, Vec<Diagnostic>>,
22 pub check: HashMap<FileId, Vec<Diagnostic>>, 23 pub(crate) check: HashMap<FileId, Vec<Diagnostic>>,
23 pub check_fixes: CheckFixes, 24 pub(crate) check_fixes: CheckFixes,
25 changes: FxHashSet<FileId>,
24} 26}
25 27
26#[derive(Debug, Clone)] 28#[derive(Debug, Clone)]
27pub struct Fix { 29pub(crate) struct Fix {
28 pub range: Range, 30 pub(crate) range: Range,
29 pub action: lsp_ext::CodeAction, 31 pub(crate) action: lsp_ext::CodeAction,
30}
31
32#[derive(Debug)]
33pub enum DiagnosticTask {
34 ClearCheck,
35 AddCheck(FileId, Diagnostic, Vec<lsp_ext::CodeAction>),
36 SetNative(FileId, Vec<Diagnostic>),
37} 32}
38 33
39impl DiagnosticCollection { 34impl DiagnosticCollection {
40 pub fn clear_check(&mut self) -> Vec<FileId> { 35 pub(crate) fn clear_check(&mut self) {
41 Arc::make_mut(&mut self.check_fixes).clear(); 36 Arc::make_mut(&mut self.check_fixes).clear();
42 self.check.drain().map(|(key, _value)| key).collect() 37 self.changes.extend(self.check.drain().map(|(key, _value)| key))
43 } 38 }
44 39
45 pub fn add_check_diagnostic( 40 pub(crate) fn add_check_diagnostic(
46 &mut self, 41 &mut self,
47 file_id: FileId, 42 file_id: FileId,
48 diagnostic: Diagnostic, 43 diagnostic: Diagnostic,
@@ -61,30 +56,25 @@ impl DiagnosticCollection {
61 .or_default() 56 .or_default()
62 .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action })); 57 .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
63 diagnostics.push(diagnostic); 58 diagnostics.push(diagnostic);
59 self.changes.insert(file_id);
64 } 60 }
65 61
66 pub fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) { 62 pub(crate) fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) {
67 self.native.insert(file_id, diagnostics); 63 self.native.insert(file_id, diagnostics);
64 self.changes.insert(file_id);
68 } 65 }
69 66
70 pub fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> { 67 pub(crate) fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> {
71 let native = self.native.get(&file_id).into_iter().flatten(); 68 let native = self.native.get(&file_id).into_iter().flatten();
72 let check = self.check.get(&file_id).into_iter().flatten(); 69 let check = self.check.get(&file_id).into_iter().flatten();
73 native.chain(check) 70 native.chain(check)
74 } 71 }
75 72
76 pub fn handle_task(&mut self, task: DiagnosticTask) -> Vec<FileId> { 73 pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> {
77 match task { 74 if self.changes.is_empty() {
78 DiagnosticTask::ClearCheck => self.clear_check(), 75 return None;
79 DiagnosticTask::AddCheck(file_id, diagnostic, fixes) => {
80 self.add_check_diagnostic(file_id, diagnostic, fixes);
81 vec![file_id]
82 }
83 DiagnosticTask::SetNative(file_id, diagnostics) => {
84 self.set_native_diagnostics(file_id, diagnostics);
85 vec![file_id]
86 }
87 } 76 }
77 Some(mem::take(&mut self.changes))
88 } 78 }
89} 79}
90 80
diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs
index 5fdbed8ef..03b373dee 100644
--- a/crates/rust-analyzer/src/dispatch.rs
+++ b/crates/rust-analyzer/src/dispatch.rs
@@ -1,5 +1,5 @@
1//! A visitor for downcasting arbitrary request (JSON) into a specific type. 1//! A visitor for downcasting arbitrary request (JSON) into a specific type.
2use std::{panic, time::Instant}; 2use std::panic;
3 3
4use serde::{de::DeserializeOwned, Serialize}; 4use serde::{de::DeserializeOwned, Serialize};
5 5
@@ -13,7 +13,6 @@ use crate::{
13pub(crate) struct RequestDispatcher<'a> { 13pub(crate) struct RequestDispatcher<'a> {
14 pub(crate) req: Option<lsp_server::Request>, 14 pub(crate) req: Option<lsp_server::Request>,
15 pub(crate) global_state: &'a mut GlobalState, 15 pub(crate) global_state: &'a mut GlobalState,
16 pub(crate) request_received: Instant,
17} 16}
18 17
19impl<'a> RequestDispatcher<'a> { 18impl<'a> RequestDispatcher<'a> {
@@ -34,12 +33,12 @@ impl<'a> RequestDispatcher<'a> {
34 } 33 }
35 }; 34 };
36 let world = panic::AssertUnwindSafe(&mut *self.global_state); 35 let world = panic::AssertUnwindSafe(&mut *self.global_state);
37 let task = panic::catch_unwind(move || { 36 let response = panic::catch_unwind(move || {
38 let result = f(world.0, params); 37 let result = f(world.0, params);
39 result_to_task::<R>(id, result) 38 result_to_response::<R>(id, result)
40 }) 39 })
41 .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; 40 .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?;
42 self.global_state.on_task(task); 41 self.global_state.respond(response);
43 Ok(self) 42 Ok(self)
44 } 43 }
45 44
@@ -64,7 +63,7 @@ impl<'a> RequestDispatcher<'a> {
64 let world = self.global_state.snapshot(); 63 let world = self.global_state.snapshot();
65 move || { 64 move || {
66 let result = f(world, params); 65 let result = f(world, params);
67 result_to_task::<R>(id, result) 66 Task::Response(result_to_response::<R>(id, result))
68 } 67 }
69 }); 68 });
70 69
@@ -72,17 +71,14 @@ impl<'a> RequestDispatcher<'a> {
72 } 71 }
73 72
74 pub(crate) fn finish(&mut self) { 73 pub(crate) fn finish(&mut self) {
75 match self.req.take() { 74 if let Some(req) = self.req.take() {
76 None => (), 75 log::error!("unknown request: {:?}", req);
77 Some(req) => { 76 let response = lsp_server::Response::new_err(
78 log::error!("unknown request: {:?}", req); 77 req.id,
79 let resp = lsp_server::Response::new_err( 78 lsp_server::ErrorCode::MethodNotFound as i32,
80 req.id, 79 "unknown request".to_string(),
81 lsp_server::ErrorCode::MethodNotFound as i32, 80 );
82 "unknown request".to_string(), 81 self.global_state.respond(response)
83 );
84 self.global_state.send(resp.into());
85 }
86 } 82 }
87 } 83 }
88 84
@@ -99,21 +95,20 @@ impl<'a> RequestDispatcher<'a> {
99 return None; 95 return None;
100 } 96 }
101 }; 97 };
102 self.global_state
103 .req_queue
104 .incoming
105 .register(id.clone(), (R::METHOD, self.request_received));
106 Some((id, params)) 98 Some((id, params))
107 } 99 }
108} 100}
109 101
110fn result_to_task<R>(id: lsp_server::RequestId, result: Result<R::Result>) -> Task 102fn result_to_response<R>(
103 id: lsp_server::RequestId,
104 result: Result<R::Result>,
105) -> lsp_server::Response
111where 106where
112 R: lsp_types::request::Request + 'static, 107 R: lsp_types::request::Request + 'static,
113 R::Params: DeserializeOwned + 'static, 108 R::Params: DeserializeOwned + 'static,
114 R::Result: Serialize + 'static, 109 R::Result: Serialize + 'static,
115{ 110{
116 let response = match result { 111 match result {
117 Ok(resp) => lsp_server::Response::new_ok(id, &resp), 112 Ok(resp) => lsp_server::Response::new_ok(id, &resp),
118 Err(e) => match e.downcast::<LspError>() { 113 Err(e) => match e.downcast::<LspError>() {
119 Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message), 114 Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message),
@@ -133,8 +128,7 @@ where
133 } 128 }
134 } 129 }
135 }, 130 },
136 }; 131 }
137 Task::Respond(response)
138} 132}
139 133
140pub(crate) struct NotificationDispatcher<'a> { 134pub(crate) struct NotificationDispatcher<'a> {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 56d50c789..149b1b5f9 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -253,13 +253,19 @@ impl GlobalState {
253 self.analysis_host.collect_garbage() 253 self.analysis_host.collect_garbage()
254 } 254 }
255 255
256 pub(crate) fn complete_request(&mut self, request: RequestMetrics) {
257 self.latest_requests.write().record(request)
258 }
259
260 pub(crate) fn send(&mut self, message: lsp_server::Message) { 256 pub(crate) fn send(&mut self, message: lsp_server::Message) {
261 self.sender.send(message).unwrap() 257 self.sender.send(message).unwrap()
262 } 258 }
259 pub(crate) fn respond(&mut self, response: lsp_server::Response) {
260 if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) {
261 let duration = start.elapsed();
262 log::info!("handled req#{} in {:?}", response.id, duration);
263 let metrics =
264 RequestMetrics { id: response.id.clone(), method: method.to_string(), duration };
265 self.latest_requests.write().record(metrics);
266 self.send(response.into());
267 }
268 }
263 pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) { 269 pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
264 show_message(typ, message, &self.sender) 270 show_message(typ, message, &self.sender)
265 } 271 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index b2ff9a157..12b494496 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -31,7 +31,6 @@ use stdx::{format_to, split_delim};
31use crate::{ 31use crate::{
32 cargo_target_spec::CargoTargetSpec, 32 cargo_target_spec::CargoTargetSpec,
33 config::RustfmtConfig, 33 config::RustfmtConfig,
34 diagnostics::DiagnosticTask,
35 from_json, from_proto, 34 from_json, from_proto,
36 global_state::GlobalStateSnapshot, 35 global_state::GlobalStateSnapshot,
37 lsp_ext::{self, InlayHint, InlayHintsParams}, 36 lsp_ext::{self, InlayHint, InlayHintsParams},
@@ -950,7 +949,7 @@ pub(crate) fn handle_ssr(
950pub(crate) fn publish_diagnostics( 949pub(crate) fn publish_diagnostics(
951 snap: &GlobalStateSnapshot, 950 snap: &GlobalStateSnapshot,
952 file_id: FileId, 951 file_id: FileId,
953) -> Result<DiagnosticTask> { 952) -> Result<Vec<Diagnostic>> {
954 let _p = profile("publish_diagnostics"); 953 let _p = profile("publish_diagnostics");
955 let line_index = snap.analysis.file_line_index(file_id)?; 954 let line_index = snap.analysis.file_line_index(file_id)?;
956 let diagnostics: Vec<Diagnostic> = snap 955 let diagnostics: Vec<Diagnostic> = snap
@@ -967,7 +966,7 @@ pub(crate) fn publish_diagnostics(
967 tags: None, 966 tags: None,
968 }) 967 })
969 .collect(); 968 .collect();
970 Ok(DiagnosticTask::SetNative(file_id, diagnostics)) 969 Ok(diagnostics)
971} 970}
972 971
973pub(crate) fn handle_inlay_hints( 972pub(crate) fn handle_inlay_hints(
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c2f43df1d..1bd9d6389 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -15,7 +15,6 @@ use ra_project_model::{PackageRoot, ProjectWorkspace};
15 15
16use crate::{ 16use crate::{
17 config::{Config, FilesWatcher, LinkedProject}, 17 config::{Config, FilesWatcher, LinkedProject},
18 diagnostics::DiagnosticTask,
19 dispatch::{NotificationDispatcher, RequestDispatcher}, 18 dispatch::{NotificationDispatcher, RequestDispatcher},
20 from_proto, 19 from_proto,
21 global_state::{file_id_to_url, GlobalState, Status}, 20 global_state::{file_id_to_url, GlobalState, Status},
@@ -23,7 +22,6 @@ use crate::{
23 lsp_utils::{ 22 lsp_utils::{
24 apply_document_changes, is_canceled, notification_is, notification_new, show_message, 23 apply_document_changes, is_canceled, notification_is, notification_new, show_message,
25 }, 24 },
26 request_metrics::RequestMetrics,
27 Result, 25 Result,
28}; 26};
29 27
@@ -126,6 +124,52 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
126 Ok(()) 124 Ok(())
127} 125}
128 126
127enum Event {
128 Lsp(lsp_server::Message),
129 Task(Task),
130 Vfs(vfs::loader::Message),
131 Flycheck(flycheck::Message),
132}
133
134#[derive(Debug)]
135pub(crate) enum Task {
136 Response(Response),
137 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
138 Unit,
139}
140
141impl fmt::Debug for Event {
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
144 f.debug_struct("Notification").field("method", &not.method).finish()
145 };
146
147 match self {
148 Event::Lsp(lsp_server::Message::Notification(not)) => {
149 if notification_is::<lsp_types::notification::DidOpenTextDocument>(not)
150 || notification_is::<lsp_types::notification::DidChangeTextDocument>(not)
151 {
152 return debug_verbose_not(not, f);
153 }
154 }
155 Event::Task(Task::Response(resp)) => {
156 return f
157 .debug_struct("Response")
158 .field("id", &resp.id)
159 .field("error", &resp.error)
160 .finish();
161 }
162 _ => (),
163 }
164 match self {
165 Event::Lsp(it) => fmt::Debug::fmt(it, f),
166 Event::Task(it) => fmt::Debug::fmt(it, f),
167 Event::Vfs(it) => fmt::Debug::fmt(it, f),
168 Event::Flycheck(it) => fmt::Debug::fmt(it, f),
169 }
170 }
171}
172
129impl GlobalState { 173impl GlobalState {
130 fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { 174 fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> {
131 select! { 175 select! {
@@ -145,101 +189,165 @@ impl GlobalState {
145 189
146 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { 190 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
147 while let Some(event) = self.next_event(&inbox) { 191 while let Some(event) = self.next_event(&inbox) {
148 let loop_start = Instant::now(); 192 if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
149 // NOTE: don't count blocking select! call as a loop-turn time 193 if not.method == lsp_types::notification::Exit::METHOD {
150 let _p = profile("main_loop_inner/loop-turn"); 194 return Ok(());
151 195 }
152 log::info!("loop turn = {:?}", event);
153 let queue_count = self.task_pool.0.len();
154 if queue_count > 0 {
155 log::info!("queued count = {}", queue_count);
156 } 196 }
197 self.loop_turn(event)?
198 }
199 Err("client exited without proper shutdown sequence")?
200 }
157 201
158 let mut became_ready = false; 202 fn loop_turn(&mut self, event: Event) -> Result<()> {
159 match event { 203 let loop_start = Instant::now();
160 Event::Lsp(msg) => match msg { 204 // NOTE: don't count blocking select! call as a loop-turn time
161 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, 205 let _p = profile("main_loop_inner/loop-turn");
162 lsp_server::Message::Notification(not) => { 206
163 if not.method == lsp_types::notification::Exit::METHOD { 207 log::info!("loop turn = {:?}", event);
164 return Ok(()); 208 let queue_count = self.task_pool.0.len();
209 if queue_count > 0 {
210 log::info!("queued count = {}", queue_count);
211 }
212
213 let mut became_ready = false;
214 match event {
215 Event::Lsp(msg) => match msg {
216 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
217 lsp_server::Message::Notification(not) => {
218 self.on_notification(not)?;
219 }
220 lsp_server::Message::Response(resp) => {
221 let handler = self.req_queue.outgoing.complete(resp.id.clone());
222 handler(self, resp)
223 }
224 },
225 Event::Task(task) => {
226 match task {
227 Task::Response(response) => self.respond(response),
228 Task::Diagnostics(diagnostics_per_file) => {
229 for (file_id, diagnostics) in diagnostics_per_file {
230 self.diagnostics.set_native_diagnostics(file_id, diagnostics)
165 } 231 }
166 self.on_notification(not)?;
167 } 232 }
168 lsp_server::Message::Response(resp) => { 233 Task::Unit => (),
169 let handler = self.req_queue.outgoing.complete(resp.id.clone());
170 handler(&mut self, resp)
171 }
172 },
173 Event::Task(task) => {
174 self.on_task(task);
175 self.maybe_collect_garbage();
176 } 234 }
177 Event::Vfs(task) => match task { 235 self.maybe_collect_garbage();
178 vfs::loader::Message::Loaded { files } => { 236 }
179 let vfs = &mut self.vfs.write().0; 237 Event::Vfs(task) => match task {
180 for (path, contents) in files { 238 vfs::loader::Message::Loaded { files } => {
181 let path = VfsPath::from(path); 239 let vfs = &mut self.vfs.write().0;
182 if !self.mem_docs.contains(&path) { 240 for (path, contents) in files {
183 vfs.set_file_contents(path, contents) 241 let path = VfsPath::from(path);
184 } 242 if !self.mem_docs.contains(&path) {
243 vfs.set_file_contents(path, contents)
185 } 244 }
186 } 245 }
187 vfs::loader::Message::Progress { n_total, n_done } => { 246 }
188 let state = if n_done == 0 { 247 vfs::loader::Message::Progress { n_total, n_done } => {
189 Progress::Begin 248 let state = if n_done == 0 {
190 } else if n_done < n_total { 249 Progress::Begin
191 Progress::Report 250 } else if n_done < n_total {
192 } else { 251 Progress::Report
193 assert_eq!(n_done, n_total); 252 } else {
194 self.status = Status::Ready; 253 assert_eq!(n_done, n_total);
195 became_ready = true; 254 self.status = Status::Ready;
196 Progress::End 255 became_ready = true;
256 Progress::End
257 };
258 report_progress(
259 self,
260 "roots scanned",
261 state,
262 Some(format!("{}/{}", n_done, n_total)),
263 Some(percentage(n_done, n_total)),
264 )
265 }
266 },
267 Event::Flycheck(task) => match task {
268 flycheck::Message::ClearDiagnostics => self.diagnostics.clear_check(),
269
270 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
271 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
272 &self.config.diagnostics,
273 &diagnostic,
274 &workspace_root,
275 );
276 for diag in diagnostics {
277 let path = from_proto::vfs_path(&diag.location.uri)?;
278 let file_id = match self.vfs.read().0.file_id(&path) {
279 Some(file) => FileId(file.0),
280 None => {
281 log::error!(
282 "File with cargo diagnostic not found in VFS: {}",
283 path
284 );
285 return Ok(());
286 }
197 }; 287 };
198 report_progress( 288 self.diagnostics.add_check_diagnostic(file_id, diag.diagnostic, diag.fixes)
199 &mut self,
200 "roots scanned",
201 state,
202 Some(format!("{}/{}", n_done, n_total)),
203 Some(percentage(n_done, n_total)),
204 )
205 } 289 }
206 }, 290 }
207 Event::Flycheck(task) => on_check_task(task, &mut self)?,
208 }
209 291
210 let state_changed = self.process_changes(); 292 flycheck::Message::Progress(status) => {
211 if became_ready { 293 let (state, message) = match status {
212 if let Some(flycheck) = &self.flycheck { 294 flycheck::Progress::Being => (Progress::Begin, None),
213 flycheck.0.update(); 295 flycheck::Progress::DidCheckCrate(target) => {
296 (Progress::Report, Some(target))
297 }
298 flycheck::Progress::End => (Progress::End, None),
299 };
300
301 report_progress(self, "cargo check", state, message, None);
214 } 302 }
303 },
304 }
305
306 let state_changed = self.process_changes();
307 if became_ready {
308 if let Some(flycheck) = &self.flycheck {
309 flycheck.0.update();
215 } 310 }
311 }
216 312
217 if self.status == Status::Ready && (state_changed || became_ready) { 313 if self.status == Status::Ready && (state_changed || became_ready) {
218 let subscriptions = self 314 let subscriptions = self
219 .mem_docs 315 .mem_docs
220 .iter() 316 .iter()
221 .map(|path| self.vfs.read().0.file_id(&path).unwrap()) 317 .map(|path| self.vfs.read().0.file_id(&path).unwrap())
222 .collect::<Vec<_>>(); 318 .collect::<Vec<_>>();
319
320 self.update_file_notifications_on_threadpool(subscriptions);
321 }
223 322
224 self.update_file_notifications_on_threadpool(subscriptions); 323 if let Some(diagnostic_changes) = self.diagnostics.take_changes() {
324 for file_id in diagnostic_changes {
325 let url = file_id_to_url(&self.vfs.read().0, file_id);
326 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
327 let params =
328 lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None };
329 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
330 self.send(not.into());
225 } 331 }
332 }
226 333
227 let loop_duration = loop_start.elapsed(); 334 let loop_duration = loop_start.elapsed();
228 if loop_duration > Duration::from_millis(100) { 335 if loop_duration > Duration::from_millis(100) {
229 log::error!("overly long loop turn: {:?}", loop_duration); 336 log::warn!("overly long loop turn: {:?}", loop_duration);
230 if env::var("RA_PROFILE").is_ok() { 337 if env::var("RA_PROFILE").is_ok() {
231 self.show_message( 338 self.show_message(
232 lsp_types::MessageType::Error, 339 lsp_types::MessageType::Error,
233 format!("overly long loop turn: {:?}", loop_duration), 340 format!("overly long loop turn: {:?}", loop_duration),
234 ) 341 )
235 }
236 } 342 }
237 } 343 }
238 Err("client exited without proper shutdown sequence")? 344 Ok(())
239 } 345 }
240 346
241 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { 347 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
242 RequestDispatcher { req: Some(req), global_state: self, request_received } 348 self.req_queue.incoming.register(req.id.clone(), (req.method.clone(), request_received));
349
350 RequestDispatcher { req: Some(req), global_state: self }
243 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.collect_garbage()))? 351 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.collect_garbage()))?
244 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? 352 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
245 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? 353 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
@@ -400,27 +508,6 @@ impl GlobalState {
400 .finish(); 508 .finish();
401 Ok(()) 509 Ok(())
402 } 510 }
403 pub(crate) fn on_task(&mut self, task: Task) {
404 match task {
405 Task::Respond(response) => {
406 if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone())
407 {
408 let duration = start.elapsed();
409 log::info!("handled req#{} in {:?}", response.id, duration);
410 self.complete_request(RequestMetrics {
411 id: response.id.clone(),
412 method: method.to_string(),
413 duration,
414 });
415 self.send(response.into());
416 }
417 }
418 Task::Diagnostics(tasks) => {
419 tasks.into_iter().for_each(|task| on_diagnostic_task(task, self))
420 }
421 Task::Unit => (),
422 }
423 }
424 fn update_file_notifications_on_threadpool(&mut self, subscriptions: Vec<FileId>) { 511 fn update_file_notifications_on_threadpool(&mut self, subscriptions: Vec<FileId>) {
425 log::trace!("updating notifications for {:?}", subscriptions); 512 log::trace!("updating notifications for {:?}", subscriptions);
426 if self.config.publish_diagnostics { 513 if self.config.publish_diagnostics {
@@ -438,6 +525,7 @@ impl GlobalState {
438 () 525 ()
439 }) 526 })
440 .ok() 527 .ok()
528 .map(|diags| (file_id, diags))
441 }) 529 })
442 .collect::<Vec<_>>(); 530 .collect::<Vec<_>>();
443 Task::Diagnostics(diagnostics) 531 Task::Diagnostics(diagnostics)
@@ -454,115 +542,10 @@ impl GlobalState {
454 } 542 }
455} 543}
456 544
457#[derive(Debug)]
458pub(crate) enum Task {
459 Respond(Response),
460 Diagnostics(Vec<DiagnosticTask>),
461 Unit,
462}
463
464enum Event {
465 Lsp(lsp_server::Message),
466 Task(Task),
467 Vfs(vfs::loader::Message),
468 Flycheck(flycheck::Message),
469}
470
471impl fmt::Debug for Event {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
474 f.debug_struct("Notification").field("method", &not.method).finish()
475 };
476
477 match self {
478 Event::Lsp(lsp_server::Message::Notification(not)) => {
479 if notification_is::<lsp_types::notification::DidOpenTextDocument>(not)
480 || notification_is::<lsp_types::notification::DidChangeTextDocument>(not)
481 {
482 return debug_verbose_not(not, f);
483 }
484 }
485 Event::Task(Task::Respond(resp)) => {
486 return f
487 .debug_struct("Response")
488 .field("id", &resp.id)
489 .field("error", &resp.error)
490 .finish();
491 }
492 _ => (),
493 }
494 match self {
495 Event::Lsp(it) => fmt::Debug::fmt(it, f),
496 Event::Task(it) => fmt::Debug::fmt(it, f),
497 Event::Vfs(it) => fmt::Debug::fmt(it, f),
498 Event::Flycheck(it) => fmt::Debug::fmt(it, f),
499 }
500 }
501}
502
503pub(crate) type ReqHandler = fn(&mut GlobalState, Response); 545pub(crate) type ReqHandler = fn(&mut GlobalState, Response);
504pub(crate) type ReqQueue = lsp_server::ReqQueue<(&'static str, Instant), ReqHandler>; 546pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
505const DO_NOTHING: ReqHandler = |_, _| (); 547const DO_NOTHING: ReqHandler = |_, _| ();
506 548
507fn on_check_task(task: flycheck::Message, global_state: &mut GlobalState) -> Result<()> {
508 match task {
509 flycheck::Message::ClearDiagnostics => {
510 on_diagnostic_task(DiagnosticTask::ClearCheck, global_state)
511 }
512
513 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
514 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
515 &global_state.config.diagnostics,
516 &diagnostic,
517 &workspace_root,
518 );
519 for diag in diagnostics {
520 let path = from_proto::vfs_path(&diag.location.uri)?;
521 let file_id = match global_state.vfs.read().0.file_id(&path) {
522 Some(file) => FileId(file.0),
523 None => {
524 log::error!("File with cargo diagnostic not found in VFS: {}", path);
525 return Ok(());
526 }
527 };
528
529 on_diagnostic_task(
530 DiagnosticTask::AddCheck(
531 file_id,
532 diag.diagnostic,
533 diag.fixes.into_iter().map(|it| it.into()).collect(),
534 ),
535 global_state,
536 )
537 }
538 }
539
540 flycheck::Message::Progress(status) => {
541 let (state, message) = match status {
542 flycheck::Progress::Being => (Progress::Begin, None),
543 flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
544 flycheck::Progress::End => (Progress::End, None),
545 };
546
547 report_progress(global_state, "cargo check", state, message, None);
548 }
549 };
550
551 Ok(())
552}
553
554fn on_diagnostic_task(task: DiagnosticTask, global_state: &mut GlobalState) {
555 let subscriptions = global_state.diagnostics.handle_task(task);
556
557 for file_id in subscriptions {
558 let url = file_id_to_url(&global_state.vfs.read().0, file_id);
559 let diagnostics = global_state.diagnostics.diagnostics_for(file_id).cloned().collect();
560 let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None };
561 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
562 global_state.send(not.into());
563 }
564}
565
566#[derive(Eq, PartialEq)] 549#[derive(Eq, PartialEq)]
567enum Progress { 550enum Progress {
568 Begin, 551 Begin,