From e958b461f67f0bcd5cf85058b52e6e71f3ec657a Mon Sep 17 00:00:00 2001 From: Nikkuss Date: Fri, 14 Nov 2025 21:21:14 +0000 Subject: [PATCH] work on bar display and stuff --- src/download_display.rs | 327 ++++++++++++++++++++++-------- src/handlers/download.rs | 191 +++++++++-------- src/handlers/substitute_status.rs | 36 +++- 3 files changed, 383 insertions(+), 171 deletions(-) diff --git a/src/download_display.rs b/src/download_display.rs index 4fcc102..5525a64 100644 --- a/src/download_display.rs +++ b/src/download_display.rs @@ -1,100 +1,251 @@ -pub fn format(&self, width: u16) { - let name = pad_string(&name, (width / 4) as usize); - let status = format!("Download {} |", name.purple().bold()); - let status_len = (width / 4) as usize + 10; +pub const DOWNLOAD_STYLE: owo_colors::Style = owo_colors::Style::new().blue().bold(); +pub const EXTRACT_STYLE: owo_colors::Style = owo_colors::Style::new().green().bold(); +use std::{borrow::Cow, fmt::Display, rc::Rc}; - if self.download_expected == 0 || self.extract_expected == 0 { - self.bar.set_message(format!("{status}")); - self.bar.tick(); - return; +use indicatif::HumanBytes; +use owo_colors::Style; + +use crate::multibar::MultiBar; + +pub enum StyledStringInner<'a> { + Cow(Cow<'a, str>), + Styled(Box>), +} + +impl<'a> From> for StyledStringInner<'a> { + fn from(value: Cow<'a, str>) -> Self { + StyledStringInner::Cow(value) } +} +impl<'a> From> for StyledStringInner<'a> { + fn from(value: StyledString<'a>) -> Self { + StyledStringInner::Styled(Box::new(value)) + } +} +impl<'a> StyledStringInner<'a> { + pub fn len(&self) -> usize { + match self { + StyledStringInner::Cow(cow) => cow.len(), + StyledStringInner::Styled(styled) => styled.len(), + } + } + pub fn pad_to_width(self, width: usize) -> StyledStringInner<'a> { + let len = self.len(); + if len == width { + self + } else if len > width { + match self { + StyledStringInner::Cow(cow) => StyledStringInner::Cow(Cow::Owned( + cow.chars() + .take(width.saturating_sub(2)) + .collect::() + + "..", + )), + StyledStringInner::Styled(styled) => styled.pad_to_width(width).into(), + } + } else { + match self { + StyledStringInner::Cow(cow) => StyledStringInner::Cow(Cow::Owned(format!( + "{}{}", + cow, + " ".repeat(width - len) + ))), + StyledStringInner::Styled(styled) => styled.pad_to_width(width).into(), + } + } + } +} - let total_expected = self.download_expected + self.extract_expected; - let total_done = self.download_done + self.extract_done; +pub struct StyledString<'a> { + inner: StyledStringInner<'a>, + style: Style, +} - let dl_percent = ((self.download_done as f64 / self.download_expected as f64) * 100.0) as u64; - let ex_percent = ((self.extract_done as f64 / self.extract_expected as f64) * 100.0) as u64; +impl<'a> StyledString<'a> { + pub fn new(inner: impl Into>, style: Style) -> Self { + Self { + inner: inner.into(), + style, + } + } + pub fn len(&self) -> usize { + self.inner.len() + } + pub fn pad_to_width(self, width: usize) -> StyledString<'a> { + if self.inner.len() == width { + self + } else { + StyledString { + inner: self.inner.pad_to_width(width), + style: self.style, + } + } + } +} - let download_per_sec = - HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64).to_string(); - let extract_per_sec = - HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64).to_string(); - let download_done_human = HumanBytes(self.download_done).to_string(); - let download_done_human = pad_string(&download_done_human, INFO_WIDTH) - .blue() - .bold() - .to_string(); - let extract_done_human = HumanBytes(self.extract_done).to_string(); - let extract_done_human = pad_string(&extract_done_human, INFO_WIDTH) - .green() - .bold() - .to_string(); - let download_per_sec = pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH) - .blue() - .bold() - .to_string(); - let extract_per_sec = pad_string(&format!("{extract_per_sec}/s"), INFO_WIDTH) - .green() - .bold() - .to_string(); - let work_per_sec = if self.download_done < self.download_expected { - download_per_sec - } else { - extract_per_sec - }; +impl<'a> Display for StyledStringInner<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StyledStringInner::Cow(cow) => write!(f, "{}", cow), + StyledStringInner::Styled(styled) => write!(f, "{}", styled), + } + } +} - let display = format!("{work_per_sec} | {download_done_human} | {extract_done_human} "); - let display_length = (INFO_WIDTH * 3) + 9; +impl<'a> Display for StyledString<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.style.style(self.inner.to_string())) + } +} - // + 6 to account for final format - let total_length = status_len + display_length + 4; +// {prefix} {name} | {work_per_sec} | {download_done_human} | {extract_done_human} [bar] +// +// +const NAME_WIDTH_CUT: usize = 4; +const NAME_WIDTH_MIN: usize = 5; +const INFO_WIDTH: usize = 12; - let min = dl_percent.min(ex_percent); - let dl = dl_percent.saturating_sub(min); - let len = total_length as u16; - // if width <= len { - // self.bar.set_message(format!( - // "{}: {}/{}", - // name, - // total_done, - // if total_expected == 0 { - // "-".to_string() - // } else { - // total_expected.to_string() - // } - // )); - // self.bar.tick(); - // return; - // } - let msg = match width { - 0..60 => { +pub fn style_bar<'s, const N: usize>( + prefix: StyledString, + name: StyledString, + work_per_sec: StyledString, + download_done: usize, + extract_done: usize, + bar: MultiBar<'s, N>, + + width: u16, +) -> String { + let name = name.pad_to_width((width as usize / NAME_WIDTH_CUT).max(NAME_WIDTH_MIN)); + let status = format!("{} {}", prefix, name); + let remaining_width = width.saturating_sub((name.len() + prefix.len()) as u16 + 6); + if remaining_width == 0 { + return status; + } + let possible_chunks = remaining_width.saturating_div(INFO_WIDTH as u16 + 3); + let possible_chunks = possible_chunks.min(3); + let leftover_width = remaining_width.saturating_sub(possible_chunks * (INFO_WIDTH as u16 + 3)); + let download_done_human = StyledString::new( + Cow::Owned(HumanBytes(download_done as u64).to_string()), + DOWNLOAD_STYLE, + ) + .pad_to_width(INFO_WIDTH); + let extract_done_human = StyledString::new( + Cow::Owned(HumanBytes(extract_done as u64).to_string()), + EXTRACT_STYLE, + ) + .pad_to_width(INFO_WIDTH); + let work_per_sec = work_per_sec.pad_to_width(INFO_WIDTH); + let bar = bar.scale(leftover_width as u64).to_string(); + + match possible_chunks { + 0 => format!("{status} | {download_done_human}"), + 1 => format!("{status} | {download_done_human} | [{bar}]"), + 2 => format!("{status} | {work_per_sec} | {download_done_human} | [{bar}]"), + _ => { format!( - "{}: {}/{}", - name, - total_done, - if total_expected == 0 { - "-".to_string() - } else { - total_expected.to_string() - } + "{status} | {work_per_sec} | {download_done_human} | {extract_done_human} | [{bar}]" ) } - _ => { - let bar = MultiBar([ - BarSegment::Dynamic(&DONE_CHAR, min), - BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), - BarSegment::Dynamic(" ", 100 - min - dl), - ]) - .scale((width.saturating_sub(total_length as u16)) as u64); - let bar = if width > display_length as u16 + status_len as u16 { - format!("[{}]", bar) - } else { - String::new() - }; - - format!("{status} {display} {bar}",) - } - }; - self.bar.set_message(msg); - self.bar.tick(); + } } + +// pub fn format(width: u16) { +// let name = pad_string(&name, (width / 4) as usize); +// let status = format!("Download {} |", name.purple().bold()); +// let status_len = (width / 4) as usize + 10; +// +// if self.download_expected == 0 || self.extract_expected == 0 { +// self.bar.set_message(format!("{status}")); +// self.bar.tick(); +// return; +// } +// +// let total_expected = self.download_expected + self.extract_expected; +// let total_done = self.download_done + self.extract_done; +// +// let dl_percent = ((self.download_done as f64 / self.download_expected as f64) * 100.0) as u64; +// let ex_percent = ((self.extract_done as f64 / self.extract_expected as f64) * 100.0) as u64; +// +// let download_per_sec = +// HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64).to_string(); +// let extract_per_sec = +// HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64).to_string(); +// let download_done_human = HumanBytes(self.download_done).to_string(); +// let download_done_human = pad_string(&download_done_human, INFO_WIDTH) +// .blue() +// .bold() +// .to_string(); +// let extract_done_human = HumanBytes(self.extract_done).to_string(); +// let extract_done_human = pad_string(&extract_done_human, INFO_WIDTH) +// .green() +// .bold() +// .to_string(); +// let download_per_sec = pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH) +// .blue() +// .bold() +// .to_string(); +// let extract_per_sec = pad_string(&format!("{extract_per_sec}/s"), INFO_WIDTH) +// .green() +// .bold() +// .to_string(); +// let work_per_sec = if self.download_done < self.download_expected { +// download_per_sec +// } else { +// extract_per_sec +// }; +// +// let display = format!("{work_per_sec} | {download_done_human} | {extract_done_human} "); +// let display_length = (INFO_WIDTH * 3) + 9; +// +// // + 6 to account for final format +// let total_length = status_len + display_length + 4; +// +// let min = dl_percent.min(ex_percent); +// let dl = dl_percent.saturating_sub(min); +// let len = total_length as u16; +// // if width <= len { +// // self.bar.set_message(format!( +// // "{}: {}/{}", +// // name, +// // total_done, +// // if total_expected == 0 { +// // "-".to_string() +// // } else { +// // total_expected.to_string() +// // } +// // )); +// // self.bar.tick(); +// // return; +// // } +// let msg = match width { +// 0..60 => { +// format!( +// "{}: {}/{}", +// name, +// total_done, +// if total_expected == 0 { +// "-".to_string() +// } else { +// total_expected.to_string() +// } +// ) +// } +// _ => { +// let bar = MultiBar([ +// BarSegment::Dynamic(&DONE_CHAR, min), +// BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), +// BarSegment::Dynamic(" ", 100 - min - dl), +// ]) +// .scale((width.saturating_sub(total_length as u16)) as u64); +// let bar = if width > display_length as u16 + status_len as u16 { +// format!("[{}]", bar) +// } else { +// String::new() +// }; +// +// format!("{status} {display} {bar}",) +// } +// }; +// self.bar.set_message(msg); +// self.bar.tick(); +// } diff --git a/src/handlers/download.rs b/src/handlers/download.rs index 98fda14..b2c55b6 100644 --- a/src/handlers/download.rs +++ b/src/handlers/download.rs @@ -1,10 +1,11 @@ -use std::{sync::LazyLock, time::Instant}; +use std::{borrow::Cow, sync::LazyLock, time::Instant}; use indicatif::{HumanBytes, ProgressBar}; -use owo_colors::OwoColorize; +use owo_colors::{OwoColorize, Style}; use crate::{ action::{Action, BuildStepId, ResultFields, StartFields}, + download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar}, estimator::Estimator, handlers::{Handler, fetch::FetchHandler}, multibar::{BarSegment, MultiBar}, @@ -57,11 +58,9 @@ impl Handler for CopyHandler { parent, .. } if parent == &self.0 => { - let bar = state.add_pb(ProgressBar::new(1)); state.plug(DownloadHandler::new( state.term_width, *id, - bar, path.to_string(), )); // Handle global start action @@ -77,7 +76,7 @@ pub struct DownloadHandler { pub copy_id: BuildStepId, pub download_estimator: Estimator, pub extract_estimator: Estimator, - pub bar: ProgressBar, + pub bar: Option, pub path: String, pub download_expected: u64, pub download_done: u64, @@ -87,14 +86,13 @@ pub struct DownloadHandler { } impl DownloadHandler { - pub fn new(width: u16, copy_id: BuildStepId, bar: ProgressBar, path: String) -> Self { - bar.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap()); + pub fn new(width: u16, copy_id: BuildStepId, path: String) -> Self { let new = Self { id: u64::MAX.into(), copy_id, download_estimator: Estimator::new(Instant::now()), extract_estimator: Estimator::new(Instant::now()), - bar, + bar: None, path, download_expected: 0, download_done: 0, @@ -106,6 +104,9 @@ impl DownloadHandler { new } pub fn draw_bar(&self, width: u16) { + let Some(bar) = &self.bar else { + return; + }; let name = nix_path::extract_package_name_string(&self.path) .unwrap_or_else(|| "unknown".to_string()); let name = pad_string(&name, (width / 4) as usize); @@ -113,55 +114,82 @@ impl DownloadHandler { let status_len = (width / 4) as usize + 10; if self.download_expected == 0 || self.extract_expected == 0 { - self.bar.set_message(format!("{status}")); - self.bar.tick(); + bar.set_message(format!("{status}")); + bar.tick(); return; } - let total_expected = self.download_expected + self.extract_expected; - let total_done = self.download_done + self.extract_done; + let work_per_sec = if self.download_done < self.download_expected { + StyledString::new( + Cow::Owned(format!( + "{}", + HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64) + )), + DOWNLOAD_STYLE, + ) + } else { + StyledString::new( + Cow::Owned(format!( + "{}", + HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64) + )), + EXTRACT_STYLE, + ) + }; let dl_percent = ((self.download_done as f64 / self.download_expected as f64) * 100.0) as u64; let ex_percent = ((self.extract_done as f64 / self.extract_expected as f64) * 100.0) as u64; - - let download_per_sec = - HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64).to_string(); - let extract_per_sec = - HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64).to_string(); - let download_done_human = HumanBytes(self.download_done).to_string(); - let download_done_human = pad_string(&download_done_human, INFO_WIDTH) - .blue() - .bold() - .to_string(); - let extract_done_human = HumanBytes(self.extract_done).to_string(); - let extract_done_human = pad_string(&extract_done_human, INFO_WIDTH) - .green() - .bold() - .to_string(); - let download_per_sec = pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH) - .blue() - .bold() - .to_string(); - let extract_per_sec = pad_string(&format!("{extract_per_sec}/s"), INFO_WIDTH) - .green() - .bold() - .to_string(); - let work_per_sec = if self.download_done < self.download_expected { - download_per_sec - } else { - extract_per_sec - }; - - let display = format!("{work_per_sec} | {download_done_human} | {extract_done_human} "); - let display_length = (INFO_WIDTH * 3) + 9; - - // + 6 to account for final format - let total_length = status_len + display_length + 4; - let min = dl_percent.min(ex_percent); let dl = dl_percent.saturating_sub(min); - let len = total_length as u16; + let mbar = MultiBar([ + BarSegment::Dynamic(&DONE_CHAR, min), + BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), + BarSegment::Dynamic(" ", 100 - min - dl), + ]); + let msg = style_bar( + StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), + StyledString::new(Cow::Owned(name), Style::new().purple()), + work_per_sec, + self.download_done as usize, + self.extract_done as usize, + mbar, + width, + ); + + // let total_expected = self.download_expected + self.extract_expected; + // let total_done = self.download_done + self.extract_done; + // + // + // let download_per_sec = + // HumanBytes(self.download_estimator.steps_per_second(Instant::now()) as u64).to_string(); + // let extract_per_sec = + // HumanBytes(self.extract_estimator.steps_per_second(Instant::now()) as u64).to_string(); + // let download_done_human = HumanBytes(self.download_done).to_string(); + // let download_done_human = pad_string(&download_done_human, INFO_WIDTH) + // .blue() + // .bold() + // .to_string(); + // let extract_done_human = HumanBytes(self.extract_done).to_string(); + // let extract_done_human = pad_string(&extract_done_human, INFO_WIDTH) + // .green() + // .bold() + // .to_string(); + // let download_per_sec = pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH) + // .blue() + // .bold() + // .to_string(); + // let extract_per_sec = pad_string(&format!("{extract_per_sec}/s"), INFO_WIDTH) + // .green() + // .bold() + // .to_string(); + // + // let display = format!("{work_per_sec} | {download_done_human} | {extract_done_human} "); + // let display_length = (INFO_WIDTH * 3) + 9; + // + // // + 6 to account for final format + // let total_length = status_len + display_length + 4; + // // if width <= len { // self.bar.set_message(format!( // "{}: {}/{}", @@ -176,37 +204,32 @@ impl DownloadHandler { // self.bar.tick(); // return; // } - let msg = match width { - 0..60 => { - format!( - "{}: {}/{}", - name, - total_done, - if total_expected == 0 { - "-".to_string() - } else { - total_expected.to_string() - } - ) - } - _ => { - let bar = MultiBar([ - BarSegment::Dynamic(&DONE_CHAR, min), - BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), - BarSegment::Dynamic(" ", 100 - min - dl), - ]) - .scale((width.saturating_sub(total_length as u16)) as u64); - let bar = if width > display_length as u16 + status_len as u16 { - format!("[{}]", bar) - } else { - String::new() - }; - - format!("{status} {display} {bar}",) - } - }; - self.bar.set_message(msg); - self.bar.tick(); + // let msg = match width { + // 0..60 => { + // format!( + // "{}: {}/{}", + // name, + // total_done, + // if total_expected == 0 { + // "-".to_string() + // } else { + // total_expected.to_string() + // } + // ) + // } + // _ => { + // .scale((width.saturating_sub(total_length as u16)) as u64); + // let bar = if width > display_length as u16 + status_len as u16 { + // format!("[{}]", bar) + // } else { + // String::new() + // }; + // + // format!("{status} {display} {bar}",) + // } + // }; + bar.set_message(msg); + bar.tick(); } } @@ -248,11 +271,19 @@ impl Handler for DownloadHandler { self.extract_estimator.record(*done, Instant::now()); self.draw_bar(state.term_width); } + if self.download_expected > 0 && self.extract_expected > 0 && self.bar.is_none() { + let bar = state.add_pb(ProgressBar::new(1)); + bar.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap()); + self.bar = Some(bar); + self.draw_bar(state.term_width); + } Ok(true) } Action::Stop { id } if id == &self.copy_id => { - self.bar.finish_and_clear(); + if let Some(bar) = &self.bar { + bar.finish_and_clear(); + } Ok(false) } _ => Ok(true), diff --git a/src/handlers/substitute_status.rs b/src/handlers/substitute_status.rs index 70524c7..a6657ec 100644 --- a/src/handlers/substitute_status.rs +++ b/src/handlers/substitute_status.rs @@ -1,10 +1,11 @@ -use std::{collections::HashMap, sync::LazyLock, time::Instant}; +use std::{borrow::Cow, collections::HashMap, sync::LazyLock, time::Instant}; use indicatif::{HumanBytes, ProgressBar}; -use owo_colors::OwoColorize; +use owo_colors::{OwoColorize, Style}; use crate::{ action::{Action, ActionType, BuildStepId, ResultFields, StartFields}, + download_display::{StyledString, style_bar}, estimator::Estimator, handlers::{Handler, fetch::FetchHandler}, multibar::{BarSegment, MultiBar}, @@ -31,6 +32,8 @@ impl Handler for CopyPathsHandler { } => { state.println(format!("CopyPaths start"))?; let progress = state.add_pb(ProgressBar::new(1)); + + progress.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap()); state.plug(SubstitutionStatusHandler::new( id, state.term_width, @@ -84,7 +87,34 @@ impl SubstitutionStatusHandler { self.state_copy.values().map(|&[done, ..]| done).sum() } - fn draw_bar(&mut self, width: u16) {} + fn draw_bar(&mut self, width: u16) { + if self.max_transfer == 0 || self.max_copy == 0 { + return; + } + let dl_percent = ((self.get_done() as f64 / self.max_transfer as f64) * 100.0) as u64; + let ex_percent = ((self.get_unpacked() as f64 / self.max_copy as f64) * 100.0) as u64; + let min = dl_percent.min(ex_percent); + let dl = dl_percent.saturating_sub(min); + let mbar = MultiBar([ + BarSegment::Dynamic(&DONE_CHAR, min), + BarSegment::Dynamic(&DOWNLOAD_CHAR, dl), + BarSegment::Dynamic(" ", 100 - min - dl), + ]); + let msg = style_bar( + StyledString::new(Cow::Owned("Downloading".to_string()), Style::new()), + StyledString::new( + Cow::Owned(format!("{}/{}", self.state_self[0], self.state_self[1])), + Style::new().purple(), + ), + StyledString::new(Cow::Owned(format!("awa")), Style::new().yellow()), + self.get_done() as usize, + self.get_unpacked() as usize, + mbar, + width, + ); + self.progress.set_message(msg); + self.progress.tick(); + } } impl Handler for SubstitutionStatusHandler {