use getset::Getters;
use typed_builder::TypedBuilder;
use crate::domain::{
process::{ActivityState, Process, ProcessState},
pty::ExitOutcome,
value::PaneId,
};
#[derive(Getters, TypedBuilder)]
#[getset(get = "pub")]
pub struct Workspace {
processes: Vec<Process>,
#[builder(default)]
selected_index: usize,
}
impl Workspace {
pub fn selected_process(&self) -> Option<&Process> {
self.processes.get(self.selected_index)
}
pub fn is_empty(&self) -> bool {
self.processes.is_empty()
}
pub fn add_process(&mut self, process: Process) {
self.processes.push(process);
}
pub fn remove(&mut self, pane: PaneId) {
if let Some(index) = self.position_of(pane) {
self.processes.remove(index);
if self.selected_index >= self.processes.len() {
self.selected_index = self.processes.len().saturating_sub(1);
}
}
}
pub fn select_next(&mut self) {
if !self.processes.is_empty() {
self.selected_index = (self.selected_index + 1) % self.processes.len();
}
}
pub fn select_at(&mut self, index: usize) {
if !self.processes.is_empty() {
self.selected_index = index.min(self.processes.len() - 1);
}
}
pub fn select_previous(&mut self) {
if !self.processes.is_empty() {
let last = self.processes.len() - 1;
self.selected_index = if self.selected_index == 0 {
last
} else {
self.selected_index - 1
};
}
}
pub fn position_of(&self, pane: PaneId) -> Option<usize> {
self.processes
.iter()
.position(|process| *process.id() == pane)
}
pub fn process(&self, pane: PaneId) -> Option<&Process> {
self.position_of(pane).map(|index| &self.processes[index])
}
pub fn set_state(&mut self, pane: PaneId, state: ProcessState) {
if let Some(index) = self.position_of(pane) {
self.processes[index].set_state(state);
}
}
pub fn set_autostart(&mut self, pane: PaneId, autostart: bool) {
if let Some(index) = self.position_of(pane) {
self.processes[index].set_autostart(autostart);
}
}
pub fn set_activity(&mut self, pane: PaneId, activity: ActivityState) {
if let Some(index) = self.position_of(pane) {
self.processes[index].set_activity(activity);
}
}
pub fn should_restart(&self, pane: PaneId, outcome: ExitOutcome) -> bool {
self.process(pane)
.map(|process| process.restart().should_restart(outcome))
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::{
process::{ProcessKind, RestartPolicy},
value::{CommandLine, ProcessName},
};
fn process(id: u64, restart: RestartPolicy) -> Process {
Process::builder()
.id(PaneId::new(id))
.name(ProcessName::try_new(format!("p{id}")).unwrap())
.kind(ProcessKind::Command)
.command(Some(CommandLine::try_new("true").unwrap()))
.restart(restart)
.build()
}
fn workspace() -> Workspace {
Workspace::builder()
.processes(vec![
process(0, RestartPolicy::Never),
process(1, RestartPolicy::Always),
])
.build()
}
#[test]
fn selection_wraps_in_both_directions() {
let mut ws = workspace();
assert_eq!(*ws.selected_index(), 0);
ws.select_previous();
assert_eq!(*ws.selected_index(), 1);
ws.select_next();
assert_eq!(*ws.selected_index(), 0);
}
#[test]
fn set_state_updates_the_named_process() {
let mut ws = workspace();
ws.set_state(PaneId::new(1), ProcessState::Running);
assert_eq!(
*ws.process(PaneId::new(1)).unwrap().state(),
ProcessState::Running
);
}
#[test]
fn restart_decision_follows_policy() {
let ws = workspace();
assert!(!ws.should_restart(PaneId::new(0), ExitOutcome::Failed));
assert!(ws.should_restart(PaneId::new(1), ExitOutcome::Failed));
}
}