aboutsummaryrefslogtreecommitdiff
path: root/crates/rust-analyzer/src/main_loop.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-06-11 10:04:09 +0100
committerAleksey Kladov <[email protected]>2020-06-23 16:51:06 +0100
commitdad1333b48c38bc7a5628fc0ff5304d003776a85 (patch)
tree29be52a980b4cae72f46a48c48135a15e31641e0 /crates/rust-analyzer/src/main_loop.rs
parent7aa66371ee3e8b31217513204c8b4f683584419d (diff)
New VFS
Diffstat (limited to 'crates/rust-analyzer/src/main_loop.rs')
-rw-r--r--crates/rust-analyzer/src/main_loop.rs330
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
4mod handlers; 4mod handlers;
5mod subscriptions;
6pub(crate) mod request_metrics; 5pub(crate) mod request_metrics;
7 6
8use std::{ 7use 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};
20use lsp_server::{ 18use lsp_server::{
21 Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, 19 Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response,
22}; 20};
23use lsp_types::{ 21use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent};
24 request::Request as _, DidChangeTextDocumentParams, NumberOrString, 22use ra_flycheck::CheckTask;
25 TextDocumentContentChangeEvent, WorkDoneProgress, WorkDoneProgressBegin,
26 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport,
27};
28use ra_flycheck::{CheckTask, Status};
29use ra_ide::{Canceled, FileId, LineIndex}; 23use ra_ide::{Canceled, FileId, LineIndex};
30use ra_prof::profile; 24use ra_prof::profile;
31use ra_project_model::{PackageRoot, ProjectWorkspace}; 25use ra_project_model::{PackageRoot, ProjectWorkspace};
32use ra_vfs::VfsTask; 26use rustc_hash::FxHashSet;
33use serde::{de::DeserializeOwned, Serialize}; 27use serde::{de::DeserializeOwned, Serialize};
34use threadpool::ThreadPool; 28use 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};
39use ra_db::VfsPath;
45 40
46#[derive(Debug)] 41#[derive(Debug)]
47pub struct LspError { 42pub 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 {
222enum Event { 210enum 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)]
271struct LoopState { 259struct 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)]
266enum Status {
267 Loading,
268 Ready,
269}
270
271impl Default for Status {
272 fn default() -> Self {
273 Status::Loading
274 }
278} 275}
279 276
280fn loop_turn( 277fn 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
466fn on_notification( 482fn 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(&params.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(&params.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(&params.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
595fn apply_document_changes( 618fn 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
731fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) { 747fn 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
784struct PoolDispatcher<'a> { 789struct PoolDispatcher<'a> {
@@ -976,18 +981,12 @@ where
976 981
977#[cfg(test)] 982#[cfg(test)]
978mod tests { 983mod 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}