use std::collections::HashSet;
use tui_pane::ReactivateOutcome;
use tui_pane::ToastTaskId;
use tui_pane::TrackedItem;
use tui_pane::TrackedItemKey;
use tui_pane::format_toast_items;
use tui_pane::toast_body_width;
use crate::lint::LintRunOrigin;
use crate::project;
use crate::tui::app::App;
use crate::tui::integration;
impl App {
pub fn sync_running_clean_toast(&mut self) {
let (toast_slot, items) = self.inflight.clean().items_for_toast(
|p| project::home_relative_path(p.as_path()),
integration::path_key,
);
let next = self.sync_running_toast(toast_slot, "cargo clean", &items[..]);
self.inflight.clean_mut().toast = next;
}
pub(super) fn sync_running_lint_toast(&mut self) {
self.sync_running_lint_toast_for_origin(LintRunOrigin::CatchUp);
self.sync_running_lint_toast_for_origin(LintRunOrigin::Normal);
}
fn sync_running_lint_toast_for_origin(&mut self, origin: LintRunOrigin) {
let (toast_slot, items) = self.lint.toast_items_for_origin(origin);
let title = crate::tui::state::Lint::running_toast_title(origin);
let next = self.sync_running_toast(toast_slot, title, &items);
self.lint.set_running_toast_for_origin(origin, next);
}
pub(super) fn sync_running_repo_fetch_toast(&mut self) {
let Some(toast_slot) = self.net.network_toasts().map(|toasts| toasts.github) else {
return;
};
let (_, items) = self
.net
.github_running()
.items_for_toast(ToString::to_string, integration::owner_repo_key);
let next = self.sync_running_toast(toast_slot, "Retrieving GitHub repo details", &items);
if let Some(toasts) = self.net.network_toasts_mut() {
toasts.github = next;
}
}
pub(super) fn sync_running_crates_io_toast(&mut self) {
let Some(toast_slot) = self.net.network_toasts().map(|toasts| toasts.crates_io) else {
return;
};
let (_, items) = self
.net
.crates_io_running()
.items_for_toast(String::clone, |name| TrackedItemKey::from(name.as_str()));
let next = self.sync_running_toast(toast_slot, "Fetching crates.io info", &items);
if let Some(toasts) = self.net.network_toasts_mut() {
toasts.crates_io = next;
}
}
pub(super) fn enter_startup_owned_network_stage(&mut self) {
if let Some(slots) = self.net.network_toasts().map(|t| [t.crates_io, t.github]) {
for task_id in slots.into_iter().flatten() {
self.framework
.toasts
.complete_missing_items(task_id, &HashSet::new());
}
}
self.net.set_network_toasts_startup_owned();
}
pub(super) fn sync_running_toast(
&mut self,
toast_slot: Option<ToastTaskId>,
title: &str,
running_items: &[TrackedItem],
) -> Option<ToastTaskId> {
if running_items.is_empty() {
if let Some(task_id) = toast_slot {
let empty: HashSet<TrackedItemKey> = HashSet::new();
self.framework
.toasts
.complete_missing_items(task_id, &empty);
}
return toast_slot;
}
let running_keys: HashSet<TrackedItemKey> =
running_items.iter().map(|item| item.key.clone()).collect();
match toast_slot {
Some(task_id) => match self.framework.toasts.reactivate_task(task_id) {
ReactivateOutcome::Revived => {
self.framework
.toasts
.complete_missing_items(task_id, &running_keys);
self.framework
.toasts
.add_new_tracked_items(task_id, running_items);
for item in running_items {
if let Some(started) = item.started_at {
self.framework
.toasts
.restart_tracked_item(task_id, &item.key, started);
}
}
Some(task_id)
},
ReactivateOutcome::DismissedByUser => {
Some(task_id)
},
ReactivateOutcome::NotFound => Some(self.start_running_toast(title, running_items)),
},
None => Some(self.start_running_toast(title, running_items)),
}
}
fn start_running_toast(&mut self, title: &str, running_items: &[TrackedItem]) -> ToastTaskId {
let labels: Vec<&str> = running_items
.iter()
.map(|item| item.label.as_str())
.collect();
let width = toast_body_width(self.framework.toast_settings());
let body = format_toast_items(&labels, width);
let task_id = self.framework.toasts.start_task(title, body);
self.set_task_tracked_items(task_id, running_items);
task_id
}
}
#[cfg(test)]
#[allow(
clippy::expect_used,
reason = "tests should panic on unexpected values"
)]
mod tests {
use tui_pane::ToastId;
use super::*;
use crate::tui::test_support;
const DISMISSED_ITEM: &str = "dismissed";
const FIRST_ITEM: &str = "first";
const RUNNING_TOAST_TITLE: &str = "Running task";
const SECOND_ITEM: &str = "second";
const STALE_ITEM: &str = "stale";
const STALE_TASK_ID: ToastTaskId = ToastTaskId(u64::MAX);
#[test]
fn sync_running_toast_preserves_reactivate_outcomes() {
let mut app = test_support::make_app(&[]);
let first_items = [tracked_item(FIRST_ITEM)];
let first_task_id = app
.sync_running_toast(None, RUNNING_TOAST_TITLE, &first_items)
.expect("running items should create the first toast");
assert_eq!(app.framework.toasts.active().len(), first_items.len());
assert_eq!(
app.framework.toasts.tracked_item_count(first_task_id),
first_items.len()
);
let revived_items = [tracked_item(FIRST_ITEM), tracked_item(SECOND_ITEM)];
let revived_task_id = app
.sync_running_toast(Some(first_task_id), RUNNING_TOAST_TITLE, &revived_items)
.expect("existing toast should reactivate");
assert_eq!(revived_task_id, first_task_id);
assert_eq!(app.framework.toasts.active().len(), first_items.len());
assert_eq!(
app.framework.toasts.tracked_item_count(first_task_id),
revived_items.len()
);
assert!(app.framework.toasts.dismiss(ToastId(first_task_id.get())));
let stored_toasts_after_dismiss = app.framework.toasts.active().len();
let dismissed_items = [tracked_item(DISMISSED_ITEM)];
let dismissed_task_id = app
.sync_running_toast(Some(first_task_id), RUNNING_TOAST_TITLE, &dismissed_items)
.expect("dismissed task slot should stay wired");
assert_eq!(dismissed_task_id, first_task_id);
assert_eq!(
app.framework.toasts.active().len(),
stored_toasts_after_dismiss
);
assert_eq!(app.framework.toasts.active_now().len(), 0);
assert_eq!(
app.framework.toasts.tracked_item_count(first_task_id),
revived_items.len()
);
let stale_items = [tracked_item(STALE_ITEM)];
let fresh_task_id = app
.sync_running_toast(Some(STALE_TASK_ID), RUNNING_TOAST_TITLE, &stale_items)
.expect("stale task slot should create a replacement toast");
assert_ne!(fresh_task_id, STALE_TASK_ID);
assert_ne!(fresh_task_id, first_task_id);
assert_eq!(
app.framework.toasts.active().len(),
stored_toasts_after_dismiss + 1
);
assert_eq!(app.framework.toasts.active_now().len(), stale_items.len());
assert_eq!(app.framework.toasts.tracked_item_count(STALE_TASK_ID), 0);
assert_eq!(
app.framework.toasts.tracked_item_count(fresh_task_id),
stale_items.len()
);
}
fn tracked_item(label: &str) -> TrackedItem { TrackedItem::new(label, label) }
}