diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 915199bd878a..cba597d0ee0b 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -244,8 +244,11 @@ impl Analysis { self.with_db(|db| status::status(&*db)) } - pub fn prime_caches(&self, files: Vec) -> Cancelable<()> { - self.with_db(|db| prime_caches::prime_caches(db, files)) + pub fn prime_caches

(&self, files: Vec, 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. diff --git a/crates/ra_ide/src/prime_caches.rs b/crates/ra_ide/src/prime_caches.rs index 90bf7d25f20c..81c714ba1fa4 100644 --- a/crates/ra_ide/src/prime_caches.rs +++ b/crates/ra_ide/src/prime_caches.rs @@ -5,8 +5,13 @@ use crate::{FileId, RootDatabase}; -pub(crate) fn prime_caches(db: &RootDatabase, files: Vec) { - for file in files { +pub(crate) fn prime_caches( + db: &RootDatabase, + files: Vec, + mut report_progress: impl FnMut(usize), +) { + for (i, file) in files.into_iter().enumerate() { let _ = crate::syntax_highlighting::highlight(db, file, None); + report_progress(i); } } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 17b0b95b9dbc..9dce1b0e37c5 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -4,6 +4,8 @@ mod handlers; mod subscriptions; pub(crate) mod pending_requests; +mod progress; +mod lsp_utils; use std::{ borrow::Cow, @@ -20,11 +22,7 @@ use std::{ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; use itertools::Itertools; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; -use lsp_types::{ - DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, - WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, - WorkDoneProgressReport, -}; +use lsp_types::{DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent}; use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; use ra_prof::profile; @@ -47,6 +45,9 @@ use crate::{ world::{WorldSnapshot, WorldState}, 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 { @@ -93,6 +94,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) } let mut loop_state = LoopState::default(); + let mut world_state = { let workspaces = { // FIXME: support dynamic workspace loading. @@ -174,6 +176,11 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) loop_state.roots_total = world_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::(); @@ -299,7 +306,7 @@ struct LoopState { in_flight_libraries: usize, pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc)>)>, workspace_loaded: bool, - roots_progress_reported: Option, + roots_progress: Option, roots_scanned: usize, roots_total: usize, configuration_request_id: Option, @@ -428,7 +435,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 { @@ -441,7 +448,22 @@ fn loop_turn( pool.execute({ let subs = loop_state.subscriptions.subscriptions(); let snap = world_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| ()); + } }); } @@ -774,54 +796,12 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: } } -fn send_startup_progress(sender: &Sender, 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::( - loop_state.next_request_id(), - WorkDoneProgressCreateParams { - token: req::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, work_done_progress: WorkDoneProgress) { - let notif = notification_new::(req::ProgressParams { - token: req::ProgressToken::String("rustAnalyzer/startup".into()), - value: req::ProgressParamsValue::WorkDone(work_done_progress), - }); - sender.send(notif.into()).unwrap(); } } @@ -944,7 +924,7 @@ where } } Err(e) => { - if is_canceled(&e) { + if is_canceled(&*e) { Response::new_err( id, ErrorCode::ContentModified as i32, @@ -971,7 +951,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); } } @@ -984,45 +964,6 @@ fn update_file_notifications_on_threadpool( } } -pub fn show_message(typ: req::MessageType, message: impl Into, sender: &Sender) { - let message = message.into(); - let params = req::ShowMessageParams { typ, message }; - let not = notification_new::(params); - sender.send(not.into()).unwrap(); -} - -fn is_canceled(e: &Box) -> bool { - e.downcast_ref::().is_some() -} - -fn notification_is(notification: &Notification) -> bool { - notification.method == N::METHOD -} - -fn notification_cast(notification: Notification) -> std::result::Result -where - N: lsp_types::notification::Notification, - N::Params: DeserializeOwned, -{ - notification.extract(N::METHOD) -} - -fn notification_new(params: N::Params) -> Notification -where - N: lsp_types::notification::Notification, - N::Params: Serialize, -{ - Notification::new(N::METHOD.to_string(), params) -} - -fn request_new(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; diff --git a/crates/rust-analyzer/src/main_loop/lsp_utils.rs b/crates/rust-analyzer/src/main_loop/lsp_utils.rs new file mode 100644 index 000000000000..fecc9934603f --- /dev/null +++ b/crates/rust-analyzer/src/main_loop/lsp_utils.rs @@ -0,0 +1,47 @@ +use crate::req; +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: req::MessageType, message: impl Into, sender: &Sender) { + let message = message.into(); + let params = req::ShowMessageParams { typ, message }; + let not = notification_new::(params); + sender.send(not.into()).unwrap(); +} + +pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { + e.downcast_ref::().is_some() +} + +pub(crate) fn notification_is( + notification: &Notification, +) -> bool { + notification.method == N::METHOD +} + +pub(crate) fn notification_cast(notification: Notification) -> Result +where + N: lsp_types::notification::Notification, + N::Params: DeserializeOwned, +{ + notification.extract(N::METHOD) +} + +pub(crate) fn notification_new(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(id: RequestId, params: R::Params) -> Request +where + R: lsp_types::request::Request, + R::Params: Serialize, +{ + Request::new(id, R::METHOD.to_string(), params) +} diff --git a/crates/rust-analyzer/src/main_loop/progress.rs b/crates/rust-analyzer/src/main_loop/progress.rs new file mode 100644 index 000000000000..21e0abe2bfdf --- /dev/null +++ b/crates/rust-analyzer/src/main_loop/progress.rs @@ -0,0 +1,130 @@ +use super::lsp_utils::{notification_new, request_new}; +use crate::req; +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, 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, 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, + token: &'static str, + label: &'static str, + processed: usize, + total: usize, +} + +impl ProgressNotifier { + fn begin(&self, req_id: RequestId) { + let create_req = request_new::( + req_id, + WorkDoneProgressCreateParams { + token: req::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::(req::ProgressParams { + token: req::ProgressToken::String(self.token.to_owned()), + value: req::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 + } +} diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 8d47ee4f64c4..e129a926e440 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs @@ -208,7 +208,7 @@ impl Server { ProgressParams { token: req::ProgressToken::String(ref token), value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)), - } if token == "rustAnalyzer/startup" => true, + } if token == "rustAnalyzer/workspaceAnalysis" => true, _ => false, } }