diff options
author | Aleksey Kladov <[email protected]> | 2020-06-11 10:04:09 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-06-23 16:51:06 +0100 |
commit | dad1333b48c38bc7a5628fc0ff5304d003776a85 (patch) | |
tree | 29be52a980b4cae72f46a48c48135a15e31641e0 /crates/rust-analyzer/src/main_loop.rs | |
parent | 7aa66371ee3e8b31217513204c8b4f683584419d (diff) |
New VFS
Diffstat (limited to 'crates/rust-analyzer/src/main_loop.rs')
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 330 |
1 files changed, 166 insertions, 164 deletions
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 674b1323b..b9d296856 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -2,11 +2,9 @@ | |||
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | 3 | ||
4 | mod handlers; | 4 | mod handlers; |
5 | mod subscriptions; | ||
6 | pub(crate) mod request_metrics; | 5 | pub(crate) mod request_metrics; |
7 | 6 | ||
8 | use std::{ | 7 | use std::{ |
9 | borrow::Cow, | ||
10 | env, | 8 | env, |
11 | error::Error, | 9 | error::Error, |
12 | fmt, | 10 | fmt, |
@@ -20,16 +18,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | |||
20 | use lsp_server::{ | 18 | use lsp_server::{ |
21 | Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, | 19 | Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, |
22 | }; | 20 | }; |
23 | use lsp_types::{ | 21 | use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent}; |
24 | request::Request as _, DidChangeTextDocumentParams, NumberOrString, | 22 | use ra_flycheck::CheckTask; |
25 | TextDocumentContentChangeEvent, WorkDoneProgress, WorkDoneProgressBegin, | ||
26 | WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, | ||
27 | }; | ||
28 | use ra_flycheck::{CheckTask, Status}; | ||
29 | use ra_ide::{Canceled, FileId, LineIndex}; | 23 | use ra_ide::{Canceled, FileId, LineIndex}; |
30 | use ra_prof::profile; | 24 | use ra_prof::profile; |
31 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 25 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
32 | use ra_vfs::VfsTask; | 26 | use rustc_hash::FxHashSet; |
33 | use serde::{de::DeserializeOwned, Serialize}; | 27 | use serde::{de::DeserializeOwned, Serialize}; |
34 | use threadpool::ThreadPool; | 28 | use threadpool::ThreadPool; |
35 | 29 | ||
@@ -39,9 +33,10 @@ use crate::{ | |||
39 | from_proto, | 33 | from_proto, |
40 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, | 34 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, |
41 | lsp_ext, | 35 | lsp_ext, |
42 | main_loop::{request_metrics::RequestMetrics, subscriptions::Subscriptions}, | 36 | main_loop::request_metrics::RequestMetrics, |
43 | Result, | 37 | Result, |
44 | }; | 38 | }; |
39 | use ra_db::VfsPath; | ||
45 | 40 | ||
46 | #[derive(Debug)] | 41 | #[derive(Debug)] |
47 | pub struct LspError { | 42 | pub struct LspError { |
@@ -128,13 +123,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
128 | .collect::<Vec<_>>() | 123 | .collect::<Vec<_>>() |
129 | }; | 124 | }; |
130 | 125 | ||
131 | let globs = config | ||
132 | .files | ||
133 | .exclude | ||
134 | .iter() | ||
135 | .map(|glob| crate::vfs_glob::Glob::new(glob)) | ||
136 | .collect::<std::result::Result<Vec<_>, _>>()?; | ||
137 | |||
138 | if let FilesWatcher::Client = config.files.watcher { | 126 | if let FilesWatcher::Client = config.files.watcher { |
139 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | 127 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { |
140 | watchers: workspaces | 128 | watchers: workspaces |
@@ -159,11 +147,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
159 | connection.sender.send(request.into()).unwrap(); | 147 | connection.sender.send(request.into()).unwrap(); |
160 | } | 148 | } |
161 | 149 | ||
162 | GlobalState::new(workspaces, config.lru_capacity, &globs, config) | 150 | GlobalState::new(workspaces, config.lru_capacity, config) |
163 | }; | 151 | }; |
164 | 152 | ||
165 | loop_state.roots_total = global_state.vfs.read().n_roots(); | ||
166 | |||
167 | let pool = ThreadPool::default(); | 153 | let pool = ThreadPool::default(); |
168 | let (task_sender, task_receiver) = unbounded::<Task>(); | 154 | let (task_sender, task_receiver) = unbounded::<Task>(); |
169 | 155 | ||
@@ -192,7 +178,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
192 | break; | 178 | break; |
193 | }; | 179 | }; |
194 | } | 180 | } |
181 | assert!(!global_state.vfs.read().0.has_changes()); | ||
195 | loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?; | 182 | loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?; |
183 | assert!(!global_state.vfs.read().0.has_changes()); | ||
196 | } | 184 | } |
197 | } | 185 | } |
198 | global_state.analysis_host.request_cancellation(); | 186 | global_state.analysis_host.request_cancellation(); |
@@ -222,7 +210,7 @@ enum Task { | |||
222 | enum Event { | 210 | enum Event { |
223 | Msg(Message), | 211 | Msg(Message), |
224 | Task(Task), | 212 | Task(Task), |
225 | Vfs(VfsTask), | 213 | Vfs(vfs::loader::Message), |
226 | CheckWatcher(CheckTask), | 214 | CheckWatcher(CheckTask), |
227 | } | 215 | } |
228 | 216 | ||
@@ -270,11 +258,20 @@ type Incoming = lsp_server::Incoming<(&'static str, Instant)>; | |||
270 | #[derive(Default)] | 258 | #[derive(Default)] |
271 | struct LoopState { | 259 | struct LoopState { |
272 | req_queue: ReqQueue<(&'static str, Instant), ReqHandler>, | 260 | req_queue: ReqQueue<(&'static str, Instant), ReqHandler>, |
273 | subscriptions: Subscriptions, | 261 | mem_docs: FxHashSet<VfsPath>, |
274 | workspace_loaded: bool, | 262 | status: Status, |
275 | roots_progress_reported: Option<usize>, | 263 | } |
276 | roots_scanned: usize, | 264 | |
277 | roots_total: usize, | 265 | #[derive(Eq, PartialEq)] |
266 | enum Status { | ||
267 | Loading, | ||
268 | Ready, | ||
269 | } | ||
270 | |||
271 | impl Default for Status { | ||
272 | fn default() -> Self { | ||
273 | Status::Loading | ||
274 | } | ||
278 | } | 275 | } |
279 | 276 | ||
280 | fn loop_turn( | 277 | fn loop_turn( |
@@ -295,14 +292,36 @@ fn loop_turn( | |||
295 | log::info!("queued count = {}", queue_count); | 292 | log::info!("queued count = {}", queue_count); |
296 | } | 293 | } |
297 | 294 | ||
295 | let mut became_ready = false; | ||
298 | match event { | 296 | match event { |
299 | Event::Task(task) => { | 297 | Event::Task(task) => { |
300 | on_task(task, &connection.sender, &mut loop_state.req_queue.incoming, global_state); | 298 | on_task(task, &connection.sender, &mut loop_state.req_queue.incoming, global_state); |
301 | global_state.maybe_collect_garbage(); | 299 | global_state.maybe_collect_garbage(); |
302 | } | 300 | } |
303 | Event::Vfs(task) => { | 301 | Event::Vfs(task) => match task { |
304 | global_state.vfs.write().handle_task(task); | 302 | vfs::loader::Message::Loaded { files } => { |
305 | } | 303 | let vfs = &mut global_state.vfs.write().0; |
304 | for (path, contents) in files { | ||
305 | let path = VfsPath::from(path); | ||
306 | if !loop_state.mem_docs.contains(&path) { | ||
307 | vfs.set_file_contents(path, contents) | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | vfs::loader::Message::Progress { n_entries_total, n_entries_done } => { | ||
312 | if n_entries_done == n_entries_done { | ||
313 | loop_state.status = Status::Ready; | ||
314 | became_ready = true; | ||
315 | } | ||
316 | report_progress( | ||
317 | loop_state, | ||
318 | &connection.sender, | ||
319 | n_entries_done, | ||
320 | n_entries_total, | ||
321 | "roots scanned", | ||
322 | ) | ||
323 | } | ||
324 | }, | ||
306 | Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, | 325 | Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, |
307 | Event::Msg(msg) => match msg { | 326 | Event::Msg(msg) => match msg { |
308 | Message::Request(req) => on_request( | 327 | Message::Request(req) => on_request( |
@@ -324,32 +343,29 @@ fn loop_turn( | |||
324 | }, | 343 | }, |
325 | }; | 344 | }; |
326 | 345 | ||
327 | let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned); | 346 | let state_changed = global_state.process_changes(); |
328 | 347 | ||
329 | let show_progress = | 348 | if became_ready { |
330 | !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; | ||
331 | |||
332 | if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total { | ||
333 | state_changed = true; | ||
334 | loop_state.workspace_loaded = true; | ||
335 | if let Some(flycheck) = &global_state.flycheck { | 349 | if let Some(flycheck) = &global_state.flycheck { |
336 | flycheck.update(); | 350 | flycheck.update(); |
337 | } | 351 | } |
338 | } | 352 | } |
339 | 353 | ||
340 | if show_progress { | 354 | if loop_state.status == Status::Ready && (state_changed || became_ready) { |
341 | send_startup_progress(&connection.sender, loop_state); | 355 | let subscriptions = loop_state |
342 | } | 356 | .mem_docs |
357 | .iter() | ||
358 | .map(|path| global_state.vfs.read().0.file_id(&path).unwrap()) | ||
359 | .collect::<Vec<_>>(); | ||
343 | 360 | ||
344 | if state_changed && loop_state.workspace_loaded { | ||
345 | update_file_notifications_on_threadpool( | 361 | update_file_notifications_on_threadpool( |
346 | pool, | 362 | pool, |
347 | global_state.snapshot(), | 363 | global_state.snapshot(), |
348 | task_sender.clone(), | 364 | task_sender.clone(), |
349 | loop_state.subscriptions.subscriptions(), | 365 | subscriptions.clone(), |
350 | ); | 366 | ); |
351 | pool.execute({ | 367 | pool.execute({ |
352 | let subs = loop_state.subscriptions.subscriptions(); | 368 | let subs = subscriptions; |
353 | let snap = global_state.snapshot(); | 369 | let snap = global_state.snapshot(); |
354 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) | 370 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) |
355 | }); | 371 | }); |
@@ -465,7 +481,7 @@ fn on_request( | |||
465 | 481 | ||
466 | fn on_notification( | 482 | fn on_notification( |
467 | msg_sender: &Sender<Message>, | 483 | msg_sender: &Sender<Message>, |
468 | state: &mut GlobalState, | 484 | global_state: &mut GlobalState, |
469 | loop_state: &mut LoopState, | 485 | loop_state: &mut LoopState, |
470 | not: Notification, | 486 | not: Notification, |
471 | ) -> Result<()> { | 487 | ) -> Result<()> { |
@@ -484,12 +500,15 @@ fn on_notification( | |||
484 | }; | 500 | }; |
485 | let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) { | 501 | let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) { |
486 | Ok(params) => { | 502 | Ok(params) => { |
487 | let uri = params.text_document.uri; | 503 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
488 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 504 | if !loop_state.mem_docs.insert(path.clone()) { |
489 | if let Some(file_id) = | 505 | log::error!("duplicate DidOpenTextDocument: {}", path) |
490 | state.vfs.write().add_file_overlay(&path, params.text_document.text) | 506 | } |
491 | { | 507 | global_state |
492 | loop_state.subscriptions.add_sub(FileId(file_id.0)); | 508 | .vfs |
509 | .write() | ||
510 | .0 | ||
511 | .set_file_contents(path, Some(params.text_document.text.into_bytes())); | ||
493 | } | 512 | } |
494 | return Ok(()); | 513 | return Ok(()); |
495 | } | 514 | } |
@@ -497,23 +516,13 @@ fn on_notification( | |||
497 | }; | 516 | }; |
498 | let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) { | 517 | let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) { |
499 | Ok(params) => { | 518 | Ok(params) => { |
500 | let DidChangeTextDocumentParams { text_document, content_changes } = params; | 519 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
501 | let world = state.snapshot(); | 520 | assert!(loop_state.mem_docs.contains(&path)); |
502 | let file_id = from_proto::file_id(&world, &text_document.uri)?; | 521 | let vfs = &mut global_state.vfs.write().0; |
503 | let line_index = world.analysis().file_line_index(file_id)?; | 522 | let file_id = vfs.file_id(&path).unwrap(); |
504 | let uri = text_document.uri; | 523 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); |
505 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 524 | apply_document_changes(&mut text, params.content_changes); |
506 | state.vfs.write().change_file_overlay(&path, |old_text| { | 525 | vfs.set_file_contents(path, Some(text.into_bytes())) |
507 | apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes); | ||
508 | }); | ||
509 | return Ok(()); | ||
510 | } | ||
511 | Err(not) => not, | ||
512 | }; | ||
513 | let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) { | ||
514 | Ok(_params) => { | ||
515 | if let Some(flycheck) = &state.flycheck { | ||
516 | flycheck.update(); | ||
517 | } | 526 | } |
518 | return Ok(()); | 527 | return Ok(()); |
519 | } | 528 | } |
@@ -521,19 +530,34 @@ fn on_notification( | |||
521 | }; | 530 | }; |
522 | let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) { | 531 | let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) { |
523 | Ok(params) => { | 532 | Ok(params) => { |
524 | let uri = params.text_document.uri; | 533 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
525 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 534 | if !loop_state.mem_docs.remove(&path) { |
526 | if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { | 535 | log::error!("orphan DidCloseTextDocument: {}", path) |
527 | loop_state.subscriptions.remove_sub(FileId(file_id.0)); | 536 | } |
537 | if let Some(path) = path.as_path() { | ||
538 | global_state.loader.invalidate(path.to_path_buf()); | ||
539 | } | ||
528 | } | 540 | } |
529 | let params = | 541 | let params = lsp_types::PublishDiagnosticsParams { |
530 | lsp_types::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; | 542 | uri: params.text_document.uri, |
543 | diagnostics: Vec::new(), | ||
544 | version: None, | ||
545 | }; | ||
531 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 546 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
532 | msg_sender.send(not.into()).unwrap(); | 547 | msg_sender.send(not.into()).unwrap(); |
533 | return Ok(()); | 548 | return Ok(()); |
534 | } | 549 | } |
535 | Err(not) => not, | 550 | Err(not) => not, |
536 | }; | 551 | }; |
552 | let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) { | ||
553 | Ok(_params) => { | ||
554 | if let Some(flycheck) = &global_state.flycheck { | ||
555 | flycheck.update(); | ||
556 | } | ||
557 | return Ok(()); | ||
558 | } | ||
559 | Err(not) => not, | ||
560 | }; | ||
537 | let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) { | 561 | let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) { |
538 | Ok(_) => { | 562 | Ok(_) => { |
539 | // As stated in https://github.com/microsoft/language-server-protocol/issues/676, | 563 | // As stated in https://github.com/microsoft/language-server-protocol/issues/676, |
@@ -575,11 +599,10 @@ fn on_notification( | |||
575 | }; | 599 | }; |
576 | let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) { | 600 | let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) { |
577 | Ok(params) => { | 601 | Ok(params) => { |
578 | let mut vfs = state.vfs.write(); | ||
579 | for change in params.changes { | 602 | for change in params.changes { |
580 | let uri = change.uri; | 603 | if let Ok(path) = from_proto::abs_path(&change.uri) { |
581 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 604 | global_state.loader.invalidate(path) |
582 | vfs.notify_changed(path) | 605 | } |
583 | } | 606 | } |
584 | return Ok(()); | 607 | return Ok(()); |
585 | } | 608 | } |
@@ -594,9 +617,9 @@ fn on_notification( | |||
594 | 617 | ||
595 | fn apply_document_changes( | 618 | fn apply_document_changes( |
596 | old_text: &mut String, | 619 | old_text: &mut String, |
597 | mut line_index: Cow<'_, LineIndex>, | ||
598 | content_changes: Vec<TextDocumentContentChangeEvent>, | 620 | content_changes: Vec<TextDocumentContentChangeEvent>, |
599 | ) { | 621 | ) { |
622 | let mut line_index = LineIndex::new(old_text); | ||
600 | // The changes we got must be applied sequentially, but can cross lines so we | 623 | // The changes we got must be applied sequentially, but can cross lines so we |
601 | // have to keep our line index updated. | 624 | // have to keep our line index updated. |
602 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | 625 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we |
@@ -621,7 +644,7 @@ fn apply_document_changes( | |||
621 | match change.range { | 644 | match change.range { |
622 | Some(range) => { | 645 | Some(range) => { |
623 | if !index_valid.covers(range.end.line) { | 646 | if !index_valid.covers(range.end.line) { |
624 | line_index = Cow::Owned(LineIndex::new(&old_text)); | 647 | line_index = LineIndex::new(&old_text); |
625 | } | 648 | } |
626 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | 649 | index_valid = IndexValid::UpToLineExclusive(range.start.line); |
627 | let range = from_proto::text_range(&line_index, range); | 650 | let range = from_proto::text_range(&line_index, range); |
@@ -652,18 +675,11 @@ fn on_check_task( | |||
652 | &workspace_root, | 675 | &workspace_root, |
653 | ); | 676 | ); |
654 | for diag in diagnostics { | 677 | for diag in diagnostics { |
655 | let path = diag | 678 | let path = from_proto::vfs_path(&diag.location.uri)?; |
656 | .location | 679 | let file_id = match global_state.vfs.read().0.file_id(&path) { |
657 | .uri | ||
658 | .to_file_path() | ||
659 | .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; | ||
660 | let file_id = match global_state.vfs.read().path2file(&path) { | ||
661 | Some(file) => FileId(file.0), | 680 | Some(file) => FileId(file.0), |
662 | None => { | 681 | None => { |
663 | log::error!( | 682 | log::error!("File with cargo diagnostic not found in VFS: {}", path); |
664 | "File with cargo diagnostic not found in VFS: {}", | ||
665 | path.display() | ||
666 | ); | ||
667 | return Ok(()); | 683 | return Ok(()); |
668 | } | 684 | } |
669 | }; | 685 | }; |
@@ -679,7 +695,7 @@ fn on_check_task( | |||
679 | CheckTask::Status(status) => { | 695 | CheckTask::Status(status) => { |
680 | if global_state.config.client_caps.work_done_progress { | 696 | if global_state.config.client_caps.work_done_progress { |
681 | let progress = match status { | 697 | let progress = match status { |
682 | Status::Being => { | 698 | ra_flycheck::Status::Being => { |
683 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { | 699 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { |
684 | title: "Running `cargo check`".to_string(), | 700 | title: "Running `cargo check`".to_string(), |
685 | cancellable: Some(false), | 701 | cancellable: Some(false), |
@@ -687,14 +703,14 @@ fn on_check_task( | |||
687 | percentage: None, | 703 | percentage: None, |
688 | }) | 704 | }) |
689 | } | 705 | } |
690 | Status::Progress(target) => { | 706 | ra_flycheck::Status::Progress(target) => { |
691 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { | 707 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { |
692 | cancellable: Some(false), | 708 | cancellable: Some(false), |
693 | message: Some(target), | 709 | message: Some(target), |
694 | percentage: None, | 710 | percentage: None, |
695 | }) | 711 | }) |
696 | } | 712 | } |
697 | Status::End => { | 713 | ra_flycheck::Status::End => { |
698 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { | 714 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { |
699 | message: None, | 715 | message: None, |
700 | }) | 716 | }) |
@@ -720,7 +736,7 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
720 | let subscriptions = state.diagnostics.handle_task(task); | 736 | let subscriptions = state.diagnostics.handle_task(task); |
721 | 737 | ||
722 | for file_id in subscriptions { | 738 | for file_id in subscriptions { |
723 | let url = file_id_to_url(&state.vfs.read(), file_id); | 739 | let url = file_id_to_url(&state.vfs.read().0, file_id); |
724 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | 740 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); |
725 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; | 741 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; |
726 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 742 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
@@ -728,57 +744,46 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
728 | } | 744 | } |
729 | } | 745 | } |
730 | 746 | ||
731 | fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) { | 747 | fn report_progress( |
732 | let total: usize = loop_state.roots_total; | 748 | loop_state: &mut LoopState, |
733 | let prev = loop_state.roots_progress_reported; | 749 | sender: &Sender<Message>, |
734 | let progress = loop_state.roots_scanned; | 750 | done: usize, |
735 | loop_state.roots_progress_reported = Some(progress); | 751 | total: usize, |
736 | 752 | message: &str, | |
737 | match (prev, loop_state.workspace_loaded) { | 753 | ) { |
738 | (None, false) => { | 754 | let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", message)); |
739 | let request = loop_state.req_queue.outgoing.register( | 755 | let message = Some(format!("{}/{} {}", done, total, message)); |
740 | lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(), | 756 | let percentage = Some(100.0 * done as f64 / total.max(1) as f64); |
741 | WorkDoneProgressCreateParams { | 757 | let work_done_progress = if done == 0 { |
742 | token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()), | 758 | let work_done_progress_create = loop_state.req_queue.outgoing.register( |
743 | }, | 759 | lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(), |
744 | DO_NOTHING, | 760 | lsp_types::WorkDoneProgressCreateParams { token: token.clone() }, |
745 | ); | 761 | DO_NOTHING, |
746 | sender.send(request.into()).unwrap(); | 762 | ); |
747 | send_startup_progress_notif( | 763 | sender.send(work_done_progress_create.into()).unwrap(); |
748 | sender, | ||
749 | WorkDoneProgress::Begin(WorkDoneProgressBegin { | ||
750 | title: "rust-analyzer".into(), | ||
751 | cancellable: None, | ||
752 | message: Some(format!("{}/{} packages", progress, total)), | ||
753 | percentage: Some(100.0 * progress as f64 / total as f64), | ||
754 | }), | ||
755 | ); | ||
756 | } | ||
757 | (Some(prev), false) if progress != prev => send_startup_progress_notif( | ||
758 | sender, | ||
759 | WorkDoneProgress::Report(WorkDoneProgressReport { | ||
760 | cancellable: None, | ||
761 | message: Some(format!("{}/{} packages", progress, total)), | ||
762 | percentage: Some(100.0 * progress as f64 / total as f64), | ||
763 | }), | ||
764 | ), | ||
765 | (_, true) => send_startup_progress_notif( | ||
766 | sender, | ||
767 | WorkDoneProgress::End(WorkDoneProgressEnd { | ||
768 | message: Some(format!("rust-analyzer loaded, {} packages", progress)), | ||
769 | }), | ||
770 | ), | ||
771 | _ => {} | ||
772 | } | ||
773 | 764 | ||
774 | fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { | 765 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { |
775 | let notif = | 766 | title: "rust-analyzer".into(), |
776 | notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams { | 767 | cancellable: None, |
777 | token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()), | 768 | message, |
778 | value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), | 769 | percentage, |
779 | }); | 770 | }) |
780 | sender.send(notif.into()).unwrap(); | 771 | } else if done < total { |
781 | } | 772 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { |
773 | cancellable: None, | ||
774 | message, | ||
775 | percentage, | ||
776 | }) | ||
777 | } else { | ||
778 | assert!(done == total); | ||
779 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message }) | ||
780 | }; | ||
781 | let notification = | ||
782 | notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams { | ||
783 | token, | ||
784 | value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), | ||
785 | }); | ||
786 | sender.send(notification.into()).unwrap(); | ||
782 | } | 787 | } |
783 | 788 | ||
784 | struct PoolDispatcher<'a> { | 789 | struct PoolDispatcher<'a> { |
@@ -976,18 +981,12 @@ where | |||
976 | 981 | ||
977 | #[cfg(test)] | 982 | #[cfg(test)] |
978 | mod tests { | 983 | mod tests { |
979 | use std::borrow::Cow; | ||
980 | |||
981 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; | 984 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; |
982 | use ra_ide::LineIndex; | ||
983 | 985 | ||
984 | #[test] | 986 | use super::*; |
985 | fn apply_document_changes() { | ||
986 | fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) { | ||
987 | let line_index = Cow::Owned(LineIndex::new(&text)); | ||
988 | super::apply_document_changes(text, line_index, changes); | ||
989 | } | ||
990 | 987 | ||
988 | #[test] | ||
989 | fn test_apply_document_changes() { | ||
991 | macro_rules! c { | 990 | macro_rules! c { |
992 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { | 991 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { |
993 | vec![$(TextDocumentContentChangeEvent { | 992 | vec![$(TextDocumentContentChangeEvent { |
@@ -1002,9 +1001,9 @@ mod tests { | |||
1002 | } | 1001 | } |
1003 | 1002 | ||
1004 | let mut text = String::new(); | 1003 | let mut text = String::new(); |
1005 | run(&mut text, vec![]); | 1004 | apply_document_changes(&mut text, vec![]); |
1006 | assert_eq!(text, ""); | 1005 | assert_eq!(text, ""); |
1007 | run( | 1006 | apply_document_changes( |
1008 | &mut text, | 1007 | &mut text, |
1009 | vec![TextDocumentContentChangeEvent { | 1008 | vec![TextDocumentContentChangeEvent { |
1010 | range: None, | 1009 | range: None, |
@@ -1013,36 +1012,39 @@ mod tests { | |||
1013 | }], | 1012 | }], |
1014 | ); | 1013 | ); |
1015 | assert_eq!(text, "the"); | 1014 | assert_eq!(text, "the"); |
1016 | run(&mut text, c![0, 3; 0, 3 => " quick"]); | 1015 | apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); |
1017 | assert_eq!(text, "the quick"); | 1016 | assert_eq!(text, "the quick"); |
1018 | run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | 1017 | apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); |
1019 | assert_eq!(text, "quick foxes"); | 1018 | assert_eq!(text, "quick foxes"); |
1020 | run(&mut text, c![0, 11; 0, 11 => "\ndream"]); | 1019 | apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); |
1021 | assert_eq!(text, "quick foxes\ndream"); | 1020 | assert_eq!(text, "quick foxes\ndream"); |
1022 | run(&mut text, c![1, 0; 1, 0 => "have "]); | 1021 | apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); |
1023 | assert_eq!(text, "quick foxes\nhave dream"); | 1022 | assert_eq!(text, "quick foxes\nhave dream"); |
1024 | run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]); | 1023 | apply_document_changes( |
1024 | &mut text, | ||
1025 | c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], | ||
1026 | ); | ||
1025 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | 1027 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); |
1026 | run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | 1028 | apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); |
1027 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | 1029 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); |
1028 | run( | 1030 | apply_document_changes( |
1029 | &mut text, | 1031 | &mut text, |
1030 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | 1032 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], |
1031 | ); | 1033 | ); |
1032 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | 1034 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); |
1033 | run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | 1035 | apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); |
1034 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | 1036 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); |
1035 | 1037 | ||
1036 | text = String::from("❤️"); | 1038 | text = String::from("❤️"); |
1037 | run(&mut text, c![0, 0; 0, 0 => "a"]); | 1039 | apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); |
1038 | assert_eq!(text, "a❤️"); | 1040 | assert_eq!(text, "a❤️"); |
1039 | 1041 | ||
1040 | text = String::from("a\nb"); | 1042 | text = String::from("a\nb"); |
1041 | run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); | 1043 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); |
1042 | assert_eq!(text, "adcb"); | 1044 | assert_eq!(text, "adcb"); |
1043 | 1045 | ||
1044 | text = String::from("a\nb"); | 1046 | text = String::from("a\nb"); |
1045 | run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); | 1047 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); |
1046 | assert_eq!(text, "ațc\ncb"); | 1048 | assert_eq!(text, "ațc\ncb"); |
1047 | } | 1049 | } |
1048 | } | 1050 | } |