aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorveetaha <[email protected]>2020-05-10 16:35:33 +0100
committerVeetaha <[email protected]>2020-06-18 12:50:56 +0100
commit76c1160ffa626fc5f07b309420e6666eb79a3311 (patch)
treefaa1e1bab885988042fb735f89c7bc5c59127a12 /crates
parent2f8126fcace3c5e7db01c755b91eb45a9c632cfd (diff)
Migrate flycheck to fully-lsp-compatible progress reports (introduce ra_progress crate)
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_flycheck/Cargo.toml1
-rw-r--r--crates/ra_flycheck/src/lib.rs62
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/lib.rs7
-rw-r--r--crates/ra_ide/src/prime_caches.rs9
-rw-r--r--crates/ra_progress/Cargo.toml8
-rw-r--r--crates/ra_progress/src/lib.rs129
-rw-r--r--crates/rust-analyzer/Cargo.toml1
-rw-r--r--crates/rust-analyzer/src/global_state.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop.rs223
-rw-r--r--crates/rust-analyzer/src/main_loop/lsp_utils.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop/progress.rs129
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs2
13 files changed, 351 insertions, 259 deletions
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml
index 1aa39bade..838973963 100644
--- a/crates/ra_flycheck/Cargo.toml
+++ b/crates/ra_flycheck/Cargo.toml
@@ -14,3 +14,4 @@ cargo_metadata = "0.10.0"
14serde_json = "1.0.48" 14serde_json = "1.0.48"
15jod-thread = "0.1.1" 15jod-thread = "0.1.1"
16ra_toolchain = { path = "../ra_toolchain" } 16ra_toolchain = { path = "../ra_toolchain" }
17ra_progress = { path = "../ra_progress" }
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index 6c4170529..7b9f48eb0 100644
--- a/crates/ra_flycheck/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -3,6 +3,7 @@
3//! LSP diagnostics based on the output of the command. 3//! LSP diagnostics based on the output of the command.
4 4
5use std::{ 5use std::{
6 fmt,
6 io::{self, BufReader}, 7 io::{self, BufReader},
7 path::PathBuf, 8 path::PathBuf,
8 process::{Command, Stdio}, 9 process::{Command, Stdio},
@@ -16,6 +17,9 @@ pub use cargo_metadata::diagnostic::{
16 Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, 17 Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion,
17}; 18};
18 19
20type Progress = ra_progress::Progress<(), String>;
21type ProgressSource = ra_progress::ProgressSource<(), String>;
22
19#[derive(Clone, Debug, PartialEq, Eq)] 23#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum FlycheckConfig { 24pub enum FlycheckConfig {
21 CargoCommand { 25 CargoCommand {
@@ -31,6 +35,17 @@ pub enum FlycheckConfig {
31 }, 35 },
32} 36}
33 37
38impl fmt::Display for FlycheckConfig {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
42 FlycheckConfig::CustomCommand { command, args } => {
43 write!(f, "{} {}", command, args.join(" "))
44 }
45 }
46 }
47}
48
34/// Flycheck wraps the shared state and communication machinery used for 49/// Flycheck wraps the shared state and communication machinery used for
35/// running `cargo check` (or other compatible command) and providing 50/// running `cargo check` (or other compatible command) and providing
36/// diagnostics based on the output. 51/// diagnostics based on the output.
@@ -44,11 +59,15 @@ pub struct Flycheck {
44} 59}
45 60
46impl Flycheck { 61impl Flycheck {
47 pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck { 62 pub fn new(
63 config: FlycheckConfig,
64 workspace_root: PathBuf,
65 progress_src: ProgressSource,
66 ) -> Flycheck {
48 let (task_send, task_recv) = unbounded::<CheckTask>(); 67 let (task_send, task_recv) = unbounded::<CheckTask>();
49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 68 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
50 let handle = jod_thread::spawn(move || { 69 let handle = jod_thread::spawn(move || {
51 FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv); 70 FlycheckThread::new(config, workspace_root, progress_src).run(&task_send, &cmd_recv);
52 }); 71 });
53 Flycheck { task_recv, cmd_send, handle } 72 Flycheck { task_recv, cmd_send, handle }
54 } 73 }
@@ -66,16 +85,6 @@ pub enum CheckTask {
66 85
67 /// Request adding a diagnostic with fixes included to a file 86 /// Request adding a diagnostic with fixes included to a file
68 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, 87 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
69
70 /// Request check progress notification to client
71 Status(Status),
72}
73
74#[derive(Debug)]
75pub enum Status {
76 Being,
77 Progress(String),
78 End,
79} 88}
80 89
81pub enum CheckCommand { 90pub enum CheckCommand {
@@ -87,6 +96,8 @@ struct FlycheckThread {
87 config: FlycheckConfig, 96 config: FlycheckConfig,
88 workspace_root: PathBuf, 97 workspace_root: PathBuf,
89 last_update_req: Option<Instant>, 98 last_update_req: Option<Instant>,
99 progress_src: ProgressSource,
100 progress: Option<Progress>,
90 // XXX: drop order is significant 101 // XXX: drop order is significant
91 message_recv: Receiver<CheckEvent>, 102 message_recv: Receiver<CheckEvent>,
92 /// WatchThread exists to wrap around the communication needed to be able to 103 /// WatchThread exists to wrap around the communication needed to be able to
@@ -98,11 +109,17 @@ struct FlycheckThread {
98} 109}
99 110
100impl FlycheckThread { 111impl FlycheckThread {
101 fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread { 112 fn new(
113 config: FlycheckConfig,
114 workspace_root: PathBuf,
115 progress_src: ProgressSource,
116 ) -> FlycheckThread {
102 FlycheckThread { 117 FlycheckThread {
103 config, 118 config,
104 workspace_root, 119 workspace_root,
120 progress_src,
105 last_update_req: None, 121 last_update_req: None,
122 progress: None,
106 message_recv: never(), 123 message_recv: never(),
107 check_process: None, 124 check_process: None,
108 } 125 }
@@ -140,9 +157,9 @@ impl FlycheckThread {
140 } 157 }
141 } 158 }
142 159
143 fn clean_previous_results(&self, task_send: &Sender<CheckTask>) { 160 fn clean_previous_results(&mut self, task_send: &Sender<CheckTask>) {
144 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 161 task_send.send(CheckTask::ClearDiagnostics).unwrap();
145 task_send.send(CheckTask::Status(Status::End)).unwrap(); 162 self.progress = None;
146 } 163 }
147 164
148 fn should_recheck(&mut self) -> bool { 165 fn should_recheck(&mut self) -> bool {
@@ -161,18 +178,17 @@ impl FlycheckThread {
161 } 178 }
162 } 179 }
163 180
164 fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) { 181 fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
165 match msg { 182 match msg {
166 CheckEvent::Begin => { 183 CheckEvent::Begin => {
167 task_send.send(CheckTask::Status(Status::Being)).unwrap(); 184 self.progress = Some(self.progress_src.begin(()));
168 } 185 }
169 186 CheckEvent::End => self.progress = None,
170 CheckEvent::End => {
171 task_send.send(CheckTask::Status(Status::End)).unwrap();
172 }
173
174 CheckEvent::Msg(Message::CompilerArtifact(msg)) => { 187 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
175 task_send.send(CheckTask::Status(Status::Progress(msg.target.name))).unwrap(); 188 self.progress
189 .as_mut()
190 .expect("check process reported progress without the 'Begin' notification")
191 .report(msg.target.name);
176 } 192 }
177 193
178 CheckEvent::Msg(Message::CompilerMessage(msg)) => { 194 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 05c940605..722652fb2 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -29,6 +29,7 @@ ra_fmt = { path = "../ra_fmt" }
29ra_prof = { path = "../ra_prof" } 29ra_prof = { path = "../ra_prof" }
30test_utils = { path = "../test_utils" } 30test_utils = { path = "../test_utils" }
31ra_assists = { path = "../ra_assists" } 31ra_assists = { path = "../ra_assists" }
32ra_progress = { path = "../ra_progress" }
32 33
33# ra_ide should depend only on the top-level `hir` package. if you need 34# ra_ide should depend only on the top-level `hir` package. if you need
34# something from some `hir_xxx` subpackage, reexport the API via `hir`. 35# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 6704467d9..51dc1f041 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -241,11 +241,8 @@ impl Analysis {
241 self.with_db(|db| status::status(&*db)) 241 self.with_db(|db| status::status(&*db))
242 } 242 }
243 243
244 pub fn prime_caches<P>(&self, files: Vec<FileId>, report_progress: P) -> Cancelable<()> 244 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
245 where 245 self.with_db(|db| prime_caches::prime_caches(db, files))
246 P: FnMut(usize) + std::panic::UnwindSafe,
247 {
248 self.with_db(|db| prime_caches::prime_caches(db, files, report_progress))
249 } 246 }
250 247
251 /// Gets the text of the source file. 248 /// 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 f60595989..c5ab5a1d8 100644
--- a/crates/ra_ide/src/prime_caches.rs
+++ b/crates/ra_ide/src/prime_caches.rs
@@ -5,13 +5,8 @@
5 5
6use crate::{FileId, RootDatabase}; 6use crate::{FileId, RootDatabase};
7 7
8pub(crate) fn prime_caches( 8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
9 db: &RootDatabase, 9 for file in files {
10 files: Vec<FileId>,
11 mut report_progress: impl FnMut(usize),
12) {
13 for (i, file) in files.into_iter().enumerate() {
14 let _ = crate::syntax_highlighting::highlight(db, file, None, false); 10 let _ = crate::syntax_highlighting::highlight(db, file, None, false);
15 report_progress(i);
16 } 11 }
17} 12}
diff --git a/crates/ra_progress/Cargo.toml b/crates/ra_progress/Cargo.toml
new file mode 100644
index 000000000..c7f7c6dd3
--- /dev/null
+++ b/crates/ra_progress/Cargo.toml
@@ -0,0 +1,8 @@
1[package]
2name = "ra_progress"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6
7[dependencies]
8crossbeam-channel = { version = "0.4" }
diff --git a/crates/ra_progress/src/lib.rs b/crates/ra_progress/src/lib.rs
new file mode 100644
index 000000000..0ff1f846c
--- /dev/null
+++ b/crates/ra_progress/src/lib.rs
@@ -0,0 +1,129 @@
1//! General-purpose instrumentation for progress reporting.
2//!
3//! Note:
4//! Most of the methods accept `&mut self` just to be more restrictive (for forward compat)
5//! even tho for some of them we can weaken this requirement to shared reference (`&self`).
6
7use crossbeam_channel::Receiver;
8use std::fmt;
9
10#[derive(Debug)]
11pub enum ProgressStatus<B, P> {
12 Begin(B),
13 Progress(P),
14 End,
15}
16
17pub struct Progress<B, P>(Option<crossbeam_channel::Sender<ProgressStatus<B, P>>>);
18impl<B, P> Progress<B, P> {
19 pub fn report(&mut self, payload: P) {
20 self.report_with(|| payload);
21 }
22
23 pub fn report_with(&mut self, payload: impl FnOnce() -> P) {
24 self.send_status(|| ProgressStatus::Progress(payload()));
25 }
26
27 fn send_status(&self, status: impl FnOnce() -> ProgressStatus<B, P>) {
28 if let Some(sender) = &self.0 {
29 sender.try_send(status()).expect("progress report must not block");
30 }
31 }
32}
33
34impl<B, P> Drop for Progress<B, P> {
35 fn drop(&mut self) {
36 self.send_status(|| ProgressStatus::End);
37 }
38}
39
40pub struct ProgressSource<B, P>(Option<crossbeam_channel::Sender<ProgressStatus<B, P>>>);
41impl<B, P> ProgressSource<B, P> {
42 pub fn real_if(real: bool) -> (Receiver<ProgressStatus<B, P>>, Self) {
43 if real {
44 let (sender, receiver) = crossbeam_channel::unbounded();
45 (receiver, Self(Some(sender)))
46 } else {
47 (crossbeam_channel::never(), Self(None))
48 }
49 }
50
51 pub fn begin(&mut self, payload: B) -> Progress<B, P> {
52 self.begin_with(|| payload)
53 }
54
55 pub fn begin_with(&mut self, payload: impl FnOnce() -> B) -> Progress<B, P> {
56 let progress = Progress(self.0.clone());
57 progress.send_status(|| ProgressStatus::Begin(payload()));
58 progress
59 }
60}
61
62impl<B, P> Clone for ProgressSource<B, P> {
63 fn clone(&self) -> Self {
64 Self(self.0.clone())
65 }
66}
67
68impl<B, P> fmt::Debug for ProgressSource<B, P> {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.debug_tuple("ProgressSource").field(&self.0).finish()
71 }
72}
73
74pub type U32ProgressStatus = ProgressStatus<U32ProgressReport, U32ProgressReport>;
75
76#[derive(Debug)]
77pub struct U32ProgressReport {
78 pub processed: u32,
79 pub total: u32,
80}
81impl U32ProgressReport {
82 pub fn percentage(&self) -> f64 {
83 f64::from(100 * self.processed) / f64::from(self.total)
84 }
85 pub fn to_message(&self, prefix: &str, unit: &str) -> String {
86 format!("{} ({}/{} {})", prefix, self.processed, self.total, unit)
87 }
88}
89
90pub struct U32Progress {
91 inner: Progress<U32ProgressReport, U32ProgressReport>,
92 processed: u32,
93 total: u32,
94}
95
96#[derive(Debug, Eq, PartialEq)]
97pub struct IsDone(pub bool);
98
99impl U32Progress {
100 pub fn report(&mut self, new_processed: u32) -> IsDone {
101 if self.processed < new_processed {
102 self.processed = new_processed;
103 self.inner.report(U32ProgressReport { processed: new_processed, total: self.total });
104 }
105 IsDone(self.processed >= self.total)
106 }
107}
108
109#[derive(Clone)]
110pub struct U32ProgressSource {
111 inner: ProgressSource<U32ProgressReport, U32ProgressReport>,
112}
113
114impl U32ProgressSource {
115 pub fn real_if(
116 real: bool,
117 ) -> (Receiver<ProgressStatus<U32ProgressReport, U32ProgressReport>>, Self) {
118 let (recv, inner) = ProgressSource::real_if(real);
119 (recv, Self { inner })
120 }
121
122 pub fn begin(&mut self, initial: u32, total: u32) -> U32Progress {
123 U32Progress {
124 inner: self.inner.begin(U32ProgressReport { processed: initial, total }),
125 processed: initial,
126 total,
127 }
128 }
129}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 458089e53..22f6b45dd 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -48,6 +48,7 @@ hir = { path = "../ra_hir", package = "ra_hir" }
48hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } 48hir_def = { path = "../ra_hir_def", package = "ra_hir_def" }
49hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } 49hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" }
50ra_proc_macro_srv = { path = "../ra_proc_macro_srv" } 50ra_proc_macro_srv = { path = "../ra_proc_macro_srv" }
51ra_progress = { path = "../ra_progress" }
51 52
52[target.'cfg(windows)'.dependencies] 53[target.'cfg(windows)'.dependencies]
53winapi = "0.3.8" 54winapi = "0.3.8"
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 1527c9947..2d854cecf 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -26,14 +26,19 @@ use crate::{
26 LspError, Result, 26 LspError, Result,
27}; 27};
28use ra_db::{CrateId, ExternSourceId}; 28use ra_db::{CrateId, ExternSourceId};
29use ra_progress::{ProgressSource, ProgressStatus};
29use rustc_hash::{FxHashMap, FxHashSet}; 30use rustc_hash::{FxHashMap, FxHashSet};
30 31
31fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { 32fn create_flycheck(
33 workspaces: &[ProjectWorkspace],
34 config: &FlycheckConfig,
35 progress_src: &ProgressSource<(), String>,
36) -> Option<Flycheck> {
32 // FIXME: Figure out the multi-workspace situation 37 // FIXME: Figure out the multi-workspace situation
33 workspaces.iter().find_map(|w| match w { 38 workspaces.iter().find_map(move |w| match w {
34 ProjectWorkspace::Cargo { cargo, .. } => { 39 ProjectWorkspace::Cargo { cargo, .. } => {
35 let cargo_project_root = cargo.workspace_root().to_path_buf(); 40 let cargo_project_root = cargo.workspace_root().to_path_buf();
36 Some(Flycheck::new(config.clone(), cargo_project_root)) 41 Some(Flycheck::new(config.clone(), cargo_project_root, progress_src.clone()))
37 } 42 }
38 ProjectWorkspace::Json { .. } => { 43 ProjectWorkspace::Json { .. } => {
39 log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); 44 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
@@ -59,6 +64,8 @@ pub struct GlobalState {
59 pub flycheck: Option<Flycheck>, 64 pub flycheck: Option<Flycheck>,
60 pub diagnostics: DiagnosticCollection, 65 pub diagnostics: DiagnosticCollection,
61 pub proc_macro_client: ProcMacroClient, 66 pub proc_macro_client: ProcMacroClient,
67 pub flycheck_progress_src: ProgressSource<(), String>,
68 pub flycheck_progress_receiver: Receiver<ProgressStatus<(), String>>,
62} 69}
63 70
64/// An immutable snapshot of the world's state at a point in time. 71/// An immutable snapshot of the world's state at a point in time.
@@ -158,7 +165,12 @@ impl GlobalState {
158 } 165 }
159 change.set_crate_graph(crate_graph); 166 change.set_crate_graph(crate_graph);
160 167
161 let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); 168 let (flycheck_progress_receiver, flycheck_progress_src) =
169 ProgressSource::real_if(config.client_caps.work_done_progress);
170 let flycheck = config
171 .check
172 .as_ref()
173 .and_then(|c| create_flycheck(&workspaces, c, &flycheck_progress_src));
162 174
163 let mut analysis_host = AnalysisHost::new(lru_capacity); 175 let mut analysis_host = AnalysisHost::new(lru_capacity);
164 analysis_host.apply_change(change); 176 analysis_host.apply_change(change);
@@ -171,6 +183,8 @@ impl GlobalState {
171 task_receiver, 183 task_receiver,
172 latest_requests: Default::default(), 184 latest_requests: Default::default(),
173 flycheck, 185 flycheck,
186 flycheck_progress_src,
187 flycheck_progress_receiver,
174 diagnostics: Default::default(), 188 diagnostics: Default::default(),
175 proc_macro_client, 189 proc_macro_client,
176 } 190 }
@@ -179,8 +193,10 @@ impl GlobalState {
179 pub fn update_configuration(&mut self, config: Config) { 193 pub fn update_configuration(&mut self, config: Config) {
180 self.analysis_host.update_lru_capacity(config.lru_capacity); 194 self.analysis_host.update_lru_capacity(config.lru_capacity);
181 if config.check != self.config.check { 195 if config.check != self.config.check {
182 self.flycheck = 196 self.flycheck = config
183 config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); 197 .check
198 .as_ref()
199 .and_then(|it| create_flycheck(&self.workspaces, it, &self.flycheck_progress_src));
184 } 200 }
185 201
186 self.config = config; 202 self.config = config;
@@ -188,7 +204,7 @@ impl GlobalState {
188 204
189 /// Returns a vec of libraries 205 /// Returns a vec of libraries
190 /// FIXME: better API here 206 /// FIXME: better API here
191 pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool { 207 pub fn process_changes(&mut self, roots_scanned: &mut u32) -> bool {
192 let changes = self.vfs.write().commit_changes(); 208 let changes = self.vfs.write().commit_changes();
193 if changes.is_empty() { 209 if changes.is_empty() {
194 return false; 210 return false;
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 590836c1e..740c52e21 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -4,11 +4,11 @@
4mod handlers; 4mod handlers;
5mod subscriptions; 5mod subscriptions;
6pub(crate) mod pending_requests; 6pub(crate) mod pending_requests;
7mod progress;
8mod lsp_utils; 7mod lsp_utils;
9 8
10use std::{ 9use std::{
11 borrow::Cow, 10 borrow::Cow,
11 convert::TryFrom,
12 env, 12 env,
13 error::Error, 13 error::Error,
14 fmt, 14 fmt,
@@ -20,12 +20,8 @@ use std::{
20 20
21use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; 21use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
22use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 22use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
23use lsp_types::{ 23use lsp_types::{DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent};
24 DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, 24use ra_flycheck::CheckTask;
25 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
26 WorkDoneProgressReport,
27};
28use ra_flycheck::{CheckTask, Status};
29use ra_ide::{Canceled, FileId, LineIndex}; 25use ra_ide::{Canceled, FileId, LineIndex};
30use ra_prof::profile; 26use ra_prof::profile;
31use ra_project_model::{PackageRoot, ProjectWorkspace}; 27use ra_project_model::{PackageRoot, ProjectWorkspace};
@@ -48,7 +44,12 @@ use crate::{
48}; 44};
49pub use lsp_utils::show_message; 45pub use lsp_utils::show_message;
50use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new}; 46use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new};
51use progress::{IsDone, PrimeCachesProgressNotifier, WorkspaceAnalysisProgressNotifier}; 47use ra_progress::{
48 IsDone, ProgressStatus, U32Progress, U32ProgressReport, U32ProgressSource, U32ProgressStatus,
49};
50
51const FLYCHECK_PROGRESS_TOKEN: &str = "rustAnalyzer/flycheck";
52const ROOTS_SCANNED_PROGRESS_TOKEN: &str = "rustAnalyzer/rootsScanned";
52 53
53#[derive(Debug)] 54#[derive(Debug)]
54pub struct LspError { 55pub struct LspError {
@@ -95,7 +96,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
95 } 96 }
96 97
97 let mut loop_state = LoopState::default(); 98 let mut loop_state = LoopState::default();
98
99 let mut global_state = { 99 let mut global_state = {
100 let workspaces = { 100 let workspaces = {
101 if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found { 101 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<()> {
169 GlobalState::new(workspaces, config.lru_capacity, &globs, config) 169 GlobalState::new(workspaces, config.lru_capacity, &globs, config)
170 }; 170 };
171 171
172 loop_state.roots_total = global_state.vfs.read().n_roots(); 172 loop_state.roots_total = u32::try_from(global_state.vfs.read().n_roots())
173 .expect("Wow, your project is so huge, that it cannot fit into u32...");
174
173 loop_state.roots_scanned = 0; 175 loop_state.roots_scanned = 0;
174 loop_state.roots_progress = Some(WorkspaceAnalysisProgressNotifier::begin( 176 let mut roots_scanned_progress_receiver = {
175 connection.sender.clone(), 177 let (recv, mut progress_src) =
176 loop_state.next_request_id(), 178 U32ProgressSource::real_if(global_state.config.client_caps.work_done_progress);
177 loop_state.roots_total, 179 loop_state.roots_progress = Some(progress_src.begin(0, loop_state.roots_total));
178 )); 180 recv
181 };
179 182
180 let pool = ThreadPool::default(); 183 let pool = ThreadPool::default();
181 let (task_sender, task_receiver) = unbounded::<Task>(); 184 let (task_sender, task_receiver) = unbounded::<Task>();
@@ -198,6 +201,18 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
198 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { 201 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
199 Ok(task) => Event::CheckWatcher(task), 202 Ok(task) => Event::CheckWatcher(task),
200 Err(RecvError) => return Err("check watcher died".into()), 203 Err(RecvError) => return Err("check watcher died".into()),
204 },
205 recv(global_state.flycheck_progress_receiver) -> status => match status {
206 Ok(status) => Event::ProgressReport(ProgressReport::Flycheck(status)),
207 Err(RecvError) => return Err("check watcher died".into()),
208 },
209 recv(roots_scanned_progress_receiver) -> status => match status {
210 Ok(status) => Event::ProgressReport(ProgressReport::RootsScanned(status)),
211 Err(RecvError) => {
212 // Roots analysis has finished, we no longer need this receiver
213 roots_scanned_progress_receiver = never();
214 continue;
215 }
201 } 216 }
202 }; 217 };
203 if let Event::Msg(Message::Request(req)) = &event { 218 if let Event::Msg(Message::Request(req)) = &event {
@@ -228,7 +243,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
228#[derive(Debug)] 243#[derive(Debug)]
229enum Task { 244enum Task {
230 Respond(Response), 245 Respond(Response),
231 Notify(Notification), 246 SendMessage(Message),
232 Diagnostic(DiagnosticTask), 247 Diagnostic(DiagnosticTask),
233} 248}
234 249
@@ -237,6 +252,13 @@ enum Event {
237 Task(Task), 252 Task(Task),
238 Vfs(VfsTask), 253 Vfs(VfsTask),
239 CheckWatcher(CheckTask), 254 CheckWatcher(CheckTask),
255 ProgressReport(ProgressReport),
256}
257
258#[derive(Debug)]
259enum ProgressReport {
260 Flycheck(ProgressStatus<(), String>),
261 RootsScanned(U32ProgressStatus),
240} 262}
241 263
242impl fmt::Debug for Event { 264impl fmt::Debug for Event {
@@ -253,7 +275,7 @@ impl fmt::Debug for Event {
253 return debug_verbose_not(not, f); 275 return debug_verbose_not(not, f);
254 } 276 }
255 } 277 }
256 Event::Task(Task::Notify(not)) => { 278 Event::Task(Task::SendMessage(Message::Notification(not))) => {
257 if notification_is::<lsp_types::notification::PublishDiagnostics>(not) { 279 if notification_is::<lsp_types::notification::PublishDiagnostics>(not) {
258 return debug_verbose_not(not, f); 280 return debug_verbose_not(not, f);
259 } 281 }
@@ -272,20 +294,21 @@ impl fmt::Debug for Event {
272 Event::Task(it) => fmt::Debug::fmt(it, f), 294 Event::Task(it) => fmt::Debug::fmt(it, f),
273 Event::Vfs(it) => fmt::Debug::fmt(it, f), 295 Event::Vfs(it) => fmt::Debug::fmt(it, f),
274 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f), 296 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
297 Event::ProgressReport(it) => fmt::Debug::fmt(it, f),
275 } 298 }
276 } 299 }
277} 300}
278 301
279#[derive(Debug, Default)] 302#[derive(Default)]
280struct LoopState { 303struct LoopState {
281 next_request_id: u64, 304 next_request_id: u64,
282 pending_responses: FxHashSet<RequestId>, 305 pending_responses: FxHashSet<RequestId>,
283 pending_requests: PendingRequests, 306 pending_requests: PendingRequests,
284 subscriptions: Subscriptions, 307 subscriptions: Subscriptions,
285 workspace_loaded: bool, 308 workspace_loaded: bool,
286 roots_progress: Option<WorkspaceAnalysisProgressNotifier>, 309 roots_progress: Option<U32Progress>,
287 roots_scanned: usize, 310 roots_scanned: u32,
288 roots_total: usize, 311 roots_total: u32,
289 configuration_request_id: Option<RequestId>, 312 configuration_request_id: Option<RequestId>,
290} 313}
291 314
@@ -326,6 +349,9 @@ fn loop_turn(
326 global_state.vfs.write().handle_task(task); 349 global_state.vfs.write().handle_task(task);
327 } 350 }
328 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, 351 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
352 Event::ProgressReport(report) => {
353 on_progress_report(report, task_sender, loop_state, global_state)
354 }
329 Event::Msg(msg) => match msg { 355 Event::Msg(msg) => match msg {
330 Message::Request(req) => on_request( 356 Message::Request(req) => on_request(
331 global_state, 357 global_state,
@@ -384,7 +410,13 @@ fn loop_turn(
384 } 410 }
385 411
386 if show_progress { 412 if show_progress {
387 send_workspace_analisys_progress(loop_state); 413 if let Some(progress) = &mut loop_state.roots_progress {
414 if loop_state.workspace_loaded
415 || progress.report(loop_state.roots_scanned) == IsDone(true)
416 {
417 loop_state.roots_progress = None;
418 }
419 }
388 } 420 }
389 421
390 if state_changed && loop_state.workspace_loaded { 422 if state_changed && loop_state.workspace_loaded {
@@ -397,22 +429,7 @@ fn loop_turn(
397 pool.execute({ 429 pool.execute({
398 let subs = loop_state.subscriptions.subscriptions(); 430 let subs = loop_state.subscriptions.subscriptions();
399 let snap = global_state.snapshot(); 431 let snap = global_state.snapshot();
400 432 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
401 let total = subs.len();
402
403 let mut progress = PrimeCachesProgressNotifier::begin(
404 connection.sender.clone(),
405 loop_state.next_request_id(),
406 total,
407 );
408
409 move || {
410 snap.analysis()
411 .prime_caches(subs, move |i| {
412 progress.report(i + 1);
413 })
414 .unwrap_or_else(|_: Canceled| ());
415 }
416 }); 433 });
417 } 434 }
418 435
@@ -431,6 +448,87 @@ fn loop_turn(
431 Ok(()) 448 Ok(())
432} 449}
433 450
451fn on_progress_report(
452 report: ProgressReport,
453 task_sender: &Sender<Task>,
454 loop_state: &mut LoopState,
455 global_state: &GlobalState,
456) {
457 let end_report =
458 || lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message: None });
459 let mut create_progress = |token: &'static str| {
460 let create_progress_req = request_new::<lsp_types::request::WorkDoneProgressCreate>(
461 loop_state.next_request_id(),
462 lsp_types::WorkDoneProgressCreateParams {
463 token: lsp_types::ProgressToken::String(token.to_string()),
464 },
465 );
466 task_sender.send(Task::SendMessage(create_progress_req.into())).unwrap();
467 };
468
469 let (token, progress) = match report {
470 ProgressReport::Flycheck(status) => {
471 let command = global_state
472 .config
473 .check
474 .as_ref()
475 .expect("There should be config, since flycheck is active");
476
477 let progress = match status {
478 ProgressStatus::Begin(()) => {
479 create_progress(FLYCHECK_PROGRESS_TOKEN);
480 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
481 title: "".to_string(),
482 cancellable: Some(false),
483 message: Some(command.to_string()),
484 percentage: None,
485 })
486 }
487 ProgressStatus::Progress(target) => {
488 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
489 cancellable: Some(false),
490 message: Some(format!("{} [{}]", command, target)),
491 percentage: None,
492 })
493 }
494 ProgressStatus::End => end_report(),
495 };
496 (FLYCHECK_PROGRESS_TOKEN, progress)
497 }
498 ProgressReport::RootsScanned(status) => {
499 fn to_message(report: &U32ProgressReport) -> String {
500 report.to_message("analyzing the workspace", "packages")
501 }
502 let progress = match status {
503 ProgressStatus::Begin(report) => {
504 create_progress(ROOTS_SCANNED_PROGRESS_TOKEN);
505 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
506 title: "rust-analyzer".to_string(),
507 cancellable: Some(false),
508 message: Some(to_message(&report)),
509 percentage: Some(report.percentage()),
510 })
511 }
512 ProgressStatus::Progress(report) => {
513 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
514 cancellable: Some(false),
515 message: Some(to_message(&report)),
516 percentage: Some(report.percentage()),
517 })
518 }
519 ProgressStatus::End => end_report(),
520 };
521 (ROOTS_SCANNED_PROGRESS_TOKEN, progress)
522 }
523 };
524 let params = lsp_types::ProgressParams {
525 token: lsp_types::ProgressToken::String(token.to_string()),
526 value: lsp_types::ProgressParamsValue::WorkDone(progress),
527 };
528 let not = notification_new::<lsp_types::notification::Progress>(params);
529 task_sender.send(Task::SendMessage(not.into())).unwrap()
530}
531
434fn on_task( 532fn on_task(
435 task: Task, 533 task: Task,
436 msg_sender: &Sender<Message>, 534 msg_sender: &Sender<Message>,
@@ -445,9 +543,7 @@ fn on_task(
445 msg_sender.send(response.into()).unwrap(); 543 msg_sender.send(response.into()).unwrap();
446 } 544 }
447 } 545 }
448 Task::Notify(n) => { 546 Task::SendMessage(msg) => msg_sender.send(msg).unwrap(),
449 msg_sender.send(n.into()).unwrap();
450 }
451 Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state), 547 Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state),
452 } 548 }
453} 549}
@@ -718,42 +814,6 @@ fn on_check_task(
718 )))?; 814 )))?;
719 } 815 }
720 } 816 }
721
722 CheckTask::Status(status) => {
723 if global_state.config.client_caps.work_done_progress {
724 let progress = match status {
725 Status::Being => {
726 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
727 title: "Running `cargo check`".to_string(),
728 cancellable: Some(false),
729 message: None,
730 percentage: None,
731 })
732 }
733 Status::Progress(target) => {
734 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
735 cancellable: Some(false),
736 message: Some(target),
737 percentage: None,
738 })
739 }
740 Status::End => {
741 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd {
742 message: None,
743 })
744 }
745 };
746
747 let params = lsp_types::ProgressParams {
748 token: lsp_types::ProgressToken::String(
749 "rustAnalyzer/cargoWatcher".to_string(),
750 ),
751 value: lsp_types::ProgressParamsValue::WorkDone(progress),
752 };
753 let not = notification_new::<lsp_types::notification::Progress>(params);
754 task_sender.send(Task::Notify(not)).unwrap();
755 }
756 }
757 }; 817 };
758 818
759 Ok(()) 819 Ok(())
@@ -771,15 +831,6 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
771 } 831 }
772} 832}
773 833
774fn send_workspace_analisys_progress(loop_state: &mut LoopState) {
775 if let Some(progress) = &mut loop_state.roots_progress {
776 if loop_state.workspace_loaded || progress.report(loop_state.roots_scanned) == IsDone(true)
777 {
778 loop_state.roots_progress = None;
779 }
780 }
781}
782
783struct PoolDispatcher<'a> { 834struct PoolDispatcher<'a> {
784 req: Option<Request>, 835 req: Option<Request>,
785 pool: &'a ThreadPool, 836 pool: &'a ThreadPool,
diff --git a/crates/rust-analyzer/src/main_loop/lsp_utils.rs b/crates/rust-analyzer/src/main_loop/lsp_utils.rs
index fc008cba5..c79022797 100644
--- a/crates/rust-analyzer/src/main_loop/lsp_utils.rs
+++ b/crates/rust-analyzer/src/main_loop/lsp_utils.rs
@@ -1,10 +1,16 @@
1//! Utilities for LSP-related boilerplate code.
2
1use crossbeam_channel::Sender; 3use crossbeam_channel::Sender;
2use lsp_server::{Message, Notification, Request, RequestId}; 4use lsp_server::{Message, Notification, Request, RequestId};
3use ra_db::Canceled; 5use ra_db::Canceled;
4use serde::{de::DeserializeOwned, Serialize}; 6use serde::{de::DeserializeOwned, Serialize};
5use std::error::Error; 7use std::error::Error;
6 8
7pub fn show_message(typ: lsp_types::MessageType, message: impl Into<String>, sender: &Sender<Message>) { 9pub fn show_message(
10 typ: lsp_types::MessageType,
11 message: impl Into<String>,
12 sender: &Sender<Message>,
13) {
8 let message = message.into(); 14 let message = message.into();
9 let params = lsp_types::ShowMessageParams { typ, message }; 15 let params = lsp_types::ShowMessageParams { typ, message };
10 let not = notification_new::<lsp_types::notification::ShowMessage>(params); 16 let not = notification_new::<lsp_types::notification::ShowMessage>(params);
diff --git a/crates/rust-analyzer/src/main_loop/progress.rs b/crates/rust-analyzer/src/main_loop/progress.rs
deleted file mode 100644
index 610e026ca..000000000
--- a/crates/rust-analyzer/src/main_loop/progress.rs
+++ /dev/null
@@ -1,129 +0,0 @@
1use super::lsp_utils::{notification_new, request_new};
2use crossbeam_channel::Sender;
3use lsp_server::{Message, RequestId};
4use lsp_types::{
5 WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
6 WorkDoneProgressReport,
7};
8
9const PRIME_CACHES_PROGRESS_TOKEN: &str = "rustAnalyzer/primeCaches";
10const WORKSPACE_ANALYSIS_PROGRESS_TOKEN: &str = "rustAnalyzer/workspaceAnalysis";
11
12#[derive(Debug)]
13pub(crate) struct PrimeCachesProgressNotifier(ProgressNotifier);
14
15impl Drop for PrimeCachesProgressNotifier {
16 fn drop(&mut self) {
17 self.0.end("done priming caches".to_owned());
18 }
19}
20
21impl PrimeCachesProgressNotifier {
22 pub(crate) fn begin(sender: Sender<Message>, req_id: RequestId, total: usize) -> Self {
23 let me = Self(ProgressNotifier {
24 sender,
25 processed: 0,
26 total,
27 token: PRIME_CACHES_PROGRESS_TOKEN,
28 label: "priming caches",
29 });
30 me.0.begin(req_id);
31 me
32 }
33
34 pub(crate) fn report(&mut self, processed: usize) -> IsDone {
35 self.0.report(processed)
36 }
37}
38
39#[derive(Debug)]
40pub(crate) struct WorkspaceAnalysisProgressNotifier(ProgressNotifier);
41
42impl Drop for WorkspaceAnalysisProgressNotifier {
43 fn drop(&mut self) {
44 self.0.end("done analyzing workspace".to_owned());
45 }
46}
47
48impl WorkspaceAnalysisProgressNotifier {
49 pub(crate) fn begin(sender: Sender<Message>, req_id: RequestId, total: usize) -> Self {
50 let me = Self(ProgressNotifier {
51 sender,
52 total,
53 processed: 0,
54 token: WORKSPACE_ANALYSIS_PROGRESS_TOKEN,
55 label: "analyzing packages",
56 });
57 me.0.begin(req_id);
58 me
59 }
60
61 pub(crate) fn report(&mut self, processed: usize) -> IsDone {
62 self.0.report(processed)
63 }
64}
65
66#[derive(Debug, PartialEq, Eq)]
67pub struct IsDone(pub bool);
68
69#[derive(Debug)]
70struct ProgressNotifier {
71 sender: Sender<Message>,
72 token: &'static str,
73 label: &'static str,
74 processed: usize,
75 total: usize,
76}
77
78impl ProgressNotifier {
79 fn begin(&self, req_id: RequestId) {
80 let create_req = request_new::<lsp_types::request::WorkDoneProgressCreate>(
81 req_id,
82 WorkDoneProgressCreateParams {
83 token: lsp_types::ProgressToken::String(self.token.to_owned()),
84 },
85 );
86 self.sender.send(create_req.into()).unwrap();
87 self.send_notification(WorkDoneProgress::Begin(WorkDoneProgressBegin {
88 cancellable: None,
89 title: "rust-analyzer".to_owned(),
90 percentage: Some(self.percentage()),
91 message: Some(self.create_progress_message()),
92 }));
93 }
94
95 fn report(&mut self, processed: usize) -> IsDone {
96 if self.processed != processed {
97 self.processed = processed;
98
99 self.send_notification(WorkDoneProgress::Report(WorkDoneProgressReport {
100 cancellable: None,
101 percentage: Some(self.percentage()),
102 message: Some(self.create_progress_message()),
103 }));
104 }
105 IsDone(processed >= self.total)
106 }
107
108 fn end(&mut self, message: String) {
109 self.send_notification(WorkDoneProgress::End(WorkDoneProgressEnd {
110 message: Some(message),
111 }));
112 }
113
114 fn send_notification(&self, progress: WorkDoneProgress) {
115 let notif = notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
116 token: lsp_types::ProgressToken::String(self.token.to_owned()),
117 value: lsp_types::ProgressParamsValue::WorkDone(progress),
118 });
119 self.sender.send(notif.into()).unwrap();
120 }
121
122 fn create_progress_message(&self) -> String {
123 format!("{} ({}/{})", self.label, self.processed, self.total)
124 }
125
126 fn percentage(&self) -> f64 {
127 (100 * self.processed) as f64 / self.total as f64
128 }
129}
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index e5f69835a..c68cdf862 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -212,7 +212,7 @@ impl Server {
212 ProgressParams { 212 ProgressParams {
213 token: lsp_types::ProgressToken::String(ref token), 213 token: lsp_types::ProgressToken::String(ref token),
214 value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)), 214 value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)),
215 } if token == "rustAnalyzer/workspaceAnalysis" => true, 215 } if token == "rustAnalyzer/rootsScanned" => true,
216 _ => false, 216 _ => false,
217 } 217 }
218 } 218 }