use colored::{ColoredString, Colorize};
use rustc_hash::FxHashMap;
use crate::{
SBStateConfig, Status, TaskId,
column::{Column, ColumnAlign, ColumnConfig, ColumnFit},
task::Task,
};
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct InternalState {
pub task_map: FxHashMap<Status, Vec<Task>>,
}
impl InternalState {
pub(crate) fn get_total(&self) -> usize {
let num_started = match self.task_map.get(&Status::Started) {
Some(v) => v.len(),
None => 0,
};
let num_queued = match self.task_map.get(&Status::Queued) {
Some(v) => v.len(),
None => 0,
};
let num_finished = match self.task_map.get(&Status::Finished) {
Some(v) => v.len(),
None => 0,
};
return num_finished + num_queued + num_started;
}
pub(crate) fn add_task(&mut self, key: TaskId, display_name: Option<String>, status: Status) {
self.task_map.entry(status).or_default().push(Task {
key,
display_name,
time: std::time::Instant::now(),
substate: InternalState::default(),
});
}
pub(crate) fn delete_task(&mut self, key: TaskId) {
for (status, tasks) in self.task_map.iter_mut() {
if !status.is_finished() {
tasks.retain(|task| if task.key == key { false } else { true });
}
}
}
pub(crate) fn set_display_name(&mut self, key: TaskId, display_name: String) {
for (_, tasks) in self.task_map.iter_mut() {
for task in tasks {
if task.key == key {
task.display_name = Some(display_name.to_string());
}
}
}
}
pub(crate) fn update_task(&mut self, key: TaskId, new_status: Status) {
let mut overall_to_move = vec![];
for (status, tasks) in self.task_map.iter_mut() {
if *status == new_status {
continue;
}
tasks.retain_mut(|task| -> bool {
if task.key == key {
task.time = std::time::Instant::now();
overall_to_move.push(task.clone());
return false;
}
return true;
});
}
self.task_map
.entry(new_status)
.or_default()
.extend(overall_to_move);
}
pub(crate) fn add_subtask(
&mut self,
key: TaskId,
subkey: TaskId,
display_name: Option<String>,
status: Status,
) {
for (_, tasks) in self.task_map.iter_mut() {
for task in tasks {
if task.key == key {
task.substate.add_task(subkey, display_name, status);
return;
}
}
}
}
pub(crate) fn update_subtask(&mut self, key: TaskId, subkey: TaskId, new_status: Status) {
for (_, tasks) in self.task_map.iter_mut() {
for task in tasks {
if task.key == key {
task.substate.update_task(subkey.clone(), new_status);
}
}
}
}
pub(crate) fn clear_old_entries(
&mut self,
max_duration: std::time::Duration,
statuses: &[Status],
) {
let now = std::time::Instant::now();
for (_, rows) in self
.task_map
.iter_mut()
.filter(|(status, _)| statuses.contains(status))
{
rows.retain_mut(|r| now.duration_since(r.time) < max_duration);
}
}
pub(crate) fn print_list<F>(
&self,
status: Status,
max: usize,
color_func: F,
terminal_width: usize,
task_name_fit: ColumnFit,
config: &SBStateConfig,
) where
F: Fn(&str) -> ColoredString,
{
if let Some(jobs) = self.task_map.get(&status) {
if jobs.len() > 0 {
println!("\n{:?} ({}):", status, jobs.len());
let mut columns = vec![
Column::new(ColumnConfig {
align: ColumnAlign::LEFT,
fit: task_name_fit,
left_padding: 4,
right_padding: 1,
}),
Column::new(ColumnConfig {
align: ColumnAlign::RIGHT,
fit: ColumnFit::NORMAL,
left_padding: 3,
right_padding: 1,
}),
Column::new(ColumnConfig {
align: ColumnAlign::RIGHT,
fit: ColumnFit::NORMAL,
left_padding: 0,
right_padding: 1,
}),
];
let mut progresses = Vec::new();
let mut num_rows = 0;
for job in jobs.iter().take(max) {
let name = job
.display_name
.clone()
.unwrap_or_else(|| job.key.to_string());
columns[0].push(color_func(&name));
if job.num_substate_total() == 0 {
columns[1].push("".into());
columns[2].push("".into());
progresses.push(None);
} else {
let total = job.num_substate_total();
let finished = job.num_substate_finished();
columns[1].push(format!("{} /", finished).into());
columns[2].push(total.to_string().into());
progresses.push(Some(finished as f32 / total as f32));
}
num_rows += 1;
}
if jobs.len() > max {
num_rows += 1;
columns[0].push("...".into());
}
for row_index in 0..num_rows {
println!(
"{}",
draw_line(
terminal_width,
&mut columns,
row_index,
progresses
.get(row_index)
.map(|e| e.to_owned())
.unwrap_or_default(),
config
)
);
}
}
}
}
}
fn draw_line(
terminal_width: usize,
columns: &mut [Column],
row_index: usize,
maybe_progress: Option<f32>,
config: &SBStateConfig,
) -> String {
let mut line = String::new();
let mut line_len = 0;
if let Some(progress) = maybe_progress {
for column_index in 0..columns.len() {
line_len += columns[column_index].line_len();
if line_len > terminal_width {
break;
}
line += &format!("{}", columns[column_index].to_string(row_index));
}
line += &get_progress_bar(
progress,
terminal_width.checked_sub(line_len).unwrap_or_default(),
);
} else {
let mut effective_columns = columns
.iter_mut()
.filter(|c| !c.is_empty(row_index))
.collect::<Vec<_>>();
let num_effective_columns = effective_columns.len();
for (index, column) in effective_columns.iter_mut().enumerate() {
if index == num_effective_columns - 1 && config.grow_if_no_progress {
line += &format!(
"{}",
column.to_wide_string(
row_index,
terminal_width.checked_sub(line.len()).unwrap_or_default()
)
);
} else {
line += &format!("{}", column.to_string(row_index));
}
}
}
line
}
fn get_progress_bar(progress: f32, available_width: usize) -> String {
if available_width < 4 {
return String::new();
}
let available_width = available_width - 2;
let progress = progress.clamp(0.0, 1.0);
let bar_width = (progress * available_width as f32).round() as usize;
let remaining_width = available_width - bar_width.max(1);
format!(
"{}{}{}",
"[",
format!("{:=>bar_width$}", ">").bright_blue(),
format!("{:.>remaining_width$}", "]"),
)
}