aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-26 16:02:00 +0100
committerGitHub <[email protected]>2020-06-26 16:02:00 +0100
commitfa70882418088271e519c5820ae5cb17557a1f43 (patch)
tree338c3148cc7f0b1b27ebb383929cf2ba044022f5
parent4f60b4f2a32190830215defe6cf81836f34d99c9 (diff)
parent1893289e5c7cebeeb9705c031c996fc29d8c5b54 (diff)
Merge #5075
5075: Be more precise about flycheck status r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/flycheck/src/lib.rs92
-rw-r--r--crates/rust-analyzer/src/global_state.rs4
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs85
-rw-r--r--crates/rust-analyzer/src/main_loop.rs77
-rw-r--r--crates/rust-analyzer/src/reload.rs24
6 files changed, 127 insertions, 157 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 4dcab7a61..92ec4f92e 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -7,7 +7,7 @@ use std::{
7 io::{self, BufReader}, 7 io::{self, BufReader},
8 path::PathBuf, 8 path::PathBuf,
9 process::{Command, Stdio}, 9 process::{Command, Stdio},
10 time::Instant, 10 time::Duration,
11}; 11};
12 12
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 13use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
@@ -74,9 +74,6 @@ impl FlycheckHandle {
74 74
75#[derive(Debug)] 75#[derive(Debug)]
76pub enum Message { 76pub enum Message {
77 /// Request a clearing of all cached diagnostics from the check watcher
78 ClearDiagnostics,
79
80 /// Request adding a diagnostic with fixes included to a file 77 /// Request adding a diagnostic with fixes included to a file
81 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, 78 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
82 79
@@ -86,9 +83,10 @@ pub enum Message {
86 83
87#[derive(Debug)] 84#[derive(Debug)]
88pub enum Progress { 85pub enum Progress {
89 Being, 86 DidStart,
90 DidCheckCrate(String), 87 DidCheckCrate(String),
91 End, 88 DidFinish,
89 DidCancel,
92} 90}
93 91
94struct Restart; 92struct Restart;
@@ -97,19 +95,18 @@ struct FlycheckActor {
97 sender: Box<dyn Fn(Message) + Send>, 95 sender: Box<dyn Fn(Message) + Send>,
98 config: FlycheckConfig, 96 config: FlycheckConfig,
99 workspace_root: PathBuf, 97 workspace_root: PathBuf,
100 last_update_req: Option<Instant>,
101 /// WatchThread exists to wrap around the communication needed to be able to 98 /// WatchThread exists to wrap around the communication needed to be able to
102 /// run `cargo check` without blocking. Currently the Rust standard library 99 /// run `cargo check` without blocking. Currently the Rust standard library
103 /// doesn't provide a way to read sub-process output without blocking, so we 100 /// doesn't provide a way to read sub-process output without blocking, so we
104 /// have to wrap sub-processes output handling in a thread and pass messages 101 /// have to wrap sub-processes output handling in a thread and pass messages
105 /// back over a channel. 102 /// back over a channel.
106 // XXX: drop order is significant 103 // XXX: drop order is significant
107 check_process: Option<(Receiver<CheckEvent>, jod_thread::JoinHandle)>, 104 check_process: Option<(Receiver<cargo_metadata::Message>, jod_thread::JoinHandle)>,
108} 105}
109 106
110enum Event { 107enum Event {
111 Restart(Restart), 108 Restart(Restart),
112 CheckEvent(Option<CheckEvent>), 109 CheckEvent(Option<cargo_metadata::Message>),
113} 110}
114 111
115impl FlycheckActor { 112impl FlycheckActor {
@@ -118,7 +115,7 @@ impl FlycheckActor {
118 config: FlycheckConfig, 115 config: FlycheckConfig,
119 workspace_root: PathBuf, 116 workspace_root: PathBuf,
120 ) -> FlycheckActor { 117 ) -> FlycheckActor {
121 FlycheckActor { sender, config, workspace_root, last_update_req: None, check_process: None } 118 FlycheckActor { sender, config, workspace_root, check_process: None }
122 } 119 }
123 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { 120 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
124 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan); 121 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan);
@@ -128,65 +125,48 @@ impl FlycheckActor {
128 } 125 }
129 } 126 }
130 fn run(&mut self, inbox: Receiver<Restart>) { 127 fn run(&mut self, inbox: Receiver<Restart>) {
131 // If we rerun the thread, we need to discard the previous check results first
132 self.send(Message::ClearDiagnostics);
133 self.send(Message::Progress(Progress::End));
134
135 while let Some(event) = self.next_event(&inbox) { 128 while let Some(event) = self.next_event(&inbox) {
136 match event { 129 match event {
137 Event::Restart(Restart) => self.last_update_req = Some(Instant::now()), 130 Event::Restart(Restart) => {
131 while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
132 self.cancel_check_process();
133 self.check_process = Some(self.start_check_process());
134 self.send(Message::Progress(Progress::DidStart));
135 }
138 Event::CheckEvent(None) => { 136 Event::CheckEvent(None) => {
139 // Watcher finished, replace it with a never channel to 137 // Watcher finished, replace it with a never channel to
140 // avoid busy-waiting. 138 // avoid busy-waiting.
141 self.check_process = None; 139 assert!(self.check_process.take().is_some());
140 self.send(Message::Progress(Progress::DidFinish));
142 } 141 }
143 Event::CheckEvent(Some(event)) => match event { 142 Event::CheckEvent(Some(message)) => match message {
144 CheckEvent::Begin => { 143 cargo_metadata::Message::CompilerArtifact(msg) => {
145 self.send(Message::Progress(Progress::Being));
146 }
147
148 CheckEvent::End => {
149 self.send(Message::Progress(Progress::End));
150 }
151
152 CheckEvent::Msg(cargo_metadata::Message::CompilerArtifact(msg)) => {
153 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name))); 144 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name)));
154 } 145 }
155 146
156 CheckEvent::Msg(cargo_metadata::Message::CompilerMessage(msg)) => { 147 cargo_metadata::Message::CompilerMessage(msg) => {
157 self.send(Message::AddDiagnostic { 148 self.send(Message::AddDiagnostic {
158 workspace_root: self.workspace_root.clone(), 149 workspace_root: self.workspace_root.clone(),
159 diagnostic: msg.message, 150 diagnostic: msg.message,
160 }); 151 });
161 } 152 }
162 153
163 CheckEvent::Msg(cargo_metadata::Message::BuildScriptExecuted(_)) 154 cargo_metadata::Message::BuildScriptExecuted(_)
164 | CheckEvent::Msg(cargo_metadata::Message::BuildFinished(_)) 155 | cargo_metadata::Message::BuildFinished(_)
165 | CheckEvent::Msg(cargo_metadata::Message::TextLine(_)) 156 | cargo_metadata::Message::TextLine(_)
166 | CheckEvent::Msg(cargo_metadata::Message::Unknown) => {} 157 | cargo_metadata::Message::Unknown => {}
167 }, 158 },
168 } 159 }
169 if self.should_recheck() {
170 self.last_update_req = None;
171 self.send(Message::ClearDiagnostics);
172 self.restart_check_process();
173 }
174 } 160 }
161 // If we rerun the thread, we need to discard the previous check results first
162 self.cancel_check_process();
175 } 163 }
176 fn should_recheck(&mut self) -> bool { 164 fn cancel_check_process(&mut self) {
177 if let Some(_last_update_req) = &self.last_update_req { 165 if self.check_process.take().is_some() {
178 // We currently only request an update on save, as we need up to 166 self.send(Message::Progress(Progress::DidCancel));
179 // date source on disk for cargo check to do it's magic, so we
180 // don't really need to debounce the requests at this point.
181 return true;
182 } 167 }
183 false
184 } 168 }
185 169 fn start_check_process(&self) -> (Receiver<cargo_metadata::Message>, jod_thread::JoinHandle) {
186 fn restart_check_process(&mut self) {
187 // First, clear and cancel the old thread
188 self.check_process = None;
189
190 let mut cmd = match &self.config { 170 let mut cmd = match &self.config {
191 FlycheckConfig::CargoCommand { 171 FlycheckConfig::CargoCommand {
192 command, 172 command,
@@ -223,8 +203,6 @@ impl FlycheckActor {
223 let thread = jod_thread::spawn(move || { 203 let thread = jod_thread::spawn(move || {
224 // If we trigger an error here, we will do so in the loop instead, 204 // If we trigger an error here, we will do so in the loop instead,
225 // which will break out of the loop, and continue the shutdown 205 // which will break out of the loop, and continue the shutdown
226 let _ = message_send.send(CheckEvent::Begin);
227
228 let res = run_cargo(cmd, &mut |message| { 206 let res = run_cargo(cmd, &mut |message| {
229 // Skip certain kinds of messages to only spend time on what's useful 207 // Skip certain kinds of messages to only spend time on what's useful
230 match &message { 208 match &message {
@@ -237,7 +215,7 @@ impl FlycheckActor {
237 } 215 }
238 216
239 // if the send channel was closed, we want to shutdown 217 // if the send channel was closed, we want to shutdown
240 message_send.send(CheckEvent::Msg(message)).is_ok() 218 message_send.send(message).is_ok()
241 }); 219 });
242 220
243 if let Err(err) = res { 221 if let Err(err) = res {
@@ -245,12 +223,8 @@ impl FlycheckActor {
245 // to display user-caused misconfiguration errors instead of just logging them here 223 // to display user-caused misconfiguration errors instead of just logging them here
246 log::error!("Cargo watcher failed {:?}", err); 224 log::error!("Cargo watcher failed {:?}", err);
247 } 225 }
248
249 // We can ignore any error here, as we are already in the progress
250 // of shutting down.
251 let _ = message_send.send(CheckEvent::End);
252 }); 226 });
253 self.check_process = Some((message_recv, thread)) 227 (message_recv, thread)
254 } 228 }
255 229
256 fn send(&self, check_task: Message) { 230 fn send(&self, check_task: Message) {
@@ -258,12 +232,6 @@ impl FlycheckActor {
258 } 232 }
259} 233}
260 234
261enum CheckEvent {
262 Begin,
263 Msg(cargo_metadata::Message),
264 End,
265}
266
267fn run_cargo( 235fn run_cargo(
268 mut command: Command, 236 mut command: Command,
269 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, 237 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index f224c69e7..384e1f0d2 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -21,7 +21,6 @@ use crate::{
21 main_loop::Task, 21 main_loop::Task,
22 reload::SourceRootConfig, 22 reload::SourceRootConfig,
23 request_metrics::{LatestRequests, RequestMetrics}, 23 request_metrics::{LatestRequests, RequestMetrics},
24 show_message,
25 thread_pool::TaskPool, 24 thread_pool::TaskPool,
26 to_proto::url_from_abs_path, 25 to_proto::url_from_abs_path,
27 Result, 26 Result,
@@ -182,9 +181,6 @@ impl GlobalState {
182 self.send(response.into()); 181 self.send(response.into());
183 } 182 }
184 } 183 }
185 pub(crate) fn show_message(&self, typ: lsp_types::MessageType, message: String) {
186 show_message(typ, message, &self.sender)
187 }
188} 184}
189 185
190impl Drop for GlobalState { 186impl Drop for GlobalState {
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index a24dfe58c..407944d85 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -39,7 +39,7 @@ pub mod config;
39use serde::de::DeserializeOwned; 39use serde::de::DeserializeOwned;
40 40
41pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>; 41pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>;
42pub use crate::{caps::server_capabilities, lsp_utils::show_message, main_loop::main_loop}; 42pub use crate::{caps::server_capabilities, main_loop::main_loop};
43use std::fmt; 43use std::fmt;
44 44
45pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> { 45pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> {
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 35917030c..fd793a17c 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -1,24 +1,13 @@
1//! Utilities for LSP-related boilerplate code. 1//! Utilities for LSP-related boilerplate code.
2use std::{error::Error, ops::Range}; 2use std::{error::Error, ops::Range};
3 3
4use crossbeam_channel::Sender; 4use lsp_server::Notification;
5use lsp_server::{Message, Notification}; 5use lsp_types::request::Request;
6use ra_db::Canceled; 6use ra_db::Canceled;
7use ra_ide::LineIndex; 7use ra_ide::LineIndex;
8use serde::Serialize; 8use serde::Serialize;
9 9
10use crate::from_proto; 10use crate::{from_proto, global_state::GlobalState};
11
12pub fn show_message(
13 typ: lsp_types::MessageType,
14 message: impl Into<String>,
15 sender: &Sender<Message>,
16) {
17 let message = message.into();
18 let params = lsp_types::ShowMessageParams { typ, message };
19 let not = notification_new::<lsp_types::notification::ShowMessage>(params);
20 sender.send(not.into()).unwrap();
21}
22 11
23pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { 12pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
24 e.downcast_ref::<Canceled>().is_some() 13 e.downcast_ref::<Canceled>().is_some()
@@ -38,6 +27,74 @@ where
38 Notification::new(N::METHOD.to_string(), params) 27 Notification::new(N::METHOD.to_string(), params)
39} 28}
40 29
30#[derive(Debug, Eq, PartialEq)]
31pub(crate) enum Progress {
32 Begin,
33 Report,
34 End,
35}
36
37impl Progress {
38 pub(crate) fn percentage(done: usize, total: usize) -> f64 {
39 (done as f64 / total.max(1) as f64) * 100.0
40 }
41}
42
43impl GlobalState {
44 pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
45 let message = message.into();
46 let params = lsp_types::ShowMessageParams { typ, message };
47 let not = notification_new::<lsp_types::notification::ShowMessage>(params);
48 self.send(not.into());
49 }
50
51 pub(crate) fn report_progress(
52 &mut self,
53 title: &str,
54 state: Progress,
55 message: Option<String>,
56 percentage: Option<f64>,
57 ) {
58 if !self.config.client_caps.work_done_progress {
59 return;
60 }
61 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
62 let work_done_progress = match state {
63 Progress::Begin => {
64 let work_done_progress_create = self.req_queue.outgoing.register(
65 lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
66 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
67 |_, _| (),
68 );
69 self.send(work_done_progress_create.into());
70
71 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
72 title: title.into(),
73 cancellable: None,
74 message,
75 percentage,
76 })
77 }
78 Progress::Report => {
79 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
80 cancellable: None,
81 message,
82 percentage,
83 })
84 }
85 Progress::End => {
86 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
87 }
88 };
89 let notification =
90 notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
91 token,
92 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
93 });
94 self.send(notification.into());
95 }
96}
97
41pub(crate) fn apply_document_changes( 98pub(crate) fn apply_document_changes(
42 old_text: &mut String, 99 old_text: &mut String,
43 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, 100 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 8fc816cbd..ae3c7e30e 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -18,7 +18,7 @@ use crate::{
18 from_proto, 18 from_proto,
19 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, 19 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
20 handlers, lsp_ext, 20 handlers, lsp_ext,
21 lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new}, 21 lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new, Progress},
22 Result, 22 Result,
23}; 23};
24 24
@@ -181,18 +181,15 @@ impl GlobalState {
181 became_ready = true; 181 became_ready = true;
182 Progress::End 182 Progress::End
183 }; 183 };
184 report_progress( 184 self.report_progress(
185 self,
186 "roots scanned", 185 "roots scanned",
187 state, 186 state,
188 Some(format!("{}/{}", n_done, n_total)), 187 Some(format!("{}/{}", n_done, n_total)),
189 Some(percentage(n_done, n_total)), 188 Some(Progress::percentage(n_done, n_total)),
190 ) 189 )
191 } 190 }
192 }, 191 },
193 Event::Flycheck(task) => match task { 192 Event::Flycheck(task) => match task {
194 flycheck::Message::ClearDiagnostics => self.diagnostics.clear_check(),
195
196 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { 193 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
197 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( 194 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
198 &self.config.diagnostics, 195 &self.config.diagnostics,
@@ -215,14 +212,19 @@ impl GlobalState {
215 212
216 flycheck::Message::Progress(status) => { 213 flycheck::Message::Progress(status) => {
217 let (state, message) = match status { 214 let (state, message) = match status {
218 flycheck::Progress::Being => (Progress::Begin, None), 215 flycheck::Progress::DidStart => {
216 self.diagnostics.clear_check();
217 (Progress::Begin, None)
218 }
219 flycheck::Progress::DidCheckCrate(target) => { 219 flycheck::Progress::DidCheckCrate(target) => {
220 (Progress::Report, Some(target)) 220 (Progress::Report, Some(target))
221 } 221 }
222 flycheck::Progress::End => (Progress::End, None), 222 flycheck::Progress::DidFinish | flycheck::Progress::DidCancel => {
223 (Progress::End, None)
224 }
223 }; 225 };
224 226
225 report_progress(self, "cargo check", state, message, None); 227 self.report_progress("cargo check", state, message, None);
226 } 228 }
227 }, 229 },
228 } 230 }
@@ -465,60 +467,3 @@ impl GlobalState {
465 }); 467 });
466 } 468 }
467} 469}
468
469#[derive(Eq, PartialEq)]
470enum Progress {
471 Begin,
472 Report,
473 End,
474}
475
476fn percentage(done: usize, total: usize) -> f64 {
477 (done as f64 / total.max(1) as f64) * 100.0
478}
479
480fn report_progress(
481 global_state: &mut GlobalState,
482 title: &str,
483 state: Progress,
484 message: Option<String>,
485 percentage: Option<f64>,
486) {
487 if !global_state.config.client_caps.work_done_progress {
488 return;
489 }
490 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
491 let work_done_progress = match state {
492 Progress::Begin => {
493 let work_done_progress_create = global_state.req_queue.outgoing.register(
494 lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
495 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
496 |_, _| (),
497 );
498 global_state.send(work_done_progress_create.into());
499
500 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
501 title: title.into(),
502 cancellable: None,
503 message,
504 percentage,
505 })
506 }
507 Progress::Report => {
508 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
509 cancellable: None,
510 message,
511 percentage,
512 })
513 }
514 Progress::End => {
515 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
516 }
517 };
518 let notification =
519 notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
520 token,
521 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
522 });
523 global_state.send(notification.into());
524}
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index a22d3e262..fece6176e 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -36,27 +36,31 @@ impl GlobalState {
36 self.config 36 self.config
37 .linked_projects 37 .linked_projects
38 .iter() 38 .iter()
39 .filter_map(|project| match project { 39 .map(|project| match project {
40 LinkedProject::ProjectManifest(manifest) => { 40 LinkedProject::ProjectManifest(manifest) => {
41 ra_project_model::ProjectWorkspace::load( 41 ra_project_model::ProjectWorkspace::load(
42 manifest.clone(), 42 manifest.clone(),
43 &self.config.cargo, 43 &self.config.cargo,
44 self.config.with_sysroot, 44 self.config.with_sysroot,
45 ) 45 )
46 .map_err(|err| {
47 log::error!("failed to load workspace: {:#}", err);
48 self.show_message(
49 lsp_types::MessageType::Error,
50 format!("rust-analyzer failed to load workspace: {:#}", err),
51 );
52 })
53 .ok()
54 } 46 }
55 LinkedProject::InlineJsonProject(it) => { 47 LinkedProject::InlineJsonProject(it) => {
56 Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) 48 Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
57 } 49 }
58 }) 50 })
59 .collect::<Vec<_>>() 51 .collect::<Vec<_>>()
52 .into_iter()
53 .filter_map(|res| {
54 res.map_err(|err| {
55 log::error!("failed to load workspace: {:#}", err);
56 self.show_message(
57 lsp_types::MessageType::Error,
58 format!("rust-analyzer failed to load workspace: {:#}", err),
59 );
60 })
61 .ok()
62 })
63 .collect::<Vec<_>>()
60 }; 64 };
61 65
62 if let FilesWatcher::Client = self.config.files.watcher { 66 if let FilesWatcher::Client = self.config.files.watcher {