Merge #8355
8355: internal: do not drop errors from cargo metadata/check r=matklad a=matklad Work towards #3155 Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
4414071074
@ -52,7 +52,7 @@ pub struct BuildDataCollector {
|
||||
configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub struct BuildDataResult {
|
||||
data: FxHashMap<AbsPathBuf, BuildDataMap>,
|
||||
}
|
||||
|
@ -445,8 +445,8 @@ impl Config {
|
||||
pub fn hover_actions(&self) -> bool {
|
||||
self.experimental("hoverActions")
|
||||
}
|
||||
pub fn status_notification(&self) -> bool {
|
||||
self.experimental("statusNotification")
|
||||
pub fn server_status_notification(&self) -> bool {
|
||||
self.experimental("serverStatusNotification")
|
||||
}
|
||||
|
||||
pub fn publish_diagnostics(&self) -> bool {
|
||||
|
@ -23,6 +23,7 @@ use crate::{
|
||||
document::DocumentData,
|
||||
from_proto,
|
||||
line_index::{LineEndings, LineIndex},
|
||||
lsp_ext,
|
||||
main_loop::Task,
|
||||
op_queue::OpQueue,
|
||||
reload::SourceRootConfig,
|
||||
@ -32,20 +33,6 @@ use crate::{
|
||||
Result,
|
||||
};
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||
pub(crate) enum Status {
|
||||
Loading,
|
||||
Ready { partial: bool },
|
||||
Invalid,
|
||||
NeedsReload,
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Status::Loading
|
||||
}
|
||||
}
|
||||
|
||||
// Enforces drop order
|
||||
pub(crate) struct Handle<H, C> {
|
||||
pub(crate) handle: H,
|
||||
@ -67,26 +54,36 @@ pub(crate) struct GlobalState {
|
||||
req_queue: ReqQueue,
|
||||
pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
|
||||
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
|
||||
pub(crate) vfs_config_version: u32,
|
||||
pub(crate) flycheck: Vec<FlycheckHandle>,
|
||||
pub(crate) flycheck_sender: Sender<flycheck::Message>,
|
||||
pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
|
||||
pub(crate) config: Arc<Config>,
|
||||
pub(crate) analysis_host: AnalysisHost,
|
||||
pub(crate) diagnostics: DiagnosticCollection,
|
||||
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
|
||||
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
||||
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||
pub(crate) shutdown_requested: bool,
|
||||
pub(crate) status: Status,
|
||||
pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
|
||||
pub(crate) source_root_config: SourceRootConfig,
|
||||
pub(crate) proc_macro_client: Option<ProcMacroClient>,
|
||||
|
||||
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
pub(crate) fetch_workspaces_queue: OpQueue<(), ()>,
|
||||
pub(crate) flycheck: Vec<FlycheckHandle>,
|
||||
pub(crate) flycheck_sender: Sender<flycheck::Message>,
|
||||
pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
|
||||
|
||||
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||
pub(crate) vfs_config_version: u32,
|
||||
pub(crate) vfs_progress_config_version: u32,
|
||||
pub(crate) vfs_progress_n_total: usize,
|
||||
pub(crate) vfs_progress_n_done: usize,
|
||||
|
||||
/// For both `workspaces` and `workspace_build_data`, the field stores the
|
||||
/// data we actually use, while the `OpQueue` stores the result of the last
|
||||
/// fetch.
|
||||
///
|
||||
/// If the fetch (partially) fails, we do not update the values.
|
||||
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
pub(crate) fetch_workspaces_queue: OpQueue<(), Vec<anyhow::Result<ProjectWorkspace>>>,
|
||||
pub(crate) workspace_build_data: Option<BuildDataResult>,
|
||||
pub(crate) fetch_build_data_queue: OpQueue<BuildDataCollector, ()>,
|
||||
pub(crate) fetch_build_data_queue:
|
||||
OpQueue<BuildDataCollector, Option<anyhow::Result<BuildDataResult>>>,
|
||||
|
||||
latest_requests: Arc<RwLock<LatestRequests>>,
|
||||
}
|
||||
@ -124,25 +121,32 @@ impl GlobalState {
|
||||
GlobalState {
|
||||
sender,
|
||||
req_queue: ReqQueue::default(),
|
||||
vfs_config_version: 0,
|
||||
task_pool,
|
||||
loader,
|
||||
flycheck: Vec::new(),
|
||||
flycheck_sender,
|
||||
flycheck_receiver,
|
||||
config: Arc::new(config),
|
||||
analysis_host,
|
||||
diagnostics: Default::default(),
|
||||
mem_docs: FxHashMap::default(),
|
||||
semantic_tokens_cache: Arc::new(Default::default()),
|
||||
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
|
||||
shutdown_requested: false,
|
||||
status: Status::default(),
|
||||
last_reported_status: None,
|
||||
source_root_config: SourceRootConfig::default(),
|
||||
proc_macro_client: None,
|
||||
|
||||
flycheck: Vec::new(),
|
||||
flycheck_sender,
|
||||
flycheck_receiver,
|
||||
|
||||
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
|
||||
vfs_config_version: 0,
|
||||
vfs_progress_config_version: 0,
|
||||
vfs_progress_n_total: 0,
|
||||
vfs_progress_n_done: 0,
|
||||
|
||||
workspaces: Arc::new(Vec::new()),
|
||||
fetch_workspaces_queue: OpQueue::default(),
|
||||
workspace_build_data: None,
|
||||
|
||||
fetch_build_data_queue: OpQueue::default(),
|
||||
latest_requests: Default::default(),
|
||||
}
|
||||
|
@ -241,26 +241,26 @@ pub struct SsrParams {
|
||||
pub selections: Vec<lsp_types::Range>,
|
||||
}
|
||||
|
||||
pub enum StatusNotification {}
|
||||
pub enum ServerStatusNotification {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
impl Notification for ServerStatusNotification {
|
||||
type Params = ServerStatusParams;
|
||||
const METHOD: &'static str = "experimental/serverStatus";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct ServerStatusParams {
|
||||
pub health: Health,
|
||||
pub quiescent: bool,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Status {
|
||||
Loading,
|
||||
ReadyPartial,
|
||||
Ready,
|
||||
NeedsReload,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct StatusParams {
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
impl Notification for StatusNotification {
|
||||
type Params = StatusParams;
|
||||
const METHOD: &'static str = "rust-analyzer/status";
|
||||
pub enum Health {
|
||||
Ok,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub enum CodeActionRequest {}
|
||||
|
@ -2,6 +2,7 @@
|
||||
//! requests/replies and notifications back to the client.
|
||||
use std::{
|
||||
env, fmt,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
@ -12,6 +13,7 @@ use ide::{Canceled, FileId};
|
||||
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::{
|
||||
@ -19,7 +21,7 @@ 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},
|
||||
reload::{BuildDataProgress, ProjectWorkspaceProgress},
|
||||
@ -187,7 +189,7 @@ impl GlobalState {
|
||||
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)?,
|
||||
@ -227,11 +229,24 @@ impl GlobalState {
|
||||
(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::default();
|
||||
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);
|
||||
}
|
||||
Task::FetchBuildData(progress) => {
|
||||
@ -240,19 +255,21 @@ impl GlobalState {
|
||||
BuildDataProgress::Report(msg) => {
|
||||
(Some(Progress::Report), Some(msg))
|
||||
}
|
||||
BuildDataProgress::End(collector) => {
|
||||
self.fetch_build_data_completed();
|
||||
let workspaces =
|
||||
(*self.workspaces).clone().into_iter().map(Ok).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Coalesce multiple task events into one loop turn
|
||||
task = match self.task_pool.receiver.try_recv() {
|
||||
Ok(task) => task,
|
||||
@ -298,30 +315,25 @@ impl GlobalState {
|
||||
}
|
||||
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
|
||||
@ -397,18 +409,14 @@ impl GlobalState {
|
||||
}
|
||||
|
||||
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.
|
||||
@ -437,9 +445,13 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@ -466,7 +478,8 @@ impl GlobalState {
|
||||
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)
|
||||
@ -477,7 +490,11 @@ impl GlobalState {
|
||||
}
|
||||
|
||||
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, ()| {
|
||||
|
@ -2,27 +2,27 @@
|
||||
//! at a time.
|
||||
|
||||
pub(crate) struct OpQueue<Args, Output> {
|
||||
op_scheduled: Option<Args>,
|
||||
op_requested: Option<Args>,
|
||||
op_in_progress: bool,
|
||||
last_op_result: Output,
|
||||
}
|
||||
|
||||
impl<Args, Output: Default> Default for OpQueue<Args, Output> {
|
||||
fn default() -> Self {
|
||||
Self { op_scheduled: None, op_in_progress: false, last_op_result: Default::default() }
|
||||
Self { op_requested: None, op_in_progress: false, last_op_result: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Args, Output> OpQueue<Args, Output> {
|
||||
pub(crate) fn request_op(&mut self, data: Args) {
|
||||
self.op_scheduled = Some(data);
|
||||
self.op_requested = Some(data);
|
||||
}
|
||||
pub(crate) fn should_start_op(&mut self) -> Option<Args> {
|
||||
if self.op_in_progress {
|
||||
return None;
|
||||
}
|
||||
self.op_in_progress = self.op_scheduled.is_some();
|
||||
self.op_scheduled.take()
|
||||
self.op_in_progress = self.op_requested.is_some();
|
||||
self.op_requested.take()
|
||||
}
|
||||
pub(crate) fn op_completed(&mut self, result: Output) {
|
||||
assert!(self.op_in_progress);
|
||||
@ -34,4 +34,10 @@ impl<Args, Output> OpQueue<Args, Output> {
|
||||
pub(crate) fn last_op_result(&self) -> &Output {
|
||||
&self.last_op_result
|
||||
}
|
||||
pub(crate) fn op_in_progress(&self) -> bool {
|
||||
self.op_in_progress
|
||||
}
|
||||
pub(crate) fn op_requested(&self) -> bool {
|
||||
self.op_requested.is_some()
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,10 @@ use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
|
||||
|
||||
use crate::{
|
||||
config::{Config, FilesWatcher, LinkedProject},
|
||||
global_state::{GlobalState, Status},
|
||||
global_state::GlobalState,
|
||||
lsp_ext,
|
||||
main_loop::Task,
|
||||
};
|
||||
use lsp_ext::StatusParams;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ProjectWorkspaceProgress {
|
||||
@ -30,6 +29,13 @@ pub(crate) enum BuildDataProgress {
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub(crate) fn is_quiescent(&self) -> bool {
|
||||
!(self.fetch_workspaces_queue.op_in_progress()
|
||||
|| self.fetch_build_data_queue.op_in_progress()
|
||||
|| self.vfs_progress_config_version < self.vfs_config_version
|
||||
|| self.vfs_progress_n_done < self.vfs_progress_n_total)
|
||||
}
|
||||
|
||||
pub(crate) fn update_configuration(&mut self, config: Config) {
|
||||
let _p = profile::span("GlobalState::update_configuration");
|
||||
let old_config = mem::replace(&mut self.config, Arc::new(config));
|
||||
@ -46,25 +52,17 @@ impl GlobalState {
|
||||
if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
|
||||
return;
|
||||
}
|
||||
match self.status {
|
||||
Status::Loading | Status::NeedsReload => return,
|
||||
Status::Ready { .. } | Status::Invalid => (),
|
||||
}
|
||||
log::info!(
|
||||
"Reloading workspace because of the following changes: {}",
|
||||
"Requesting workspace reload because of the following changes: {}",
|
||||
itertools::join(
|
||||
changes
|
||||
.iter()
|
||||
.filter(|(path, kind)| is_interesting(path, *kind))
|
||||
.map(|(path, kind)| format!("{}/{:?}", path.display(), kind)),
|
||||
.map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
|
||||
", "
|
||||
)
|
||||
);
|
||||
if self.config.cargo_autoreload() {
|
||||
self.fetch_workspaces_request();
|
||||
} else {
|
||||
self.transition(Status::NeedsReload);
|
||||
}
|
||||
self.fetch_workspaces_request();
|
||||
|
||||
fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
|
||||
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
|
||||
@ -101,46 +99,32 @@ impl GlobalState {
|
||||
false
|
||||
}
|
||||
}
|
||||
pub(crate) fn transition(&mut self, new_status: Status) {
|
||||
self.status = new_status;
|
||||
if self.config.status_notification() {
|
||||
let lsp_status = match new_status {
|
||||
Status::Loading => lsp_ext::Status::Loading,
|
||||
Status::Ready { partial: true } => lsp_ext::Status::ReadyPartial,
|
||||
Status::Ready { partial: false } => lsp_ext::Status::Ready,
|
||||
Status::Invalid => lsp_ext::Status::Invalid,
|
||||
Status::NeedsReload => lsp_ext::Status::NeedsReload,
|
||||
};
|
||||
self.send_notification::<lsp_ext::StatusNotification>(StatusParams {
|
||||
status: lsp_status,
|
||||
});
|
||||
pub(crate) fn report_new_status_if_needed(&mut self) {
|
||||
if !self.config.server_status_notification() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) {
|
||||
self.fetch_build_data_queue.request_op(build_data_collector);
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_build_data_if_needed(&mut self) {
|
||||
let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
let mut status = lsp_ext::ServerStatusParams {
|
||||
health: lsp_ext::Health::Ok,
|
||||
quiescent: self.is_quiescent(),
|
||||
message: None,
|
||||
};
|
||||
self.task_pool.handle.spawn_with_sender(move |sender| {
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
|
||||
if !self.config.cargo_autoreload()
|
||||
&& self.is_quiescent()
|
||||
&& self.fetch_workspaces_queue.op_requested()
|
||||
{
|
||||
status.health = lsp_ext::Health::Warning;
|
||||
status.message = Some("Workspace reload required".to_string())
|
||||
}
|
||||
if let Some(error) = self.loading_error() {
|
||||
status.health = lsp_ext::Health::Error;
|
||||
status.message = Some(format!("Workspace reload failed: {}", error))
|
||||
}
|
||||
|
||||
let progress = {
|
||||
let sender = sender.clone();
|
||||
move |msg| {
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
|
||||
}
|
||||
};
|
||||
let res = build_data_collector.collect(&progress);
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
|
||||
});
|
||||
}
|
||||
pub(crate) fn fetch_build_data_completed(&mut self) {
|
||||
self.fetch_build_data_queue.op_completed(())
|
||||
if self.last_reported_status.as_ref() != Some(&status) {
|
||||
self.last_reported_status = Some(status.clone());
|
||||
self.send_notification::<lsp_ext::ServerStatusNotification>(status);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_workspaces_request(&mut self) {
|
||||
@ -194,57 +178,69 @@ impl GlobalState {
|
||||
}
|
||||
});
|
||||
}
|
||||
pub(crate) fn fetch_workspaces_completed(&mut self) {
|
||||
self.fetch_workspaces_queue.op_completed(())
|
||||
}
|
||||
|
||||
pub(crate) fn switch_workspaces(
|
||||
pub(crate) fn fetch_workspaces_completed(
|
||||
&mut self,
|
||||
workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
|
||||
workspace_build_data: Option<anyhow::Result<BuildDataResult>>,
|
||||
) {
|
||||
self.fetch_workspaces_queue.op_completed(workspaces)
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) {
|
||||
self.fetch_build_data_queue.request_op(build_data_collector);
|
||||
}
|
||||
pub(crate) fn fetch_build_data_if_needed(&mut self) {
|
||||
let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
self.task_pool.handle.spawn_with_sender(move |sender| {
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
|
||||
|
||||
let progress = {
|
||||
let sender = sender.clone();
|
||||
move |msg| {
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
|
||||
}
|
||||
};
|
||||
let res = build_data_collector.collect(&progress);
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
|
||||
});
|
||||
}
|
||||
pub(crate) fn fetch_build_data_completed(
|
||||
&mut self,
|
||||
build_data: anyhow::Result<BuildDataResult>,
|
||||
) {
|
||||
self.fetch_build_data_queue.op_completed(Some(build_data))
|
||||
}
|
||||
|
||||
pub(crate) fn switch_workspaces(&mut self) {
|
||||
let _p = profile::span("GlobalState::switch_workspaces");
|
||||
log::info!("will switch workspaces: {:?}", workspaces);
|
||||
log::info!("will switch workspaces");
|
||||
|
||||
let mut has_errors = false;
|
||||
let workspaces = workspaces
|
||||
.into_iter()
|
||||
.filter_map(|res| {
|
||||
res.map_err(|err| {
|
||||
has_errors = true;
|
||||
log::error!("failed to load workspace: {:#}", err);
|
||||
if self.workspaces.is_empty() {
|
||||
self.show_message(
|
||||
lsp_types::MessageType::Error,
|
||||
format!("rust-analyzer failed to load workspace: {:#}", err),
|
||||
);
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let workspace_build_data = match workspace_build_data {
|
||||
Some(Ok(it)) => Some(it),
|
||||
Some(Err(err)) => {
|
||||
log::error!("failed to fetch build data: {:#}", err);
|
||||
self.show_message(
|
||||
lsp_types::MessageType::Error,
|
||||
format!("rust-analyzer failed to fetch build data: {:#}", err),
|
||||
);
|
||||
if let Some(error_message) = self.loading_error() {
|
||||
log::error!("failed to switch workspaces: {}", error_message);
|
||||
self.show_message(lsp_types::MessageType::Error, error_message);
|
||||
if !self.workspaces.is_empty() {
|
||||
return;
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
|
||||
let workspaces = self
|
||||
.fetch_workspaces_queue
|
||||
.last_op_result()
|
||||
.iter()
|
||||
.filter_map(|res| res.as_ref().ok().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
|
||||
Some(Ok(it)) => Some(it.clone()),
|
||||
None | Some(Err(_)) => None,
|
||||
};
|
||||
|
||||
if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.workspaces.is_empty() && has_errors {
|
||||
return;
|
||||
}
|
||||
|
||||
if let FilesWatcher::Client = self.config.files().watcher {
|
||||
if self.config.did_change_watched_files_dynamic_registration() {
|
||||
let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
|
||||
@ -337,14 +333,6 @@ impl GlobalState {
|
||||
};
|
||||
change.set_crate_graph(crate_graph);
|
||||
|
||||
if self.config.run_build_scripts() && workspace_build_data.is_none() {
|
||||
let mut collector = BuildDataCollector::default();
|
||||
for ws in &workspaces {
|
||||
ws.collect_build_data_configs(&mut collector);
|
||||
}
|
||||
self.fetch_build_data_request(collector)
|
||||
}
|
||||
|
||||
self.source_root_config = project_folders.source_root_config;
|
||||
self.workspaces = Arc::new(workspaces);
|
||||
self.workspace_build_data = workspace_build_data;
|
||||
@ -355,6 +343,24 @@ impl GlobalState {
|
||||
log::info!("did switch workspaces");
|
||||
}
|
||||
|
||||
fn loading_error(&self) -> Option<String> {
|
||||
let mut message = None;
|
||||
|
||||
for ws in self.fetch_workspaces_queue.last_op_result() {
|
||||
if let Err(err) = ws {
|
||||
let message = message.get_or_insert_with(String::new);
|
||||
stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
|
||||
let message = message.get_or_insert_with(String::new);
|
||||
stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
|
||||
}
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
fn reload_flycheck(&mut self) {
|
||||
let _p = profile::span("GlobalState::reload_flycheck");
|
||||
let config = match self.config.flycheck() {
|
||||
|
@ -103,7 +103,7 @@ impl<'a> Project<'a> {
|
||||
..Default::default()
|
||||
}),
|
||||
experimental: Some(json!({
|
||||
"statusNotification": true,
|
||||
"serverStatusNotification": true,
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
@ -213,13 +213,12 @@ impl Server {
|
||||
}
|
||||
pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
|
||||
self.wait_for_message_cond(1, &|msg: &Message| match msg {
|
||||
Message::Notification(n) if n.method == "rust-analyzer/status" => {
|
||||
Message::Notification(n) if n.method == "experimental/serverStatus" => {
|
||||
let status = n
|
||||
.clone()
|
||||
.extract::<lsp_ext::StatusParams>("rust-analyzer/status")
|
||||
.unwrap()
|
||||
.status;
|
||||
matches!(status, lsp_ext::Status::Ready)
|
||||
.extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
|
||||
.unwrap();
|
||||
status.quiescent
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!---
|
||||
lsp_ext.rs hash: e8a7502bd2b2c2f5
|
||||
lsp_ext.rs hash: faae991334a151d0
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
@ -419,23 +419,37 @@ Returns internal status message, mostly for debugging purposes.
|
||||
|
||||
Reloads project information (that is, re-executes `cargo metadata`).
|
||||
|
||||
## Status Notification
|
||||
## Server Status
|
||||
|
||||
**Experimental Client Capability:** `{ "statusNotification": boolean }`
|
||||
**Experimental Client Capability:** `{ "serverStatus": boolean }`
|
||||
|
||||
**Method:** `rust-analyzer/status`
|
||||
**Method:** `experimental/serverStatus`
|
||||
|
||||
**Notification:**
|
||||
|
||||
```typescript
|
||||
interface StatusParams {
|
||||
status: "loading" | "readyPartial" | "ready" | "invalid" | "needsReload",
|
||||
interface ServerStatusParams {
|
||||
/// `ok` means that the server is completely functional.
|
||||
///
|
||||
/// `warning` means that the server is partially functional.
|
||||
/// It can server requests, but some results might be wrong due to,
|
||||
/// for example, some missing dependencies.
|
||||
///
|
||||
/// `error` means that the server is not functional. For example,
|
||||
/// there's a fatal build configuration problem.
|
||||
health: "ok" | "warning" | "error",
|
||||
/// Is there any pending background work which might change the status?
|
||||
/// For example, are dependencies being downloaded?
|
||||
quiescent: bool,
|
||||
/// Explanatory message to show on hover.
|
||||
message?: string,
|
||||
}
|
||||
```
|
||||
|
||||
This notification is sent from server to client.
|
||||
The client can use it to display persistent status to the user (in modline).
|
||||
For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request.
|
||||
The client can use it to display *persistent* status to the user (in modline).
|
||||
It is similar to the `showMessage`, but is intended for stares rather than point-in-time events.
|
||||
|
||||
|
||||
## Syntax Tree
|
||||
|
||||
|
@ -159,7 +159,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
|
||||
caps.snippetTextEdit = true;
|
||||
caps.codeActionGroup = true;
|
||||
caps.hoverActions = true;
|
||||
caps.statusNotification = true;
|
||||
caps.serverStatusNotification = true;
|
||||
capabilities.experimental = caps;
|
||||
}
|
||||
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
|
||||
|
@ -5,7 +5,7 @@ import * as ra from './lsp_ext';
|
||||
import { Config } from './config';
|
||||
import { createClient } from './client';
|
||||
import { isRustEditor, RustEditor } from './util';
|
||||
import { Status } from './lsp_ext';
|
||||
import { ServerStatusParams } from './lsp_ext';
|
||||
|
||||
export class Ctx {
|
||||
private constructor(
|
||||
@ -36,7 +36,7 @@ export class Ctx {
|
||||
|
||||
res.pushCleanup(client.start());
|
||||
await client.onReady();
|
||||
client.onNotification(ra.status, (params) => res.setStatus(params.status));
|
||||
client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params));
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -66,39 +66,28 @@ export class Ctx {
|
||||
return this.extCtx.subscriptions;
|
||||
}
|
||||
|
||||
setStatus(status: Status) {
|
||||
switch (status) {
|
||||
case "loading":
|
||||
this.statusBar.text = "$(sync~spin) rust-analyzer";
|
||||
this.statusBar.tooltip = "Loading the project";
|
||||
this.statusBar.command = undefined;
|
||||
setServerStatus(status: ServerStatusParams) {
|
||||
this.statusBar.tooltip = status.message ?? "Ready";
|
||||
let icon = "";
|
||||
switch (status.health) {
|
||||
case "ok":
|
||||
this.statusBar.color = undefined;
|
||||
break;
|
||||
case "readyPartial":
|
||||
this.statusBar.text = "rust-analyzer";
|
||||
this.statusBar.tooltip = "Ready (Partial)";
|
||||
this.statusBar.command = undefined;
|
||||
this.statusBar.color = undefined;
|
||||
break;
|
||||
case "ready":
|
||||
this.statusBar.text = "rust-analyzer";
|
||||
this.statusBar.tooltip = "Ready";
|
||||
this.statusBar.command = undefined;
|
||||
this.statusBar.color = undefined;
|
||||
break;
|
||||
case "invalid":
|
||||
this.statusBar.text = "$(error) rust-analyzer";
|
||||
this.statusBar.tooltip = "Failed to load the project";
|
||||
this.statusBar.command = undefined;
|
||||
this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
|
||||
break;
|
||||
case "needsReload":
|
||||
this.statusBar.text = "$(warning) rust-analyzer";
|
||||
this.statusBar.tooltip = "Click to reload";
|
||||
case "warning":
|
||||
this.statusBar.tooltip += "\nClick to reload.";
|
||||
this.statusBar.command = "rust-analyzer.reloadWorkspace";
|
||||
this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
|
||||
icon = "$(warning) ";
|
||||
break;
|
||||
case "error":
|
||||
this.statusBar.tooltip += "\nClick to reload.";
|
||||
this.statusBar.command = "rust-analyzer.reloadWorkspace";
|
||||
this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
|
||||
icon = "$(error) ";
|
||||
break;
|
||||
}
|
||||
if (!status.quiescent) icon = "$(sync~spin) ";
|
||||
this.statusBar.text = `${icon} rust-analyzer`;
|
||||
}
|
||||
|
||||
pushCleanup(d: Disposable) {
|
||||
|
@ -10,11 +10,12 @@ export interface AnalyzerStatusParams {
|
||||
export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
|
||||
export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
|
||||
|
||||
export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload";
|
||||
export interface StatusParams {
|
||||
status: Status;
|
||||
export interface ServerStatusParams {
|
||||
health: "ok" | "warning" | "error";
|
||||
quiescent: boolean;
|
||||
message?: string;
|
||||
}
|
||||
export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status");
|
||||
export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus");
|
||||
|
||||
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user