X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Frust-analyzer%2Fsrc%2Fmain_loop.rs;fp=crates%2Frust-analyzer%2Fsrc%2Fmain_loop.rs;h=7ccdbd29cf568a8875d2d12ce6dbd772f7bd1057;hb=76a530242a12f75e2a8456f952cef07e2d564f67;hp=7a81db3d916b26213d986a02dbed1fc7e5046c08;hpb=6e81c9a921b975be7f2efb927dab4f3cfd505ebc;p=rust.git diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 7a81db3d916..7ccdbd29cf5 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -1,17 +1,7 @@ //! The main loop of `rust-analyzer` responsible for dispatching LSP //! requests/replies and notifications back to the client. - -mod handlers; -mod subscriptions; -pub(crate) mod pending_requests; -mod lsp_utils; - use std::{ - borrow::Cow, - convert::TryFrom, - env, - error::Error, - fmt, + env, fmt, ops::Range, panic, sync::Arc, @@ -20,13 +10,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; -use lsp_types::{DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent}; +use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent}; +use ra_db::VfsPath; use ra_flycheck::CheckTask; use ra_ide::{Canceled, FileId, LineIndex}; use ra_prof::profile; use ra_project_model::{PackageRoot, ProjectWorkspace}; -use ra_vfs::VfsTask; -use rustc_hash::FxHashSet; use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; @@ -34,13 +23,10 @@ config::{Config, FilesWatcher, LinkedProject}, diagnostics::DiagnosticTask, from_proto, - global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, - lsp_ext, - main_loop::{ - pending_requests::{PendingRequest, PendingRequests}, - subscriptions::Subscriptions, - }, - Result, + global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot, Status}, + handlers, lsp_ext, + request_metrics::RequestMetrics, + LspError, Result, }; pub use lsp_utils::show_message; use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new}; @@ -51,28 +37,6 @@ const FLYCHECK_PROGRESS_TOKEN: &str = "rustAnalyzer/flycheck"; const ROOTS_SCANNED_PROGRESS_TOKEN: &str = "rustAnalyzer/rootsScanned"; -#[derive(Debug)] -pub struct LspError { - pub code: i32, - pub message: String, -} - -impl LspError { - pub const UNKNOWN_FILE: i32 = -32900; - - pub fn new(code: i32, message: String) -> LspError { - LspError { code, message } - } -} - -impl fmt::Display for LspError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Language Server request failed with {}. ({})", self.code, self.message) - } -} - -impl Error for LspError {} - pub fn main_loop(config: Config, connection: Connection) -> Result<()> { log::info!("initial config: {:#?}", config); @@ -95,7 +59,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { SetThreadPriority(thread, thread_priority_above_normal); } - let mut loop_state = LoopState::default(); let mut global_state = { let workspaces = { if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found { @@ -127,21 +90,13 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { .ok() } LinkedProject::InlineJsonProject(it) => { - Some(ra_project_model::ProjectWorkspace::Json { - project: it.clone(), - project_location: config.root_path.clone(), - }) + Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) } }) .collect::>() }; - let globs = config - .files - .exclude - .iter() - .map(|glob| crate::vfs_glob::Glob::new(glob)) - .collect::, _>>()?; + let mut req_queue = ReqQueue::default(); if let FilesWatcher::Client = config.files.watcher { let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { @@ -159,25 +114,15 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { register_options: Some(serde_json::to_value(registration_options).unwrap()), }; let params = lsp_types::RegistrationParams { registrations: vec![registration] }; - let request = request_new::( - loop_state.next_request_id(), + let request = req_queue.outgoing.register( + lsp_types::request::RegisterCapability::METHOD.to_string(), params, + DO_NOTHING, ); connection.sender.send(request.into()).unwrap(); } - GlobalState::new(workspaces, config.lru_capacity, &globs, config) - }; - - loop_state.roots_total = u32::try_from(global_state.vfs.read().n_roots()) - .expect("Wow, your project is so huge, that it cannot fit into u32..."); - - loop_state.roots_scanned = 0; - let mut roots_scanned_progress_receiver = { - let (recv, mut progress_src) = - U32ProgressSource::real_if(global_state.config.client_caps.work_done_progress); - loop_state.roots_progress = Some(progress_src.begin(0, loop_state.roots_total)); - recv + GlobalState::new(workspaces, config.lru_capacity, config, req_queue) }; let pool = ThreadPool::default(); @@ -220,14 +165,14 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { break; }; } - loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?; + assert!(!global_state.vfs.read().0.has_changes()); + loop_turn(&pool, &task_sender, &connection, &mut global_state, event)?; + assert!(!global_state.vfs.read().0.has_changes()); } } global_state.analysis_host.request_cancellation(); log::info!("waiting for tasks to finish..."); - task_receiver.into_iter().for_each(|task| { - on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) - }); + task_receiver.into_iter().for_each(|task| on_task(task, &connection.sender, &mut global_state)); log::info!("...tasks have finished"); log::info!("joining threadpool..."); pool.join(); @@ -251,7 +196,7 @@ enum Task { enum Event { Msg(Message), Task(Task), - Vfs(VfsTask), + Vfs(vfs::loader::Message), CheckWatcher(CheckTask), ProgressReport(ProgressReport), } @@ -300,35 +245,15 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { } } -#[derive(Default)] -struct LoopState { - next_request_id: u64, - pending_responses: FxHashSet, - pending_requests: PendingRequests, - subscriptions: Subscriptions, - workspace_loaded: bool, - roots_progress: Option, - roots_scanned: u32, - roots_total: u32, - configuration_request_id: Option, -} - -impl LoopState { - fn next_request_id(&mut self) -> RequestId { - self.next_request_id += 1; - let res: RequestId = self.next_request_id.into(); - let inserted = self.pending_responses.insert(res.clone()); - assert!(inserted); - res - } -} +pub(crate) type ReqHandler = fn(&mut GlobalState, Response); +pub(crate) type ReqQueue = lsp_server::ReqQueue<(&'static str, Instant), ReqHandler>; +const DO_NOTHING: ReqHandler = |_, _| (); fn loop_turn( pool: &ThreadPool, task_sender: &Sender, connection: &Connection, global_state: &mut GlobalState, - loop_state: &mut LoopState, event: Event, ) -> Result<()> { let loop_start = Instant::now(); @@ -341,96 +266,73 @@ fn loop_turn( log::info!("queued count = {}", queue_count); } + let mut became_ready = false; match event { Event::Task(task) => { - on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state); + on_task(task, &connection.sender, global_state); global_state.maybe_collect_garbage(); } - Event::Vfs(task) => { - global_state.vfs.write().handle_task(task); - } + Event::Vfs(task) => match task { + vfs::loader::Message::Loaded { files } => { + let vfs = &mut global_state.vfs.write().0; + for (path, contents) in files { + let path = VfsPath::from(path); + if !global_state.mem_docs.contains(&path) { + vfs.set_file_contents(path, contents) + } + } + } + vfs::loader::Message::Progress { n_total, n_done } => { + if n_done == n_total { + global_state.status = Status::Ready; + became_ready = true; + } + report_progress(global_state, &connection.sender, n_done, n_total, "roots scanned") + } + }, Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, Event::ProgressReport(report) => { on_progress_report(report, task_sender, loop_state, global_state) } Event::Msg(msg) => match msg { - Message::Request(req) => on_request( - global_state, - &mut loop_state.pending_requests, - pool, - task_sender, - &connection.sender, - loop_start, - req, - )?, + Message::Request(req) => { + on_request(global_state, pool, task_sender, &connection.sender, loop_start, req)? + } Message::Notification(not) => { - on_notification(&connection.sender, global_state, loop_state, not)?; + on_notification(&connection.sender, global_state, not)?; } Message::Response(resp) => { - let removed = loop_state.pending_responses.remove(&resp.id); - if !removed { - log::error!("unexpected response: {:?}", resp) - } - - if Some(&resp.id) == loop_state.configuration_request_id.as_ref() { - loop_state.configuration_request_id = None; - log::debug!("config update response: '{:?}", resp); - let Response { error, result, .. } = resp; - - match (error, result) { - (Some(err), _) => { - log::error!("failed to fetch the server settings: {:?}", err) - } - (None, Some(configs)) => { - if let Some(new_config) = configs.get(0) { - let mut config = global_state.config.clone(); - config.update(&new_config); - global_state.update_configuration(config); - } - } - (None, None) => { - log::error!("received empty server settings response from the client") - } - } - } + let handler = global_state.req_queue.outgoing.complete(resp.id.clone()); + handler(global_state, resp) } }, }; - let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned); - - let show_progress = - !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; + let state_changed = global_state.process_changes(); - if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total { - state_changed = true; - loop_state.workspace_loaded = true; + if became_ready { if let Some(flycheck) = &global_state.flycheck { flycheck.update(); } } - if show_progress { - if let Some(progress) = &mut loop_state.roots_progress { - if loop_state.workspace_loaded - || progress.report(loop_state.roots_scanned) == IsDone(true) - { - loop_state.roots_progress = None; - } - } - } + if global_state.status == Status::Ready && (state_changed || became_ready) { + let subscriptions = global_state + .mem_docs + .iter() + .map(|path| global_state.vfs.read().0.file_id(&path).unwrap()) + .collect::>(); - if state_changed && loop_state.workspace_loaded { update_file_notifications_on_threadpool( pool, global_state.snapshot(), task_sender.clone(), - loop_state.subscriptions.subscriptions(), + subscriptions.clone(), ); pool.execute({ - let subs = loop_state.subscriptions.subscriptions(); + let subs = subscriptions; let snap = global_state.snapshot(); - move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) + move || snap.analysis.prime_caches(subs).unwrap_or_else(|_: Canceled| ()) }); } @@ -449,110 +351,31 @@ fn loop_turn( Ok(()) } -fn on_progress_report( - report: ProgressReport, - task_sender: &Sender, - loop_state: &mut LoopState, - global_state: &GlobalState, -) { - let end_report = - || lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message: None }); - let mut create_progress = |token: &'static str| { - let create_progress_req = request_new::( - loop_state.next_request_id(), - lsp_types::WorkDoneProgressCreateParams { - token: lsp_types::ProgressToken::String(token.to_string()), - }, - ); - task_sender.send(Task::SendRequest(create_progress_req.into())).unwrap(); - }; - - let (token, progress) = match report { - ProgressReport::Flycheck(status) => { - let command = global_state - .config - .check - .as_ref() - .expect("There should be config, since flycheck is active"); - - let progress = match status { - ProgressStatus::Begin(()) => { - create_progress(FLYCHECK_PROGRESS_TOKEN); - lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { - title: "".to_string(), - cancellable: Some(false), - message: Some(command.to_string()), - percentage: None, - }) - } - ProgressStatus::Progress(target) => { - lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { - cancellable: Some(false), - message: Some(format!("{} [{}]", command, target)), - percentage: None, - }) - } - ProgressStatus::End => end_report(), - }; - (FLYCHECK_PROGRESS_TOKEN, progress) - } - ProgressReport::RootsScanned(status) => { - fn to_message(report: &U32ProgressReport) -> String { - report.to_message("analyzing the workspace", "packages") - } - let progress = match status { - ProgressStatus::Begin(report) => { - create_progress(ROOTS_SCANNED_PROGRESS_TOKEN); - lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { - title: "rust-analyzer".to_string(), - cancellable: Some(false), - message: Some(to_message(&report)), - percentage: Some(report.percentage()), - }) - } - ProgressStatus::Progress(report) => { - lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { - cancellable: Some(false), - message: Some(to_message(&report)), - percentage: Some(report.percentage()), - }) - } - ProgressStatus::End => end_report(), - }; - (ROOTS_SCANNED_PROGRESS_TOKEN, progress) - } - }; - let params = lsp_types::ProgressParams { - token: lsp_types::ProgressToken::String(token.to_string()), - value: lsp_types::ProgressParamsValue::WorkDone(progress), - }; - let not = notification_new::(params); - task_sender.send(Task::Notify(not.into())).unwrap() -} - -fn on_task( - task: Task, - msg_sender: &Sender, - pending_requests: &mut PendingRequests, - state: &mut GlobalState, -) { +fn on_task(task: Task, msg_sender: &Sender, global_state: &mut GlobalState) { match task { Task::Respond(response) => { - if let Some(completed) = pending_requests.finish(&response.id) { - log::info!("handled req#{} in {:?}", completed.id, completed.duration); - state.complete_request(completed); + if let Some((method, start)) = + global_state.req_queue.incoming.complete(response.id.clone()) + { + let duration = start.elapsed(); + log::info!("handled req#{} in {:?}", response.id, duration); + global_state.complete_request(RequestMetrics { + id: response.id.clone(), + method: method.to_string(), + duration, + }); msg_sender.send(response.into()).unwrap(); } } - Task::Notify(not) => msg_sender.send(not.into()).unwrap(), - Task::SendRequest(req) => msg_sender.send(req.into()).unwrap(), - Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state), + Task::Notify(n) => { + msg_sender.send(n.into()).unwrap(); + } + Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, global_state), } } fn on_request( global_state: &mut GlobalState, - pending_requests: &mut PendingRequests, pool: &ThreadPool, task_sender: &Sender, msg_sender: &Sender, @@ -565,7 +388,6 @@ fn on_request( global_state, task_sender, msg_sender, - pending_requests, request_received, }; pool_dispatcher @@ -619,8 +441,7 @@ fn on_request( fn on_notification( msg_sender: &Sender, - state: &mut GlobalState, - loop_state: &mut LoopState, + global_state: &mut GlobalState, not: Notification, ) -> Result<()> { let not = match notification_cast::(not) { @@ -629,12 +450,7 @@ fn on_notification( NumberOrString::Number(id) => id.into(), NumberOrString::String(id) => id.into(), }; - if loop_state.pending_requests.cancel(&id) { - let response = Response::new_err( - id, - ErrorCode::RequestCanceled as i32, - "canceled by client".to_string(), - ); + if let Some(response) = global_state.req_queue.incoming.cancel(id) { msg_sender.send(response.into()).unwrap() } return Ok(()); @@ -643,12 +459,15 @@ fn on_notification( }; let not = match notification_cast::(not) { Ok(params) => { - let uri = params.text_document.uri; - let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; - if let Some(file_id) = - state.vfs.write().add_file_overlay(&path, params.text_document.text) - { - loop_state.subscriptions.add_sub(FileId(file_id.0)); + if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { + if !global_state.mem_docs.insert(path.clone()) { + log::error!("duplicate DidOpenTextDocument: {}", path) + } + global_state + .vfs + .write() + .0 + .set_file_contents(path, Some(params.text_document.text.into_bytes())); } return Ok(()); } @@ -656,23 +475,13 @@ fn on_notification( }; let not = match notification_cast::(not) { Ok(params) => { - let DidChangeTextDocumentParams { text_document, content_changes } = params; - let world = state.snapshot(); - let file_id = from_proto::file_id(&world, &text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let uri = text_document.uri; - let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; - state.vfs.write().change_file_overlay(&path, |old_text| { - apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes); - }); - return Ok(()); - } - Err(not) => not, - }; - let not = match notification_cast::(not) { - Ok(_params) => { - if let Some(flycheck) = &state.flycheck { - flycheck.update(); + if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { + assert!(global_state.mem_docs.contains(&path)); + let vfs = &mut global_state.vfs.write().0; + let file_id = vfs.file_id(&path).unwrap(); + let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); + apply_document_changes(&mut text, params.content_changes); + vfs.set_file_contents(path, Some(text.into_bytes())) } return Ok(()); } @@ -680,35 +489,68 @@ fn on_notification( }; let not = match notification_cast::(not) { Ok(params) => { - let uri = params.text_document.uri; - let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; - if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { - loop_state.subscriptions.remove_sub(FileId(file_id.0)); + if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { + if !global_state.mem_docs.remove(&path) { + log::error!("orphan DidCloseTextDocument: {}", path) + } + if let Some(path) = path.as_path() { + global_state.loader.invalidate(path.to_path_buf()); + } } - let params = - lsp_types::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; + let params = lsp_types::PublishDiagnosticsParams { + uri: params.text_document.uri, + diagnostics: Vec::new(), + version: None, + }; let not = notification_new::(params); msg_sender.send(not.into()).unwrap(); return Ok(()); } Err(not) => not, }; + let not = match notification_cast::(not) { + Ok(_params) => { + if let Some(flycheck) = &global_state.flycheck { + flycheck.update(); + } + return Ok(()); + } + Err(not) => not, + }; let not = match notification_cast::(not) { Ok(_) => { // As stated in https://github.com/microsoft/language-server-protocol/issues/676, // this notification's parameters should be ignored and the actual config queried separately. - let request_id = loop_state.next_request_id(); - let request = request_new::( - request_id.clone(), + let request = global_state.req_queue.outgoing.register( + lsp_types::request::WorkspaceConfiguration::METHOD.to_string(), lsp_types::ConfigurationParams { items: vec![lsp_types::ConfigurationItem { scope_uri: None, section: Some("rust-analyzer".to_string()), }], }, + |global_state, resp| { + log::debug!("config update response: '{:?}", resp); + let Response { error, result, .. } = resp; + + match (error, result) { + (Some(err), _) => { + log::error!("failed to fetch the server settings: {:?}", err) + } + (None, Some(configs)) => { + if let Some(new_config) = configs.get(0) { + let mut config = global_state.config.clone(); + config.update(&new_config); + global_state.update_configuration(config); + } + } + (None, None) => { + log::error!("received empty server settings response from the client") + } + } + }, ); msg_sender.send(request.into())?; - loop_state.configuration_request_id = Some(request_id); return Ok(()); } @@ -716,11 +558,10 @@ fn on_notification( }; let not = match notification_cast::(not) { Ok(params) => { - let mut vfs = state.vfs.write(); for change in params.changes { - let uri = change.uri; - let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; - vfs.notify_changed(path) + if let Ok(path) = from_proto::abs_path(&change.uri) { + global_state.loader.invalidate(path) + } } return Ok(()); } @@ -735,9 +576,9 @@ fn on_notification( fn apply_document_changes( old_text: &mut String, - mut line_index: Cow<'_, LineIndex>, content_changes: Vec, ) { + let mut line_index = LineIndex::new(old_text); // The changes we got must be applied sequentially, but can cross lines so we // have to keep our line index updated. // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we @@ -762,7 +603,7 @@ fn covers(&self, line: u64) -> bool { match change.range { Some(range) => { if !index_valid.covers(range.end.line) { - line_index = Cow::Owned(LineIndex::new(&old_text)); + line_index = LineIndex::new(&old_text); } index_valid = IndexValid::UpToLineExclusive(range.start.line); let range = from_proto::text_range(&line_index, range); @@ -793,18 +634,11 @@ fn on_check_task( &workspace_root, ); for diag in diagnostics { - let path = diag - .location - .uri - .to_file_path() - .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; - let file_id = match global_state.vfs.read().path2file(&path) { + let path = from_proto::vfs_path(&diag.location.uri)?; + let file_id = match global_state.vfs.read().0.file_id(&path) { Some(file) => FileId(file.0), None => { - log::error!( - "File with cargo diagnostic not found in VFS: {}", - path.display() - ); + log::error!("File with cargo diagnostic not found in VFS: {}", path); return Ok(()); } }; @@ -816,6 +650,42 @@ fn on_check_task( )))?; } } + + CheckTask::Status(status) => { + if global_state.config.client_caps.work_done_progress { + let progress = match status { + ra_flycheck::Status::Being => { + lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { + title: "Running `cargo check`".to_string(), + cancellable: Some(false), + message: None, + percentage: None, + }) + } + ra_flycheck::Status::Progress(target) => { + lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { + cancellable: Some(false), + message: Some(target), + percentage: None, + }) + } + ra_flycheck::Status::End => { + lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { + message: None, + }) + } + }; + + let params = lsp_types::ProgressParams { + token: lsp_types::ProgressToken::String( + "rustAnalyzer/cargoWatcher".to_string(), + ), + value: lsp_types::ProgressParamsValue::WorkDone(progress), + }; + let not = notification_new::(params); + task_sender.send(Task::Notify(not)).unwrap(); + } + } }; Ok(()) @@ -825,7 +695,7 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: let subscriptions = state.diagnostics.handle_task(task); for file_id in subscriptions { - let url = file_id_to_url(&state.vfs.read(), file_id); + let url = file_id_to_url(&state.vfs.read().0, file_id); let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; let not = notification_new::(params); @@ -833,11 +703,52 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: } } +fn report_progress( + global_state: &mut GlobalState, + sender: &Sender, + done: usize, + total: usize, + message: &str, +) { + let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", message)); + let message = Some(format!("{}/{} {}", done, total, message)); + let percentage = Some(100.0 * done as f64 / total.max(1) as f64); + let work_done_progress = if done == 0 { + let work_done_progress_create = global_state.req_queue.outgoing.register( + lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(), + lsp_types::WorkDoneProgressCreateParams { token: token.clone() }, + DO_NOTHING, + ); + sender.send(work_done_progress_create.into()).unwrap(); + + lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { + title: "rust-analyzer".into(), + cancellable: None, + message, + percentage, + }) + } else if done < total { + lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { + cancellable: None, + message, + percentage, + }) + } else { + assert!(done == total); + lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message }) + }; + let notification = + notification_new::(lsp_types::ProgressParams { + token, + value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), + }); + sender.send(notification.into()).unwrap(); +} + struct PoolDispatcher<'a> { req: Option, pool: &'a ThreadPool, global_state: &'a mut GlobalState, - pending_requests: &'a mut PendingRequests, msg_sender: &'a Sender, task_sender: &'a Sender, request_received: Instant, @@ -866,7 +777,7 @@ fn on_sync( result_to_task::(id, result) }) .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; - on_task(task, self.msg_sender, self.pending_requests, self.global_state); + on_task(task, self.msg_sender, self.global_state); Ok(self) } @@ -913,11 +824,10 @@ fn parse(&mut self) -> Option<(RequestId, R::Params)> return None; } }; - self.pending_requests.start(PendingRequest { - id: id.clone(), - method: R::METHOD.to_string(), - received: self.request_received, - }); + self.global_state + .req_queue + .incoming + .register(id.clone(), (R::METHOD, self.request_received)); Some((id, params)) } @@ -946,14 +856,7 @@ fn result_to_task(id: RequestId, result: Result) -> Task let response = match result { Ok(resp) => Response::new_ok(id, &resp), Err(e) => match e.downcast::() { - Ok(lsp_error) => { - if lsp_error.code == LspError::UNKNOWN_FILE { - // Work-around for https://github.com/rust-analyzer/rust-analyzer/issues/1521 - Response::new_ok(id, ()) - } else { - Response::new_err(id, lsp_error.code, lsp_error.message) - } - } + Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message), Err(e) => { if is_canceled(&*e) { Response::new_err( @@ -995,20 +898,49 @@ fn update_file_notifications_on_threadpool( } } +pub fn show_message( + typ: lsp_types::MessageType, + message: impl Into, + sender: &Sender, +) { + let message = message.into(); + let params = lsp_types::ShowMessageParams { typ, message }; + let not = notification_new::(params); + sender.send(not.into()).unwrap(); +} + +fn is_canceled(e: &Box) -> bool { + e.downcast_ref::().is_some() +} + +fn notification_is(notification: &Notification) -> bool { + notification.method == N::METHOD +} + +fn notification_cast(notification: Notification) -> std::result::Result +where + N: lsp_types::notification::Notification, + N::Params: DeserializeOwned, +{ + notification.extract(N::METHOD) +} + +fn notification_new(params: N::Params) -> Notification +where + N: lsp_types::notification::Notification, + N::Params: Serialize, +{ + Notification::new(N::METHOD.to_string(), params) +} + #[cfg(test)] mod tests { - use std::borrow::Cow; - use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; - use ra_ide::LineIndex; - #[test] - fn apply_document_changes() { - fn run(text: &mut String, changes: Vec) { - let line_index = Cow::Owned(LineIndex::new(&text)); - super::apply_document_changes(text, line_index, changes); - } + use super::*; + #[test] + fn test_apply_document_changes() { macro_rules! c { [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { vec![$(TextDocumentContentChangeEvent { @@ -1023,9 +955,9 @@ macro_rules! c { } let mut text = String::new(); - run(&mut text, vec![]); + apply_document_changes(&mut text, vec![]); assert_eq!(text, ""); - run( + apply_document_changes( &mut text, vec![TextDocumentContentChangeEvent { range: None, @@ -1034,36 +966,39 @@ macro_rules! c { }], ); assert_eq!(text, "the"); - run(&mut text, c![0, 3; 0, 3 => " quick"]); + apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); assert_eq!(text, "the quick"); - run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); + apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); assert_eq!(text, "quick foxes"); - run(&mut text, c![0, 11; 0, 11 => "\ndream"]); + apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); assert_eq!(text, "quick foxes\ndream"); - run(&mut text, c![1, 0; 1, 0 => "have "]); + apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); assert_eq!(text, "quick foxes\nhave dream"); - run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]); + apply_document_changes( + &mut text, + c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], + ); assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); - run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); + apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); - run( + apply_document_changes( &mut text, c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], ); assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); - run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); + apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); assert_eq!(text, "the quick \nthey have quiet dreams\n"); text = String::from("❤️"); - run(&mut text, c![0, 0; 0, 0 => "a"]); + apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); assert_eq!(text, "a❤️"); text = String::from("a\nb"); - run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); + apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); assert_eq!(text, "adcb"); text = String::from("a\nb"); - run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); + apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); assert_eq!(text, "ațc\ncb"); } }