Migrate flycheck to fully-lsp-compatible progress reports (introduce ra_progress crate)

This commit is contained in:
veetaha 2020-05-10 06:44:02 +03:00 committed by Veetaha
parent 0262dba97e
commit 2f8126fcac
6 changed files with 225 additions and 101 deletions

View file

@ -241,8 +241,11 @@ impl Analysis {
self.with_db(|db| status::status(&*db))
}
pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
self.with_db(|db| prime_caches::prime_caches(db, files))
pub fn prime_caches<P>(&self, files: Vec<FileId>, report_progress: P) -> Cancelable<()>
where
P: FnMut(usize) + std::panic::UnwindSafe,
{
self.with_db(|db| prime_caches::prime_caches(db, files, report_progress))
}
/// Gets the text of the source file.

View file

@ -5,8 +5,13 @@
use crate::{FileId, RootDatabase};
pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
for file in files {
pub(crate) fn prime_caches(
db: &RootDatabase,
files: Vec<FileId>,
mut report_progress: impl FnMut(usize),
) {
for (i, file) in files.into_iter().enumerate() {
let _ = crate::syntax_highlighting::highlight(db, file, None, false);
report_progress(i);
}
}

View file

@ -4,6 +4,8 @@
mod handlers;
mod subscriptions;
pub(crate) mod pending_requests;
mod progress;
mod lsp_utils;
use std::{
borrow::Cow,
@ -44,6 +46,9 @@ use crate::{
},
Result,
};
pub use lsp_utils::show_message;
use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new};
use progress::{IsDone, PrimeCachesProgressNotifier, WorkspaceAnalysisProgressNotifier};
#[derive(Debug)]
pub struct LspError {
@ -90,6 +95,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
}
let mut loop_state = LoopState::default();
let mut global_state = {
let workspaces = {
if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
@ -164,6 +170,12 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
};
loop_state.roots_total = global_state.vfs.read().n_roots();
loop_state.roots_scanned = 0;
loop_state.roots_progress = Some(WorkspaceAnalysisProgressNotifier::begin(
connection.sender.clone(),
loop_state.next_request_id(),
loop_state.roots_total,
));
let pool = ThreadPool::default();
let (task_sender, task_receiver) = unbounded::<Task>();
@ -271,7 +283,7 @@ struct LoopState {
pending_requests: PendingRequests,
subscriptions: Subscriptions,
workspace_loaded: bool,
roots_progress_reported: Option<usize>,
roots_progress: Option<WorkspaceAnalysisProgressNotifier>,
roots_scanned: usize,
roots_total: usize,
configuration_request_id: Option<RequestId>,
@ -372,7 +384,7 @@ fn loop_turn(
}
if show_progress {
send_startup_progress(&connection.sender, loop_state);
send_workspace_analisys_progress(loop_state);
}
if state_changed && loop_state.workspace_loaded {
@ -385,7 +397,22 @@ fn loop_turn(
pool.execute({
let subs = loop_state.subscriptions.subscriptions();
let snap = global_state.snapshot();
move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
let total = subs.len();
let mut progress = PrimeCachesProgressNotifier::begin(
connection.sender.clone(),
loop_state.next_request_id(),
total,
);
move || {
snap.analysis()
.prime_caches(subs, move |i| {
progress.report(i + 1);
})
.unwrap_or_else(|_: Canceled| ());
}
});
}
@ -744,55 +771,12 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
}
}
fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
let total: usize = loop_state.roots_total;
let prev = loop_state.roots_progress_reported;
let progress = loop_state.roots_scanned;
loop_state.roots_progress_reported = Some(progress);
match (prev, loop_state.workspace_loaded) {
(None, false) => {
let work_done_progress_create = request_new::<lsp_types::request::WorkDoneProgressCreate>(
loop_state.next_request_id(),
WorkDoneProgressCreateParams {
token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()),
},
);
sender.send(work_done_progress_create.into()).unwrap();
send_startup_progress_notif(
sender,
WorkDoneProgress::Begin(WorkDoneProgressBegin {
title: "rust-analyzer".into(),
cancellable: None,
message: Some(format!("{}/{} packages", progress, total)),
percentage: Some(100.0 * progress as f64 / total as f64),
}),
);
fn send_workspace_analisys_progress(loop_state: &mut LoopState) {
if let Some(progress) = &mut loop_state.roots_progress {
if loop_state.workspace_loaded || progress.report(loop_state.roots_scanned) == IsDone(true)
{
loop_state.roots_progress = None;
}
(Some(prev), false) if progress != prev => send_startup_progress_notif(
sender,
WorkDoneProgress::Report(WorkDoneProgressReport {
cancellable: None,
message: Some(format!("{}/{} packages", progress, total)),
percentage: Some(100.0 * progress as f64 / total as f64),
}),
),
(_, true) => send_startup_progress_notif(
sender,
WorkDoneProgress::End(WorkDoneProgressEnd {
message: Some(format!("rust-analyzer loaded, {} packages", progress)),
}),
),
_ => {}
}
fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) {
let notif =
notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()),
value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
});
sender.send(notif.into()).unwrap();
}
}
@ -918,7 +902,7 @@ where
}
}
Err(e) => {
if is_canceled(&e) {
if is_canceled(&*e) {
Response::new_err(
id,
ErrorCode::ContentModified as i32,
@ -945,7 +929,7 @@ fn update_file_notifications_on_threadpool(
for file_id in subscriptions {
match handlers::publish_diagnostics(&world, file_id) {
Err(e) => {
if !is_canceled(&e) {
if !is_canceled(&*e) {
log::error!("failed to compute diagnostics: {:?}", e);
}
}
@ -958,49 +942,6 @@ fn update_file_notifications_on_threadpool(
}
}
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();
}
fn is_canceled(e: &Box<dyn std::error::Error + Send + Sync>) -> bool {
e.downcast_ref::<Canceled>().is_some()
}
fn notification_is<N: lsp_types::notification::Notification>(notification: &Notification) -> bool {
notification.method == N::METHOD
}
fn notification_cast<N>(notification: Notification) -> std::result::Result<N::Params, Notification>
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned,
{
notification.extract(N::METHOD)
}
fn notification_new<N>(params: N::Params) -> Notification
where
N: lsp_types::notification::Notification,
N::Params: Serialize,
{
Notification::new(N::METHOD.to_string(), params)
}
fn request_new<R>(id: RequestId, params: R::Params) -> Request
where
R: lsp_types::request::Request,
R::Params: Serialize,
{
Request::new(id, R::METHOD.to_string(), params)
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;

View file

@ -0,0 +1,46 @@
use crossbeam_channel::Sender;
use lsp_server::{Message, Notification, Request, RequestId};
use ra_db::Canceled;
use serde::{de::DeserializeOwned, Serialize};
use std::error::Error;
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 {
e.downcast_ref::<Canceled>().is_some()
}
pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
notification: &Notification,
) -> bool {
notification.method == N::METHOD
}
pub(crate) fn notification_cast<N>(notification: Notification) -> Result<N::Params, Notification>
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned,
{
notification.extract(N::METHOD)
}
pub(crate) fn notification_new<N>(params: N::Params) -> Notification
where
N: lsp_types::notification::Notification,
N::Params: Serialize,
{
Notification::new(N::METHOD.to_string(), params)
}
pub(crate) fn request_new<R>(id: RequestId, params: R::Params) -> Request
where
R: lsp_types::request::Request,
R::Params: Serialize,
{
Request::new(id, R::METHOD.to_string(), params)
}

View file

@ -0,0 +1,129 @@
use super::lsp_utils::{notification_new, request_new};
use crossbeam_channel::Sender;
use lsp_server::{Message, RequestId};
use lsp_types::{
WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
WorkDoneProgressReport,
};
const PRIME_CACHES_PROGRESS_TOKEN: &str = "rustAnalyzer/primeCaches";
const WORKSPACE_ANALYSIS_PROGRESS_TOKEN: &str = "rustAnalyzer/workspaceAnalysis";
#[derive(Debug)]
pub(crate) struct PrimeCachesProgressNotifier(ProgressNotifier);
impl Drop for PrimeCachesProgressNotifier {
fn drop(&mut self) {
self.0.end("done priming caches".to_owned());
}
}
impl PrimeCachesProgressNotifier {
pub(crate) fn begin(sender: Sender<Message>, req_id: RequestId, total: usize) -> Self {
let me = Self(ProgressNotifier {
sender,
processed: 0,
total,
token: PRIME_CACHES_PROGRESS_TOKEN,
label: "priming caches",
});
me.0.begin(req_id);
me
}
pub(crate) fn report(&mut self, processed: usize) -> IsDone {
self.0.report(processed)
}
}
#[derive(Debug)]
pub(crate) struct WorkspaceAnalysisProgressNotifier(ProgressNotifier);
impl Drop for WorkspaceAnalysisProgressNotifier {
fn drop(&mut self) {
self.0.end("done analyzing workspace".to_owned());
}
}
impl WorkspaceAnalysisProgressNotifier {
pub(crate) fn begin(sender: Sender<Message>, req_id: RequestId, total: usize) -> Self {
let me = Self(ProgressNotifier {
sender,
total,
processed: 0,
token: WORKSPACE_ANALYSIS_PROGRESS_TOKEN,
label: "analyzing packages",
});
me.0.begin(req_id);
me
}
pub(crate) fn report(&mut self, processed: usize) -> IsDone {
self.0.report(processed)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct IsDone(pub bool);
#[derive(Debug)]
struct ProgressNotifier {
sender: Sender<Message>,
token: &'static str,
label: &'static str,
processed: usize,
total: usize,
}
impl ProgressNotifier {
fn begin(&self, req_id: RequestId) {
let create_req = request_new::<lsp_types::request::WorkDoneProgressCreate>(
req_id,
WorkDoneProgressCreateParams {
token: lsp_types::ProgressToken::String(self.token.to_owned()),
},
);
self.sender.send(create_req.into()).unwrap();
self.send_notification(WorkDoneProgress::Begin(WorkDoneProgressBegin {
cancellable: None,
title: "rust-analyzer".to_owned(),
percentage: Some(self.percentage()),
message: Some(self.create_progress_message()),
}));
}
fn report(&mut self, processed: usize) -> IsDone {
if self.processed != processed {
self.processed = processed;
self.send_notification(WorkDoneProgress::Report(WorkDoneProgressReport {
cancellable: None,
percentage: Some(self.percentage()),
message: Some(self.create_progress_message()),
}));
}
IsDone(processed >= self.total)
}
fn end(&mut self, message: String) {
self.send_notification(WorkDoneProgress::End(WorkDoneProgressEnd {
message: Some(message),
}));
}
fn send_notification(&self, progress: WorkDoneProgress) {
let notif = notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
token: lsp_types::ProgressToken::String(self.token.to_owned()),
value: lsp_types::ProgressParamsValue::WorkDone(progress),
});
self.sender.send(notif.into()).unwrap();
}
fn create_progress_message(&self) -> String {
format!("{} ({}/{})", self.label, self.processed, self.total)
}
fn percentage(&self) -> f64 {
(100 * self.processed) as f64 / self.total as f64
}
}

View file

@ -212,7 +212,7 @@ impl Server {
ProgressParams {
token: lsp_types::ProgressToken::String(ref token),
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)),
} if token == "rustAnalyzer/startup" => true,
} if token == "rustAnalyzer/workspaceAnalysis" => true,
_ => false,
}
}