//! requests/replies and notifications back to the client.
use std::{
env, fmt,
+ sync::Arc,
time::{Duration, Instant},
};
use always_assert::always;
use crossbeam_channel::{select, Receiver};
-use ide::PrimeCachesProgress;
-use ide::{Canceled, FileId};
+use ide::{FileId, PrimeCachesProgress};
use ide_db::base_db::VfsPath;
use lsp_server::{Connection, Notification, Request, Response};
use lsp_types::notification::Notification as _;
+use project_model::BuildDataCollector;
use vfs::ChangeKind;
use crate::{
dispatch::{NotificationDispatcher, RequestDispatcher},
document::DocumentData,
from_proto,
- global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
+ global_state::{file_id_to_url, url_to_file_id, GlobalState},
handlers, lsp_ext,
- lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
+ lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
reload::{BuildDataProgress, ProjectWorkspaceProgress},
Result,
};
impl GlobalState {
fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
if self.config.linked_projects().is_empty()
+ && self.config.detached_files().is_empty()
&& self.config.notifications().cargo_toml_not_found
{
self.show_message(
log::info!("task queue len: {}", task_queue_len);
}
- let mut new_status = self.status;
+ let was_quiescent = self.is_quiescent();
match event {
Event::Lsp(msg) => match msg {
lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
(Progress::Report, Some(msg))
}
ProjectWorkspaceProgress::End(workspaces) => {
- self.fetch_workspaces_completed();
- self.switch_workspaces(workspaces, None);
+ self.fetch_workspaces_completed(workspaces);
+
+ let old = Arc::clone(&self.workspaces);
+ self.switch_workspaces();
+ let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
+
+ if self.config.run_build_scripts() && workspaces_updated {
+ let mut collector =
+ BuildDataCollector::new(self.config.wrap_rustc());
+ for ws in self.workspaces.iter() {
+ ws.collect_build_data_configs(&mut collector);
+ }
+ self.fetch_build_data_request(collector)
+ }
+
(Progress::End, None)
}
};
- self.report_progress("fetching", state, msg, None);
+
+ self.report_progress("Fetching", state, msg, None);
}
Task::FetchBuildData(progress) => {
let (state, msg) = match progress {
BuildDataProgress::Report(msg) => {
(Some(Progress::Report), Some(msg))
}
- BuildDataProgress::End(collector) => {
- self.fetch_build_data_completed();
- let workspaces = (*self.workspaces)
- .clone()
- .into_iter()
- .map(|it| Ok(it))
- .collect();
- self.switch_workspaces(workspaces, Some(collector));
+ BuildDataProgress::End(build_data_result) => {
+ self.fetch_build_data_completed(build_data_result);
+
+ self.switch_workspaces();
+
(Some(Progress::End), None)
}
};
+
if let Some(state) = state {
- self.report_progress("loading", state, msg, None);
+ self.report_progress("Loading", state, msg, None);
}
}
}
+
// Coalesce multiple task events into one loop turn
task = match self.task_pool.receiver.try_recv() {
Ok(task) => task,
state = Progress::End;
message = None;
fraction = 1.0;
+
+ self.prime_caches_queue.op_completed(());
}
};
- self.report_progress("indexing", state, message, Some(fraction));
+ self.report_progress("Indexing", state, message, Some(fraction));
}
}
Event::Vfs(mut task) => {
}
vfs::loader::Message::Progress { n_total, n_done, config_version } => {
always!(config_version <= self.vfs_config_version);
- if n_total == 0 {
- new_status = Status::Invalid;
+
+ self.vfs_progress_config_version = config_version;
+ self.vfs_progress_n_total = n_total;
+ self.vfs_progress_n_done = n_done;
+
+ let state = if n_done == 0 {
+ Progress::Begin
+ } else if n_done < n_total {
+ Progress::Report
} else {
- let state = if n_done == 0 {
- new_status = Status::Loading;
- Progress::Begin
- } else if n_done < n_total {
- Progress::Report
- } else {
- assert_eq!(n_done, n_total);
- new_status = Status::Ready {
- partial: self.config.run_build_scripts()
- && self.workspace_build_data.is_none()
- || config_version < self.vfs_config_version,
- };
- Progress::End
- };
- self.report_progress(
- "roots scanned",
- state,
- Some(format!("{}/{}", n_done, n_total)),
- Some(Progress::fraction(n_done, n_total)),
- )
- }
+ assert_eq!(n_done, n_total);
+ Progress::End
+ };
+ self.report_progress(
+ "Roots Scanned",
+ state,
+ Some(format!("{}/{}", n_done, n_total)),
+ Some(Progress::fraction(n_done, n_total)),
+ )
}
}
// Coalesce many VFS event into a single loop turn
}
let state_changed = self.process_changes();
- let prev_status = self.status;
- if prev_status != new_status {
- self.transition(new_status);
- }
- let is_ready = matches!(self.status, Status::Ready { .. });
- if prev_status == Status::Loading && is_ready {
+
+ if self.is_quiescent() && !was_quiescent {
for flycheck in &self.flycheck {
flycheck.update();
}
}
- if is_ready && (state_changed || prev_status == Status::Loading) {
+ if self.is_quiescent() && (!was_quiescent || state_changed) {
self.update_file_notifications_on_threadpool();
// Refresh semantic tokens if the client supports it.
}
}
- self.fetch_workspaces_if_needed();
+ if self.config.cargo_autoreload() {
+ self.fetch_workspaces_if_needed();
+ }
self.fetch_build_data_if_needed();
+ self.report_new_status_if_needed();
+
let loop_duration = loop_start.elapsed();
if loop_duration > Duration::from_millis(100) {
log::warn!("overly long loop turn: {:?}", loop_duration);
return Ok(());
}
- if self.status == Status::Loading && req.method != "shutdown" {
+ // Avoid flashing a bunch of unresolved references during initial load.
+ if self.workspaces.is_empty() && !self.is_quiescent() {
self.respond(lsp_server::Response::new_err(
req.id,
// FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion)
lsp_server::ErrorCode::ContentModified as i32,
- "Rust Analyzer is still loading...".to_owned(),
+ "waiting for cargo metadata or cargo check".to_owned(),
));
return Ok(());
}
RequestDispatcher { req: Some(req), global_state: self }
- .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces_request()))?
+ .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| {
+ s.fetch_workspaces_request();
+ s.fetch_workspaces_if_needed();
+ Ok(())
+ })?
.on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
.on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
.on_sync::<lsp_types::request::Shutdown>(|s, ()| {
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
+ .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
+ .on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
.on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
.on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
.on::<lsp_ext::Runnables>(handlers::handle_runnables)
.on::<lsp_ext::HoverRequest>(handlers::handle_hover)
.on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
.on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
+ .on::<lsp_ext::MoveItem>(handlers::handle_move_item)
+ .on::<lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
- .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
.on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition)
.on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
.on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
.on::<lsp_types::request::Rename>(handlers::handle_rename)
.on::<lsp_types::request::References>(handlers::handle_references)
.on::<lsp_types::request::Formatting>(handlers::handle_formatting)
+ .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
.on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
.on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
.on::<lsp_types::request::CallHierarchyIncomingCalls>(
this.cancel(id);
Ok(())
})?
+ .on::<lsp_types::notification::WorkDoneProgressCancel>(|_this, _params| {
+ // Just ignore this. It is OK to continue sending progress
+ // notifications for this token, as the client can't know when
+ // we accepted notification.
+ Ok(())
+ })?
.on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
if this
}
fn update_file_notifications_on_threadpool(&mut self) {
self.maybe_update_diagnostics();
+
+ // Ensure that only one cache priming task can run at a time
+ self.prime_caches_queue.request_op(());
+ if self.prime_caches_queue.should_start_op().is_none() {
+ return;
+ }
+
self.task_pool.handle.spawn_with_sender({
let snap = self.snapshot();
move |sender| {
- snap.analysis
- .prime_caches(|progress| {
- sender.send(Task::PrimeCaches(progress)).unwrap();
- })
- .unwrap_or_else(|_: Canceled| {
- // Pretend that we're done, so that the progress bar is removed. Otherwise
- // the editor may complain about it already existing.
- sender.send(Task::PrimeCaches(PrimeCachesProgress::Finished)).unwrap()
- });
+ let cb = |progress| {
+ sender.send(Task::PrimeCaches(progress)).unwrap();
+ };
+ match snap.analysis.prime_caches(cb) {
+ Ok(()) => (),
+ Err(_canceled) => (),
+ }
}
});
}
let subscriptions = self
.mem_docs
.keys()
- .map(|path| self.vfs.read().0.file_id(&path).unwrap())
+ .map(|path| self.vfs.read().0.file_id(path).unwrap())
.collect::<Vec<_>>();
log::trace!("updating notifications for {:?}", subscriptions);
.filter_map(|file_id| {
handlers::publish_diagnostics(&snapshot, file_id)
.map_err(|err| {
- if !is_canceled(&*err) {
+ if !is_cancelled(&*err) {
log::error!("failed to compute diagnostics: {:?}", err);
}
()