use crate::bootstrap::BootstrapReport;
use crate::github::{IssueStatus, PrStatus};
use crate::sync::SyncReport;
use crate::worktree::WorktreeInfo;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskKind {
CreateWorktree,
RefreshWorktrees,
GithubIssue(u64),
GithubPr(u64),
Sync,
Bootstrap,
DeleteWorktree,
Pull,
Push,
EditWorktree,
}
impl TaskKind {
pub fn loading_label(self) -> &'static str {
match self {
TaskKind::CreateWorktree => "creating worktree…",
TaskKind::RefreshWorktrees => "refreshing worktrees…",
TaskKind::GithubIssue(_) | TaskKind::GithubPr(_) => "fetching GitHub status…",
TaskKind::Sync => "syncing…",
TaskKind::Bootstrap => "bootstrapping…",
TaskKind::DeleteWorktree => "deleting worktree…",
TaskKind::Pull => "pulling…",
TaskKind::Push => "pushing…",
TaskKind::EditWorktree => "renaming worktree…",
}
}
pub fn is_github(self) -> bool {
matches!(self, TaskKind::GithubIssue(_) | TaskKind::GithubPr(_))
}
pub fn is_mutating(self) -> bool {
matches!(
self,
TaskKind::CreateWorktree
| TaskKind::Sync
| TaskKind::Bootstrap
| TaskKind::DeleteWorktree
| TaskKind::Pull
| TaskKind::Push
| TaskKind::EditWorktree
)
}
}
pub struct CreateWorktreeResult {
pub branch: String,
pub created: PathBuf,
pub report: BootstrapReport,
}
pub struct EditWorktreeResult {
pub new_branch: String,
pub new_path: PathBuf,
pub new_name: String,
pub remote_renamed: bool,
}
pub enum TaskMsg {
CreateWorktree(u64, std::result::Result<CreateWorktreeResult, String>),
RefreshWorktrees(u64, std::result::Result<Vec<WorktreeInfo>, String>),
GithubIssue(u64, u64, std::result::Result<IssueStatus, String>),
GithubPr(u64, u64, std::result::Result<PrStatus, String>),
Sync(u64, String, std::result::Result<SyncReport, String>),
Bootstrap(u64, std::result::Result<BootstrapReport, String>),
DeleteWorktree(u64, String, String, std::result::Result<(), String>),
Pull(u64, String, std::result::Result<String, String>),
Push(u64, String, std::result::Result<String, String>),
EditWorktree(u64, std::result::Result<EditWorktreeResult, String>),
}
#[derive(Debug, Default)]
pub struct TaskRunner {
generation: HashMap<TaskKind, u64>,
running: HashSet<TaskKind>,
}
impl TaskRunner {
pub fn new() -> Self {
Self::default()
}
pub fn request(&mut self, kind: TaskKind) -> Option<u64> {
if self.running.contains(&kind) {
return None;
}
let generation = self.generation.entry(kind).or_insert(0);
*generation += 1;
let claimed = *generation;
self.running.insert(kind);
Some(claimed)
}
pub fn invalidate(&mut self, kind: TaskKind) {
*self.generation.entry(kind).or_insert(0) += 1;
self.running.remove(&kind);
}
pub fn invalidate_matching<F: Fn(TaskKind) -> bool>(&mut self, pred: F) {
let hits: Vec<TaskKind> = self.running.iter().copied().filter(|&k| pred(k)).collect();
for kind in hits {
self.invalidate(kind);
}
}
pub fn complete(&mut self, kind: TaskKind, generation: u64) -> bool {
let current = self.generation.get(&kind).copied().unwrap_or(0);
if generation != current {
return false;
}
self.running.remove(&kind)
}
pub fn is_loading(&self, kind: TaskKind) -> bool {
self.running.contains(&kind)
}
pub fn is_any_loading(&self) -> bool {
!self.running.is_empty()
}
pub fn has_mutating_task_in_flight(&self) -> bool {
self.running.iter().any(|kind| kind.is_mutating())
}
pub fn mutating_loading_label(&self) -> Option<&'static str> {
if self.running.contains(&TaskKind::CreateWorktree) {
Some(TaskKind::CreateWorktree.loading_label())
} else if self.running.contains(&TaskKind::Sync) {
Some(TaskKind::Sync.loading_label())
} else if self.running.contains(&TaskKind::Bootstrap) {
Some(TaskKind::Bootstrap.loading_label())
} else if self.running.contains(&TaskKind::DeleteWorktree) {
Some(TaskKind::DeleteWorktree.loading_label())
} else if self.running.contains(&TaskKind::Pull) {
Some(TaskKind::Pull.loading_label())
} else if self.running.contains(&TaskKind::Push) {
Some(TaskKind::Push.loading_label())
} else if self.running.contains(&TaskKind::EditWorktree) {
Some(TaskKind::EditWorktree.loading_label())
} else {
None
}
}
pub fn loading_label(&self) -> Option<&'static str> {
self.running.iter().next().map(|kind| kind.loading_label())
}
}