5075: Be more precise about flycheck status r=matklad a=matklad



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-06-26 15:02:00 +00:00 committed by GitHub
commit fa70882418
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 159 deletions

View file

@ -7,7 +7,7 @@ use std::{
io::{self, BufReader}, io::{self, BufReader},
path::PathBuf, path::PathBuf,
process::{Command, Stdio}, process::{Command, Stdio},
time::Instant, time::Duration,
}; };
use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
@ -74,9 +74,6 @@ impl FlycheckHandle {
#[derive(Debug)] #[derive(Debug)]
pub enum Message { pub enum Message {
/// Request a clearing of all cached diagnostics from the check watcher
ClearDiagnostics,
/// Request adding a diagnostic with fixes included to a file /// Request adding a diagnostic with fixes included to a file
AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
@ -86,9 +83,10 @@ pub enum Message {
#[derive(Debug)] #[derive(Debug)]
pub enum Progress { pub enum Progress {
Being, DidStart,
DidCheckCrate(String), DidCheckCrate(String),
End, DidFinish,
DidCancel,
} }
struct Restart; struct Restart;
@ -97,19 +95,18 @@ struct FlycheckActor {
sender: Box<dyn Fn(Message) + Send>, sender: Box<dyn Fn(Message) + Send>,
config: FlycheckConfig, config: FlycheckConfig,
workspace_root: PathBuf, workspace_root: PathBuf,
last_update_req: Option<Instant>,
/// WatchThread exists to wrap around the communication needed to be able to /// WatchThread exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library /// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we /// doesn't provide a way to read sub-process output without blocking, so we
/// have to wrap sub-processes output handling in a thread and pass messages /// have to wrap sub-processes output handling in a thread and pass messages
/// back over a channel. /// back over a channel.
// XXX: drop order is significant // XXX: drop order is significant
check_process: Option<(Receiver<CheckEvent>, jod_thread::JoinHandle)>, check_process: Option<(Receiver<cargo_metadata::Message>, jod_thread::JoinHandle)>,
} }
enum Event { enum Event {
Restart(Restart), Restart(Restart),
CheckEvent(Option<CheckEvent>), CheckEvent(Option<cargo_metadata::Message>),
} }
impl FlycheckActor { impl FlycheckActor {
@ -118,7 +115,7 @@ impl FlycheckActor {
config: FlycheckConfig, config: FlycheckConfig,
workspace_root: PathBuf, workspace_root: PathBuf,
) -> FlycheckActor { ) -> FlycheckActor {
FlycheckActor { sender, config, workspace_root, last_update_req: None, check_process: None } FlycheckActor { sender, config, workspace_root, check_process: None }
} }
fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan); let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan);
@ -128,65 +125,48 @@ impl FlycheckActor {
} }
} }
fn run(&mut self, inbox: Receiver<Restart>) { fn run(&mut self, inbox: Receiver<Restart>) {
// If we rerun the thread, we need to discard the previous check results first
self.send(Message::ClearDiagnostics);
self.send(Message::Progress(Progress::End));
while let Some(event) = self.next_event(&inbox) { while let Some(event) = self.next_event(&inbox) {
match event { match event {
Event::Restart(Restart) => self.last_update_req = Some(Instant::now()), Event::Restart(Restart) => {
while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
self.cancel_check_process();
self.check_process = Some(self.start_check_process());
self.send(Message::Progress(Progress::DidStart));
}
Event::CheckEvent(None) => { Event::CheckEvent(None) => {
// Watcher finished, replace it with a never channel to // Watcher finished, replace it with a never channel to
// avoid busy-waiting. // avoid busy-waiting.
self.check_process = None; assert!(self.check_process.take().is_some());
self.send(Message::Progress(Progress::DidFinish));
} }
Event::CheckEvent(Some(event)) => match event { Event::CheckEvent(Some(message)) => match message {
CheckEvent::Begin => { cargo_metadata::Message::CompilerArtifact(msg) => {
self.send(Message::Progress(Progress::Being));
}
CheckEvent::End => {
self.send(Message::Progress(Progress::End));
}
CheckEvent::Msg(cargo_metadata::Message::CompilerArtifact(msg)) => {
self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name))); self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name)));
} }
CheckEvent::Msg(cargo_metadata::Message::CompilerMessage(msg)) => { cargo_metadata::Message::CompilerMessage(msg) => {
self.send(Message::AddDiagnostic { self.send(Message::AddDiagnostic {
workspace_root: self.workspace_root.clone(), workspace_root: self.workspace_root.clone(),
diagnostic: msg.message, diagnostic: msg.message,
}); });
} }
CheckEvent::Msg(cargo_metadata::Message::BuildScriptExecuted(_)) cargo_metadata::Message::BuildScriptExecuted(_)
| CheckEvent::Msg(cargo_metadata::Message::BuildFinished(_)) | cargo_metadata::Message::BuildFinished(_)
| CheckEvent::Msg(cargo_metadata::Message::TextLine(_)) | cargo_metadata::Message::TextLine(_)
| CheckEvent::Msg(cargo_metadata::Message::Unknown) => {} | cargo_metadata::Message::Unknown => {}
}, },
} }
if self.should_recheck() { }
self.last_update_req = None; // If we rerun the thread, we need to discard the previous check results first
self.send(Message::ClearDiagnostics); self.cancel_check_process();
self.restart_check_process(); }
} fn cancel_check_process(&mut self) {
if self.check_process.take().is_some() {
self.send(Message::Progress(Progress::DidCancel));
} }
} }
fn should_recheck(&mut self) -> bool { fn start_check_process(&self) -> (Receiver<cargo_metadata::Message>, jod_thread::JoinHandle) {
if let Some(_last_update_req) = &self.last_update_req {
// We currently only request an update on save, as we need up to
// date source on disk for cargo check to do it's magic, so we
// don't really need to debounce the requests at this point.
return true;
}
false
}
fn restart_check_process(&mut self) {
// First, clear and cancel the old thread
self.check_process = None;
let mut cmd = match &self.config { let mut cmd = match &self.config {
FlycheckConfig::CargoCommand { FlycheckConfig::CargoCommand {
command, command,
@ -223,8 +203,6 @@ impl FlycheckActor {
let thread = jod_thread::spawn(move || { let thread = jod_thread::spawn(move || {
// If we trigger an error here, we will do so in the loop instead, // If we trigger an error here, we will do so in the loop instead,
// which will break out of the loop, and continue the shutdown // which will break out of the loop, and continue the shutdown
let _ = message_send.send(CheckEvent::Begin);
let res = run_cargo(cmd, &mut |message| { let res = run_cargo(cmd, &mut |message| {
// Skip certain kinds of messages to only spend time on what's useful // Skip certain kinds of messages to only spend time on what's useful
match &message { match &message {
@ -237,7 +215,7 @@ impl FlycheckActor {
} }
// if the send channel was closed, we want to shutdown // if the send channel was closed, we want to shutdown
message_send.send(CheckEvent::Msg(message)).is_ok() message_send.send(message).is_ok()
}); });
if let Err(err) = res { if let Err(err) = res {
@ -245,12 +223,8 @@ impl FlycheckActor {
// to display user-caused misconfiguration errors instead of just logging them here // to display user-caused misconfiguration errors instead of just logging them here
log::error!("Cargo watcher failed {:?}", err); log::error!("Cargo watcher failed {:?}", err);
} }
// We can ignore any error here, as we are already in the progress
// of shutting down.
let _ = message_send.send(CheckEvent::End);
}); });
self.check_process = Some((message_recv, thread)) (message_recv, thread)
} }
fn send(&self, check_task: Message) { fn send(&self, check_task: Message) {
@ -258,12 +232,6 @@ impl FlycheckActor {
} }
} }
enum CheckEvent {
Begin,
Msg(cargo_metadata::Message),
End,
}
fn run_cargo( fn run_cargo(
mut command: Command, mut command: Command,
on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,

View file

@ -21,7 +21,6 @@ use crate::{
main_loop::Task, main_loop::Task,
reload::SourceRootConfig, reload::SourceRootConfig,
request_metrics::{LatestRequests, RequestMetrics}, request_metrics::{LatestRequests, RequestMetrics},
show_message,
thread_pool::TaskPool, thread_pool::TaskPool,
to_proto::url_from_abs_path, to_proto::url_from_abs_path,
Result, Result,
@ -182,9 +181,6 @@ impl GlobalState {
self.send(response.into()); self.send(response.into());
} }
} }
pub(crate) fn show_message(&self, typ: lsp_types::MessageType, message: String) {
show_message(typ, message, &self.sender)
}
} }
impl Drop for GlobalState { impl Drop for GlobalState {

View file

@ -39,7 +39,7 @@ pub mod config;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>; pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>;
pub use crate::{caps::server_capabilities, lsp_utils::show_message, main_loop::main_loop}; pub use crate::{caps::server_capabilities, main_loop::main_loop};
use std::fmt; use std::fmt;
pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> { pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> {

View file

@ -1,24 +1,13 @@
//! Utilities for LSP-related boilerplate code. //! Utilities for LSP-related boilerplate code.
use std::{error::Error, ops::Range}; use std::{error::Error, ops::Range};
use crossbeam_channel::Sender; use lsp_server::Notification;
use lsp_server::{Message, Notification}; use lsp_types::request::Request;
use ra_db::Canceled; use ra_db::Canceled;
use ra_ide::LineIndex; use ra_ide::LineIndex;
use serde::Serialize; use serde::Serialize;
use crate::from_proto; use crate::{from_proto, global_state::GlobalState};
pub fn show_message(
typ: lsp_types::MessageType,
message: impl Into<String>,
sender: &Sender<Message>,
) {
let message = message.into();
let params = lsp_types::ShowMessageParams { typ, message };
let not = notification_new::<lsp_types::notification::ShowMessage>(params);
sender.send(not.into()).unwrap();
}
pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<Canceled>().is_some() e.downcast_ref::<Canceled>().is_some()
@ -38,6 +27,74 @@ where
Notification::new(N::METHOD.to_string(), params) Notification::new(N::METHOD.to_string(), params)
} }
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum Progress {
Begin,
Report,
End,
}
impl Progress {
pub(crate) fn percentage(done: usize, total: usize) -> f64 {
(done as f64 / total.max(1) as f64) * 100.0
}
}
impl GlobalState {
pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
let message = message.into();
let params = lsp_types::ShowMessageParams { typ, message };
let not = notification_new::<lsp_types::notification::ShowMessage>(params);
self.send(not.into());
}
pub(crate) fn report_progress(
&mut self,
title: &str,
state: Progress,
message: Option<String>,
percentage: Option<f64>,
) {
if !self.config.client_caps.work_done_progress {
return;
}
let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
let work_done_progress = match state {
Progress::Begin => {
let work_done_progress_create = self.req_queue.outgoing.register(
lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
|_, _| (),
);
self.send(work_done_progress_create.into());
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
title: title.into(),
cancellable: None,
message,
percentage,
})
}
Progress::Report => {
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
cancellable: None,
message,
percentage,
})
}
Progress::End => {
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
}
};
let notification =
notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
token,
value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
});
self.send(notification.into());
}
}
pub(crate) fn apply_document_changes( pub(crate) fn apply_document_changes(
old_text: &mut String, old_text: &mut String,
content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,

View file

@ -18,7 +18,7 @@ use crate::{
from_proto, from_proto,
global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
handlers, lsp_ext, handlers, lsp_ext,
lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new}, lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new, Progress},
Result, Result,
}; };
@ -181,18 +181,15 @@ impl GlobalState {
became_ready = true; became_ready = true;
Progress::End Progress::End
}; };
report_progress( self.report_progress(
self,
"roots scanned", "roots scanned",
state, state,
Some(format!("{}/{}", n_done, n_total)), Some(format!("{}/{}", n_done, n_total)),
Some(percentage(n_done, n_total)), Some(Progress::percentage(n_done, n_total)),
) )
} }
}, },
Event::Flycheck(task) => match task { Event::Flycheck(task) => match task {
flycheck::Message::ClearDiagnostics => self.diagnostics.clear_check(),
flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
&self.config.diagnostics, &self.config.diagnostics,
@ -215,14 +212,19 @@ impl GlobalState {
flycheck::Message::Progress(status) => { flycheck::Message::Progress(status) => {
let (state, message) = match status { let (state, message) = match status {
flycheck::Progress::Being => (Progress::Begin, None), flycheck::Progress::DidStart => {
self.diagnostics.clear_check();
(Progress::Begin, None)
}
flycheck::Progress::DidCheckCrate(target) => { flycheck::Progress::DidCheckCrate(target) => {
(Progress::Report, Some(target)) (Progress::Report, Some(target))
} }
flycheck::Progress::End => (Progress::End, None), flycheck::Progress::DidFinish | flycheck::Progress::DidCancel => {
(Progress::End, None)
}
}; };
report_progress(self, "cargo check", state, message, None); self.report_progress("cargo check", state, message, None);
} }
}, },
} }
@ -465,60 +467,3 @@ impl GlobalState {
}); });
} }
} }
#[derive(Eq, PartialEq)]
enum Progress {
Begin,
Report,
End,
}
fn percentage(done: usize, total: usize) -> f64 {
(done as f64 / total.max(1) as f64) * 100.0
}
fn report_progress(
global_state: &mut GlobalState,
title: &str,
state: Progress,
message: Option<String>,
percentage: Option<f64>,
) {
if !global_state.config.client_caps.work_done_progress {
return;
}
let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
let work_done_progress = match state {
Progress::Begin => {
let work_done_progress_create = global_state.req_queue.outgoing.register(
lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
|_, _| (),
);
global_state.send(work_done_progress_create.into());
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
title: title.into(),
cancellable: None,
message,
percentage,
})
}
Progress::Report => {
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
cancellable: None,
message,
percentage,
})
}
Progress::End => {
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
}
};
let notification =
notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
token,
value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
});
global_state.send(notification.into());
}

View file

@ -36,27 +36,31 @@ impl GlobalState {
self.config self.config
.linked_projects .linked_projects
.iter() .iter()
.filter_map(|project| match project { .map(|project| match project {
LinkedProject::ProjectManifest(manifest) => { LinkedProject::ProjectManifest(manifest) => {
ra_project_model::ProjectWorkspace::load( ra_project_model::ProjectWorkspace::load(
manifest.clone(), manifest.clone(),
&self.config.cargo, &self.config.cargo,
self.config.with_sysroot, self.config.with_sysroot,
) )
.map_err(|err| {
log::error!("failed to load workspace: {:#}", err);
self.show_message(
lsp_types::MessageType::Error,
format!("rust-analyzer failed to load workspace: {:#}", err),
);
})
.ok()
} }
LinkedProject::InlineJsonProject(it) => { LinkedProject::InlineJsonProject(it) => {
Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter()
.filter_map(|res| {
res.map_err(|err| {
log::error!("failed to load workspace: {:#}", err);
self.show_message(
lsp_types::MessageType::Error,
format!("rust-analyzer failed to load workspace: {:#}", err),
);
})
.ok()
})
.collect::<Vec<_>>()
}; };
if let FilesWatcher::Client = self.config.files.watcher { if let FilesWatcher::Client = self.config.files.watcher {