Migrate flycheck to fully-lsp-compatible progress reports (introduce ra_progress crate)
This commit is contained in:
parent
2f8126fcac
commit
76c1160ffa
16 changed files with 361 additions and 362 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -963,6 +963,7 @@ dependencies = [
|
|||
"crossbeam-channel",
|
||||
"jod-thread",
|
||||
"log",
|
||||
"ra_progress",
|
||||
"ra_toolchain",
|
||||
"serde_json",
|
||||
]
|
||||
|
@ -1075,6 +1076,7 @@ dependencies = [
|
|||
"ra_hir",
|
||||
"ra_ide_db",
|
||||
"ra_prof",
|
||||
"ra_progress",
|
||||
"ra_syntax",
|
||||
"ra_text_edit",
|
||||
"rand",
|
||||
|
@ -1162,6 +1164,13 @@ dependencies = [
|
|||
"ra_arena",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ra_progress"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ra_project_model"
|
||||
version = "0.1.0"
|
||||
|
@ -1388,6 +1397,7 @@ dependencies = [
|
|||
"ra_mbe",
|
||||
"ra_proc_macro_srv",
|
||||
"ra_prof",
|
||||
"ra_progress",
|
||||
"ra_project_model",
|
||||
"ra_syntax",
|
||||
"ra_text_edit",
|
||||
|
|
|
@ -14,3 +14,4 @@ cargo_metadata = "0.10.0"
|
|||
serde_json = "1.0.48"
|
||||
jod-thread = "0.1.1"
|
||||
ra_toolchain = { path = "../ra_toolchain" }
|
||||
ra_progress = { path = "../ra_progress" }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! LSP diagnostics based on the output of the command.
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, BufReader},
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
|
@ -16,6 +17,9 @@ pub use cargo_metadata::diagnostic::{
|
|||
Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion,
|
||||
};
|
||||
|
||||
type Progress = ra_progress::Progress<(), String>;
|
||||
type ProgressSource = ra_progress::ProgressSource<(), String>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FlycheckConfig {
|
||||
CargoCommand {
|
||||
|
@ -31,6 +35,17 @@ pub enum FlycheckConfig {
|
|||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for FlycheckConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
|
||||
FlycheckConfig::CustomCommand { command, args } => {
|
||||
write!(f, "{} {}", command, args.join(" "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flycheck wraps the shared state and communication machinery used for
|
||||
/// running `cargo check` (or other compatible command) and providing
|
||||
/// diagnostics based on the output.
|
||||
|
@ -44,11 +59,15 @@ pub struct Flycheck {
|
|||
}
|
||||
|
||||
impl Flycheck {
|
||||
pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck {
|
||||
pub fn new(
|
||||
config: FlycheckConfig,
|
||||
workspace_root: PathBuf,
|
||||
progress_src: ProgressSource,
|
||||
) -> Flycheck {
|
||||
let (task_send, task_recv) = unbounded::<CheckTask>();
|
||||
let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
|
||||
let handle = jod_thread::spawn(move || {
|
||||
FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv);
|
||||
FlycheckThread::new(config, workspace_root, progress_src).run(&task_send, &cmd_recv);
|
||||
});
|
||||
Flycheck { task_recv, cmd_send, handle }
|
||||
}
|
||||
|
@ -66,16 +85,6 @@ pub enum CheckTask {
|
|||
|
||||
/// Request adding a diagnostic with fixes included to a file
|
||||
AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
|
||||
|
||||
/// Request check progress notification to client
|
||||
Status(Status),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
Being,
|
||||
Progress(String),
|
||||
End,
|
||||
}
|
||||
|
||||
pub enum CheckCommand {
|
||||
|
@ -87,6 +96,8 @@ struct FlycheckThread {
|
|||
config: FlycheckConfig,
|
||||
workspace_root: PathBuf,
|
||||
last_update_req: Option<Instant>,
|
||||
progress_src: ProgressSource,
|
||||
progress: Option<Progress>,
|
||||
// XXX: drop order is significant
|
||||
message_recv: Receiver<CheckEvent>,
|
||||
/// WatchThread exists to wrap around the communication needed to be able to
|
||||
|
@ -98,11 +109,17 @@ struct FlycheckThread {
|
|||
}
|
||||
|
||||
impl FlycheckThread {
|
||||
fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread {
|
||||
fn new(
|
||||
config: FlycheckConfig,
|
||||
workspace_root: PathBuf,
|
||||
progress_src: ProgressSource,
|
||||
) -> FlycheckThread {
|
||||
FlycheckThread {
|
||||
config,
|
||||
workspace_root,
|
||||
progress_src,
|
||||
last_update_req: None,
|
||||
progress: None,
|
||||
message_recv: never(),
|
||||
check_process: None,
|
||||
}
|
||||
|
@ -140,9 +157,9 @@ impl FlycheckThread {
|
|||
}
|
||||
}
|
||||
|
||||
fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
|
||||
fn clean_previous_results(&mut self, task_send: &Sender<CheckTask>) {
|
||||
task_send.send(CheckTask::ClearDiagnostics).unwrap();
|
||||
task_send.send(CheckTask::Status(Status::End)).unwrap();
|
||||
self.progress = None;
|
||||
}
|
||||
|
||||
fn should_recheck(&mut self) -> bool {
|
||||
|
@ -161,18 +178,17 @@ impl FlycheckThread {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
|
||||
fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
|
||||
match msg {
|
||||
CheckEvent::Begin => {
|
||||
task_send.send(CheckTask::Status(Status::Being)).unwrap();
|
||||
self.progress = Some(self.progress_src.begin(()));
|
||||
}
|
||||
|
||||
CheckEvent::End => {
|
||||
task_send.send(CheckTask::Status(Status::End)).unwrap();
|
||||
}
|
||||
|
||||
CheckEvent::End => self.progress = None,
|
||||
CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
|
||||
task_send.send(CheckTask::Status(Status::Progress(msg.target.name))).unwrap();
|
||||
self.progress
|
||||
.as_mut()
|
||||
.expect("check process reported progress without the 'Begin' notification")
|
||||
.report(msg.target.name);
|
||||
}
|
||||
|
||||
CheckEvent::Msg(Message::CompilerMessage(msg)) => {
|
||||
|
|
|
@ -29,6 +29,7 @@ ra_fmt = { path = "../ra_fmt" }
|
|||
ra_prof = { path = "../ra_prof" }
|
||||
test_utils = { path = "../test_utils" }
|
||||
ra_assists = { path = "../ra_assists" }
|
||||
ra_progress = { path = "../ra_progress" }
|
||||
|
||||
# ra_ide should depend only on the top-level `hir` package. if you need
|
||||
# something from some `hir_xxx` subpackage, reexport the API via `hir`.
|
||||
|
|
|
@ -241,11 +241,8 @@ impl Analysis {
|
|||
self.with_db(|db| status::status(&*db))
|
||||
}
|
||||
|
||||
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))
|
||||
pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
|
||||
self.with_db(|db| prime_caches::prime_caches(db, files))
|
||||
}
|
||||
|
||||
/// Gets the text of the source file.
|
||||
|
|
|
@ -5,13 +5,8 @@
|
|||
|
||||
use crate::{FileId, RootDatabase};
|
||||
|
||||
pub(crate) fn prime_caches(
|
||||
db: &RootDatabase,
|
||||
files: Vec<FileId>,
|
||||
mut report_progress: impl FnMut(usize),
|
||||
) {
|
||||
for (i, file) in files.into_iter().enumerate() {
|
||||
pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
|
||||
for file in files {
|
||||
let _ = crate::syntax_highlighting::highlight(db, file, None, false);
|
||||
report_progress(i);
|
||||
}
|
||||
}
|
||||
|
|
8
crates/ra_progress/Cargo.toml
Normal file
8
crates/ra_progress/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "ra_progress"
|
||||
version = "0.1.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = { version = "0.4" }
|
129
crates/ra_progress/src/lib.rs
Normal file
129
crates/ra_progress/src/lib.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
//! General-purpose instrumentation for progress reporting.
|
||||
//!
|
||||
//! Note:
|
||||
//! Most of the methods accept `&mut self` just to be more restrictive (for forward compat)
|
||||
//! even tho for some of them we can weaken this requirement to shared reference (`&self`).
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProgressStatus<B, P> {
|
||||
Begin(B),
|
||||
Progress(P),
|
||||
End,
|
||||
}
|
||||
|
||||
pub struct Progress<B, P>(Option<crossbeam_channel::Sender<ProgressStatus<B, P>>>);
|
||||
impl<B, P> Progress<B, P> {
|
||||
pub fn report(&mut self, payload: P) {
|
||||
self.report_with(|| payload);
|
||||
}
|
||||
|
||||
pub fn report_with(&mut self, payload: impl FnOnce() -> P) {
|
||||
self.send_status(|| ProgressStatus::Progress(payload()));
|
||||
}
|
||||
|
||||
fn send_status(&self, status: impl FnOnce() -> ProgressStatus<B, P>) {
|
||||
if let Some(sender) = &self.0 {
|
||||
sender.try_send(status()).expect("progress report must not block");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, P> Drop for Progress<B, P> {
|
||||
fn drop(&mut self) {
|
||||
self.send_status(|| ProgressStatus::End);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProgressSource<B, P>(Option<crossbeam_channel::Sender<ProgressStatus<B, P>>>);
|
||||
impl<B, P> ProgressSource<B, P> {
|
||||
pub fn real_if(real: bool) -> (Receiver<ProgressStatus<B, P>>, Self) {
|
||||
if real {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
(receiver, Self(Some(sender)))
|
||||
} else {
|
||||
(crossbeam_channel::never(), Self(None))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin(&mut self, payload: B) -> Progress<B, P> {
|
||||
self.begin_with(|| payload)
|
||||
}
|
||||
|
||||
pub fn begin_with(&mut self, payload: impl FnOnce() -> B) -> Progress<B, P> {
|
||||
let progress = Progress(self.0.clone());
|
||||
progress.send_status(|| ProgressStatus::Begin(payload()));
|
||||
progress
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, P> Clone for ProgressSource<B, P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, P> fmt::Debug for ProgressSource<B, P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("ProgressSource").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type U32ProgressStatus = ProgressStatus<U32ProgressReport, U32ProgressReport>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct U32ProgressReport {
|
||||
pub processed: u32,
|
||||
pub total: u32,
|
||||
}
|
||||
impl U32ProgressReport {
|
||||
pub fn percentage(&self) -> f64 {
|
||||
f64::from(100 * self.processed) / f64::from(self.total)
|
||||
}
|
||||
pub fn to_message(&self, prefix: &str, unit: &str) -> String {
|
||||
format!("{} ({}/{} {})", prefix, self.processed, self.total, unit)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U32Progress {
|
||||
inner: Progress<U32ProgressReport, U32ProgressReport>,
|
||||
processed: u32,
|
||||
total: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct IsDone(pub bool);
|
||||
|
||||
impl U32Progress {
|
||||
pub fn report(&mut self, new_processed: u32) -> IsDone {
|
||||
if self.processed < new_processed {
|
||||
self.processed = new_processed;
|
||||
self.inner.report(U32ProgressReport { processed: new_processed, total: self.total });
|
||||
}
|
||||
IsDone(self.processed >= self.total)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct U32ProgressSource {
|
||||
inner: ProgressSource<U32ProgressReport, U32ProgressReport>,
|
||||
}
|
||||
|
||||
impl U32ProgressSource {
|
||||
pub fn real_if(
|
||||
real: bool,
|
||||
) -> (Receiver<ProgressStatus<U32ProgressReport, U32ProgressReport>>, Self) {
|
||||
let (recv, inner) = ProgressSource::real_if(real);
|
||||
(recv, Self { inner })
|
||||
}
|
||||
|
||||
pub fn begin(&mut self, initial: u32, total: u32) -> U32Progress {
|
||||
U32Progress {
|
||||
inner: self.inner.begin(U32ProgressReport { processed: initial, total }),
|
||||
processed: initial,
|
||||
total,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ hir = { path = "../ra_hir", package = "ra_hir" }
|
|||
hir_def = { path = "../ra_hir_def", package = "ra_hir_def" }
|
||||
hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" }
|
||||
ra_proc_macro_srv = { path = "../ra_proc_macro_srv" }
|
||||
ra_progress = { path = "../ra_progress" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.8"
|
||||
|
|
|
@ -26,14 +26,19 @@ use crate::{
|
|||
LspError, Result,
|
||||
};
|
||||
use ra_db::{CrateId, ExternSourceId};
|
||||
use ra_progress::{ProgressSource, ProgressStatus};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
|
||||
fn create_flycheck(
|
||||
workspaces: &[ProjectWorkspace],
|
||||
config: &FlycheckConfig,
|
||||
progress_src: &ProgressSource<(), String>,
|
||||
) -> Option<Flycheck> {
|
||||
// FIXME: Figure out the multi-workspace situation
|
||||
workspaces.iter().find_map(|w| match w {
|
||||
workspaces.iter().find_map(move |w| match w {
|
||||
ProjectWorkspace::Cargo { cargo, .. } => {
|
||||
let cargo_project_root = cargo.workspace_root().to_path_buf();
|
||||
Some(Flycheck::new(config.clone(), cargo_project_root))
|
||||
Some(Flycheck::new(config.clone(), cargo_project_root, progress_src.clone()))
|
||||
}
|
||||
ProjectWorkspace::Json { .. } => {
|
||||
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
|
||||
|
@ -59,6 +64,8 @@ pub struct GlobalState {
|
|||
pub flycheck: Option<Flycheck>,
|
||||
pub diagnostics: DiagnosticCollection,
|
||||
pub proc_macro_client: ProcMacroClient,
|
||||
pub flycheck_progress_src: ProgressSource<(), String>,
|
||||
pub flycheck_progress_receiver: Receiver<ProgressStatus<(), String>>,
|
||||
}
|
||||
|
||||
/// An immutable snapshot of the world's state at a point in time.
|
||||
|
@ -158,7 +165,12 @@ impl GlobalState {
|
|||
}
|
||||
change.set_crate_graph(crate_graph);
|
||||
|
||||
let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
|
||||
let (flycheck_progress_receiver, flycheck_progress_src) =
|
||||
ProgressSource::real_if(config.client_caps.work_done_progress);
|
||||
let flycheck = config
|
||||
.check
|
||||
.as_ref()
|
||||
.and_then(|c| create_flycheck(&workspaces, c, &flycheck_progress_src));
|
||||
|
||||
let mut analysis_host = AnalysisHost::new(lru_capacity);
|
||||
analysis_host.apply_change(change);
|
||||
|
@ -171,6 +183,8 @@ impl GlobalState {
|
|||
task_receiver,
|
||||
latest_requests: Default::default(),
|
||||
flycheck,
|
||||
flycheck_progress_src,
|
||||
flycheck_progress_receiver,
|
||||
diagnostics: Default::default(),
|
||||
proc_macro_client,
|
||||
}
|
||||
|
@ -179,8 +193,10 @@ impl GlobalState {
|
|||
pub fn update_configuration(&mut self, config: Config) {
|
||||
self.analysis_host.update_lru_capacity(config.lru_capacity);
|
||||
if config.check != self.config.check {
|
||||
self.flycheck =
|
||||
config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
|
||||
self.flycheck = config
|
||||
.check
|
||||
.as_ref()
|
||||
.and_then(|it| create_flycheck(&self.workspaces, it, &self.flycheck_progress_src));
|
||||
}
|
||||
|
||||
self.config = config;
|
||||
|
@ -188,7 +204,7 @@ impl GlobalState {
|
|||
|
||||
/// Returns a vec of libraries
|
||||
/// FIXME: better API here
|
||||
pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool {
|
||||
pub fn process_changes(&mut self, roots_scanned: &mut u32) -> bool {
|
||||
let changes = self.vfs.write().commit_changes();
|
||||
if changes.is_empty() {
|
||||
return false;
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
mod handlers;
|
||||
mod subscriptions;
|
||||
pub(crate) mod pending_requests;
|
||||
mod progress;
|
||||
mod lsp_utils;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::TryFrom,
|
||||
env,
|
||||
error::Error,
|
||||
fmt,
|
||||
|
@ -20,12 +20,8 @@ use std::{
|
|||
|
||||
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
|
||||
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||
use lsp_types::{
|
||||
DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
|
||||
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
|
||||
WorkDoneProgressReport,
|
||||
};
|
||||
use ra_flycheck::{CheckTask, Status};
|
||||
use lsp_types::{DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent};
|
||||
use ra_flycheck::CheckTask;
|
||||
use ra_ide::{Canceled, FileId, LineIndex};
|
||||
use ra_prof::profile;
|
||||
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
||||
|
@ -48,7 +44,12 @@ use crate::{
|
|||
};
|
||||
pub use lsp_utils::show_message;
|
||||
use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new};
|
||||
use progress::{IsDone, PrimeCachesProgressNotifier, WorkspaceAnalysisProgressNotifier};
|
||||
use ra_progress::{
|
||||
IsDone, ProgressStatus, U32Progress, U32ProgressReport, U32ProgressSource, U32ProgressStatus,
|
||||
};
|
||||
|
||||
const FLYCHECK_PROGRESS_TOKEN: &str = "rustAnalyzer/flycheck";
|
||||
const ROOTS_SCANNED_PROGRESS_TOKEN: &str = "rustAnalyzer/rootsScanned";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LspError {
|
||||
|
@ -95,7 +96,6 @@ 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 {
|
||||
|
@ -169,13 +169,16 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
|||
GlobalState::new(workspaces, config.lru_capacity, &globs, config)
|
||||
};
|
||||
|
||||
loop_state.roots_total = global_state.vfs.read().n_roots();
|
||||
loop_state.roots_total = u32::try_from(global_state.vfs.read().n_roots())
|
||||
.expect("Wow, your project is so huge, that it cannot fit into u32...");
|
||||
|
||||
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 mut roots_scanned_progress_receiver = {
|
||||
let (recv, mut progress_src) =
|
||||
U32ProgressSource::real_if(global_state.config.client_caps.work_done_progress);
|
||||
loop_state.roots_progress = Some(progress_src.begin(0, loop_state.roots_total));
|
||||
recv
|
||||
};
|
||||
|
||||
let pool = ThreadPool::default();
|
||||
let (task_sender, task_receiver) = unbounded::<Task>();
|
||||
|
@ -198,6 +201,18 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
|||
recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
|
||||
Ok(task) => Event::CheckWatcher(task),
|
||||
Err(RecvError) => return Err("check watcher died".into()),
|
||||
},
|
||||
recv(global_state.flycheck_progress_receiver) -> status => match status {
|
||||
Ok(status) => Event::ProgressReport(ProgressReport::Flycheck(status)),
|
||||
Err(RecvError) => return Err("check watcher died".into()),
|
||||
},
|
||||
recv(roots_scanned_progress_receiver) -> status => match status {
|
||||
Ok(status) => Event::ProgressReport(ProgressReport::RootsScanned(status)),
|
||||
Err(RecvError) => {
|
||||
// Roots analysis has finished, we no longer need this receiver
|
||||
roots_scanned_progress_receiver = never();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Event::Msg(Message::Request(req)) = &event {
|
||||
|
@ -228,7 +243,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
|||
#[derive(Debug)]
|
||||
enum Task {
|
||||
Respond(Response),
|
||||
Notify(Notification),
|
||||
SendMessage(Message),
|
||||
Diagnostic(DiagnosticTask),
|
||||
}
|
||||
|
||||
|
@ -237,6 +252,13 @@ enum Event {
|
|||
Task(Task),
|
||||
Vfs(VfsTask),
|
||||
CheckWatcher(CheckTask),
|
||||
ProgressReport(ProgressReport),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProgressReport {
|
||||
Flycheck(ProgressStatus<(), String>),
|
||||
RootsScanned(U32ProgressStatus),
|
||||
}
|
||||
|
||||
impl fmt::Debug for Event {
|
||||
|
@ -253,7 +275,7 @@ impl fmt::Debug for Event {
|
|||
return debug_verbose_not(not, f);
|
||||
}
|
||||
}
|
||||
Event::Task(Task::Notify(not)) => {
|
||||
Event::Task(Task::SendMessage(Message::Notification(not))) => {
|
||||
if notification_is::<lsp_types::notification::PublishDiagnostics>(not) {
|
||||
return debug_verbose_not(not, f);
|
||||
}
|
||||
|
@ -272,20 +294,21 @@ impl fmt::Debug for Event {
|
|||
Event::Task(it) => fmt::Debug::fmt(it, f),
|
||||
Event::Vfs(it) => fmt::Debug::fmt(it, f),
|
||||
Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
|
||||
Event::ProgressReport(it) => fmt::Debug::fmt(it, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
struct LoopState {
|
||||
next_request_id: u64,
|
||||
pending_responses: FxHashSet<RequestId>,
|
||||
pending_requests: PendingRequests,
|
||||
subscriptions: Subscriptions,
|
||||
workspace_loaded: bool,
|
||||
roots_progress: Option<WorkspaceAnalysisProgressNotifier>,
|
||||
roots_scanned: usize,
|
||||
roots_total: usize,
|
||||
roots_progress: Option<U32Progress>,
|
||||
roots_scanned: u32,
|
||||
roots_total: u32,
|
||||
configuration_request_id: Option<RequestId>,
|
||||
}
|
||||
|
||||
|
@ -326,6 +349,9 @@ fn loop_turn(
|
|||
global_state.vfs.write().handle_task(task);
|
||||
}
|
||||
Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
|
||||
Event::ProgressReport(report) => {
|
||||
on_progress_report(report, task_sender, loop_state, global_state)
|
||||
}
|
||||
Event::Msg(msg) => match msg {
|
||||
Message::Request(req) => on_request(
|
||||
global_state,
|
||||
|
@ -384,7 +410,13 @@ fn loop_turn(
|
|||
}
|
||||
|
||||
if show_progress {
|
||||
send_workspace_analisys_progress(loop_state);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state_changed && loop_state.workspace_loaded {
|
||||
|
@ -397,22 +429,7 @@ fn loop_turn(
|
|||
pool.execute({
|
||||
let subs = loop_state.subscriptions.subscriptions();
|
||||
let snap = global_state.snapshot();
|
||||
|
||||
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| ());
|
||||
}
|
||||
move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -431,6 +448,87 @@ fn loop_turn(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn on_progress_report(
|
||||
report: ProgressReport,
|
||||
task_sender: &Sender<Task>,
|
||||
loop_state: &mut LoopState,
|
||||
global_state: &GlobalState,
|
||||
) {
|
||||
let end_report =
|
||||
|| lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message: None });
|
||||
let mut create_progress = |token: &'static str| {
|
||||
let create_progress_req = request_new::<lsp_types::request::WorkDoneProgressCreate>(
|
||||
loop_state.next_request_id(),
|
||||
lsp_types::WorkDoneProgressCreateParams {
|
||||
token: lsp_types::ProgressToken::String(token.to_string()),
|
||||
},
|
||||
);
|
||||
task_sender.send(Task::SendMessage(create_progress_req.into())).unwrap();
|
||||
};
|
||||
|
||||
let (token, progress) = match report {
|
||||
ProgressReport::Flycheck(status) => {
|
||||
let command = global_state
|
||||
.config
|
||||
.check
|
||||
.as_ref()
|
||||
.expect("There should be config, since flycheck is active");
|
||||
|
||||
let progress = match status {
|
||||
ProgressStatus::Begin(()) => {
|
||||
create_progress(FLYCHECK_PROGRESS_TOKEN);
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: "".to_string(),
|
||||
cancellable: Some(false),
|
||||
message: Some(command.to_string()),
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
ProgressStatus::Progress(target) => {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: Some(false),
|
||||
message: Some(format!("{} [{}]", command, target)),
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
ProgressStatus::End => end_report(),
|
||||
};
|
||||
(FLYCHECK_PROGRESS_TOKEN, progress)
|
||||
}
|
||||
ProgressReport::RootsScanned(status) => {
|
||||
fn to_message(report: &U32ProgressReport) -> String {
|
||||
report.to_message("analyzing the workspace", "packages")
|
||||
}
|
||||
let progress = match status {
|
||||
ProgressStatus::Begin(report) => {
|
||||
create_progress(ROOTS_SCANNED_PROGRESS_TOKEN);
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: "rust-analyzer".to_string(),
|
||||
cancellable: Some(false),
|
||||
message: Some(to_message(&report)),
|
||||
percentage: Some(report.percentage()),
|
||||
})
|
||||
}
|
||||
ProgressStatus::Progress(report) => {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: Some(false),
|
||||
message: Some(to_message(&report)),
|
||||
percentage: Some(report.percentage()),
|
||||
})
|
||||
}
|
||||
ProgressStatus::End => end_report(),
|
||||
};
|
||||
(ROOTS_SCANNED_PROGRESS_TOKEN, progress)
|
||||
}
|
||||
};
|
||||
let params = lsp_types::ProgressParams {
|
||||
token: lsp_types::ProgressToken::String(token.to_string()),
|
||||
value: lsp_types::ProgressParamsValue::WorkDone(progress),
|
||||
};
|
||||
let not = notification_new::<lsp_types::notification::Progress>(params);
|
||||
task_sender.send(Task::SendMessage(not.into())).unwrap()
|
||||
}
|
||||
|
||||
fn on_task(
|
||||
task: Task,
|
||||
msg_sender: &Sender<Message>,
|
||||
|
@ -445,9 +543,7 @@ fn on_task(
|
|||
msg_sender.send(response.into()).unwrap();
|
||||
}
|
||||
}
|
||||
Task::Notify(n) => {
|
||||
msg_sender.send(n.into()).unwrap();
|
||||
}
|
||||
Task::SendMessage(msg) => msg_sender.send(msg).unwrap(),
|
||||
Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state),
|
||||
}
|
||||
}
|
||||
|
@ -718,42 +814,6 @@ fn on_check_task(
|
|||
)))?;
|
||||
}
|
||||
}
|
||||
|
||||
CheckTask::Status(status) => {
|
||||
if global_state.config.client_caps.work_done_progress {
|
||||
let progress = match status {
|
||||
Status::Being => {
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: "Running `cargo check`".to_string(),
|
||||
cancellable: Some(false),
|
||||
message: None,
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
Status::Progress(target) => {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: Some(false),
|
||||
message: Some(target),
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
Status::End => {
|
||||
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd {
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let params = lsp_types::ProgressParams {
|
||||
token: lsp_types::ProgressToken::String(
|
||||
"rustAnalyzer/cargoWatcher".to_string(),
|
||||
),
|
||||
value: lsp_types::ProgressParamsValue::WorkDone(progress),
|
||||
};
|
||||
let not = notification_new::<lsp_types::notification::Progress>(params);
|
||||
task_sender.send(Task::Notify(not)).unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
@ -771,15 +831,6 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PoolDispatcher<'a> {
|
||||
req: Option<Request>,
|
||||
pool: &'a ThreadPool,
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
//! Utilities for LSP-related boilerplate code.
|
||||
|
||||
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>) {
|
||||
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);
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -212,7 +212,7 @@ impl Server {
|
|||
ProgressParams {
|
||||
token: lsp_types::ProgressToken::String(ref token),
|
||||
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)),
|
||||
} if token == "rustAnalyzer/workspaceAnalysis" => true,
|
||||
} if token == "rustAnalyzer/rootsScanned" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { promises as fs, PathLike } from "fs";
|
|||
|
||||
import * as commands from './commands';
|
||||
import { activateInlayHints } from './inlay_hints';
|
||||
import { activateStatusDisplay } from './status_display';
|
||||
import { Ctx } from './ctx';
|
||||
import { Config, NIGHTLY_TAG } from './config';
|
||||
import { log, assert, isValidExecutable } from './util';
|
||||
|
@ -103,8 +102,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
|
||||
ctx.pushCleanup(activateTaskProvider(workspaceFolder));
|
||||
|
||||
activateStatusDisplay(ctx);
|
||||
|
||||
activateInlayHints(ctx);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
|
||||
import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient';
|
||||
|
||||
import { Ctx } from './ctx';
|
||||
|
||||
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||
|
||||
export function activateStatusDisplay(ctx: Ctx) {
|
||||
const statusDisplay = new StatusDisplay(ctx.config.checkOnSave.command);
|
||||
ctx.pushCleanup(statusDisplay);
|
||||
const client = ctx.client;
|
||||
if (client != null) {
|
||||
ctx.pushCleanup(client.onProgress(
|
||||
WorkDoneProgress.type,
|
||||
'rustAnalyzer/cargoWatcher',
|
||||
params => statusDisplay.handleProgressNotification(params)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class StatusDisplay implements Disposable {
|
||||
packageName?: string;
|
||||
|
||||
private i: number = 0;
|
||||
private statusBarItem: vscode.StatusBarItem;
|
||||
private command: string;
|
||||
private timer?: NodeJS.Timeout;
|
||||
|
||||
constructor(command: string) {
|
||||
this.statusBarItem = vscode.window.createStatusBarItem(
|
||||
vscode.StatusBarAlignment.Left,
|
||||
10,
|
||||
);
|
||||
this.command = command;
|
||||
this.statusBarItem.hide();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.packageName = undefined;
|
||||
|
||||
this.timer =
|
||||
this.timer ||
|
||||
setInterval(() => {
|
||||
this.tick();
|
||||
this.refreshLabel();
|
||||
}, 300);
|
||||
|
||||
this.statusBarItem.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
|
||||
this.statusBarItem.hide();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
|
||||
this.statusBarItem.dispose();
|
||||
}
|
||||
|
||||
refreshLabel() {
|
||||
if (this.packageName) {
|
||||
this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`;
|
||||
} else {
|
||||
this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command}`;
|
||||
}
|
||||
}
|
||||
|
||||
handleProgressNotification(params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd) {
|
||||
switch (params.kind) {
|
||||
case 'begin':
|
||||
this.show();
|
||||
break;
|
||||
|
||||
case 'report':
|
||||
if (params.message) {
|
||||
this.packageName = params.message;
|
||||
this.refreshLabel();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private tick() {
|
||||
this.i = (this.i + 1) % spinnerFrames.length;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue