From 850c1d0234b2c1ae09a8f1c8f669e23a324fd644 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 9 Apr 2024 19:37:39 +0200 Subject: Add progress bar to list --- src/progress_bar.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/progress_bar.rs (limited to 'src/progress_bar.rs') diff --git a/src/progress_bar.rs b/src/progress_bar.rs new file mode 100644 index 0000000..b4abbfc --- /dev/null +++ b/src/progress_bar.rs @@ -0,0 +1,41 @@ +use anyhow::{bail, Result}; +use std::fmt::Write; + +pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result { + if progress > total { + bail!("The progress of the progress bar is higher than the maximum"); + } + + // "Progress: [".len() == 11 + // "] xxx/xxx".len() == 9 + // 11 + 9 = 20 + let wrapper_width = 20; + + // If the line width is too low for a progress bar, just show the ratio. + if line_width < wrapper_width + 4 { + return Ok(format!("Progress: {progress}/{total}")); + } + + let mut line = String::with_capacity(usize::from(line_width)); + line.push_str("Progress: ["); + + let remaining_width = line_width.saturating_sub(wrapper_width); + let filled = (remaining_width * progress) / total; + + for _ in 0..filled { + line.push('='); + } + + if filled < remaining_width { + line.push('>'); + } + + for _ in 0..(remaining_width - filled).saturating_sub(1) { + line.push(' '); + } + + line.write_fmt(format_args!("] {progress:>3}/{total:<3}")) + .unwrap(); + + Ok(line) +} -- cgit v1.2.3 From a8ddc07a9aea5b2e3840a7b6e0eb20f2189bdd60 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 9 Apr 2024 22:15:41 +0200 Subject: Add "exercises" to the end of the progress bar --- src/progress_bar.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/progress_bar.rs') diff --git a/src/progress_bar.rs b/src/progress_bar.rs index b4abbfc..ee55ba7 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -7,13 +7,13 @@ pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result 99) + // 11 + 19 = 30 + let wrapper_width = 30; // If the line width is too low for a progress bar, just show the ratio. if line_width < wrapper_width + 4 { - return Ok(format!("Progress: {progress}/{total}")); + return Ok(format!("Progress: {progress}/{total} exercises")); } let mut line = String::with_capacity(usize::from(line_width)); @@ -34,7 +34,7 @@ pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result3}/{total:<3}")) + line.write_fmt(format_args!("] {progress:>3}/{total} exercises")) .unwrap(); Ok(line) -- cgit v1.2.3 From 4a80bf64411f228c35c173b6188df5114d4c52fa Mon Sep 17 00:00:00 2001 From: mo8it Date: Wed, 10 Apr 2024 00:42:32 +0200 Subject: Colorize the progress bar --- src/list/state.rs | 6 ++-- src/progress_bar.rs | 92 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 21 deletions(-) (limited to 'src/progress_bar.rs') diff --git a/src/list/state.rs b/src/list/state.rs index b67c624..8918979 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -7,7 +7,7 @@ use ratatui::{ Frame, }; -use crate::{exercise::Exercise, progress_bar::progress_bar, state_file::StateFile}; +use crate::{exercise::Exercise, progress_bar::progress_bar_ratatui, state_file::StateFile}; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Filter { @@ -166,11 +166,11 @@ impl UiState { ); frame.render_widget( - Paragraph::new(Span::raw(progress_bar( + Paragraph::new(progress_bar_ratatui( self.progress, self.exercises.len() as u16, area.width, - )?)) + )?) .block(Block::default().borders(Borders::BOTTOM)), Rect { x: 0, diff --git a/src/progress_bar.rs b/src/progress_bar.rs index ee55ba7..97c8ad9 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -1,41 +1,97 @@ use anyhow::{bail, Result}; +use ratatui::text::{Line, Span}; use std::fmt::Write; +const PREFIX: &str = "Progress: ["; +const PREFIX_WIDTH: u16 = PREFIX.len() as u16; +// Leaving the last char empty (_) for `total` > 99. +const POSTFIX_WIDTH: u16 = "] xxx/xx exercises_".len() as u16; +const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH; +const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4; + +const PROGRESS_EXCEEDS_MAX_ERR: &str = + "The progress of the progress bar is higher than the maximum"; + pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result { + use crossterm::style::Stylize; + if progress > total { - bail!("The progress of the progress bar is higher than the maximum"); + bail!(PROGRESS_EXCEEDS_MAX_ERR); } - // "Progress: [".len() == 11 - // "] xxx/xx exercises_".len() == 19 (leaving the last char empty for `total` > 99) - // 11 + 19 = 30 - let wrapper_width = 30; - - // If the line width is too low for a progress bar, just show the ratio. - if line_width < wrapper_width + 4 { + if line_width < MIN_LINE_WIDTH { return Ok(format!("Progress: {progress}/{total} exercises")); } let mut line = String::with_capacity(usize::from(line_width)); - line.push_str("Progress: ["); + line.push_str(PREFIX); - let remaining_width = line_width.saturating_sub(wrapper_width); - let filled = (remaining_width * progress) / total; + let width = line_width - WRAPPER_WIDTH; + let filled = (width * progress) / total; + let mut green_part = String::with_capacity(usize::from(filled + 1)); for _ in 0..filled { - line.push('='); + green_part.push('#'); } - if filled < remaining_width { - line.push('>'); + if filled < width { + green_part.push('>'); } + write!(line, "{}", green_part.green()).unwrap(); - for _ in 0..(remaining_width - filled).saturating_sub(1) { - line.push(' '); + let width_minus_filled = width - filled; + if width_minus_filled > 1 { + let red_part_width = width_minus_filled - 1; + let mut red_part = String::with_capacity(usize::from(red_part_width)); + for _ in 0..red_part_width { + red_part.push('-'); + } + write!(line, "{}", red_part.red()).unwrap(); } - line.write_fmt(format_args!("] {progress:>3}/{total} exercises")) - .unwrap(); + write!(line, "] {progress:>3}/{total} exercises").unwrap(); Ok(line) } + +pub fn progress_bar_ratatui(progress: u16, total: u16, line_width: u16) -> Result> { + use ratatui::style::Stylize; + + if progress > total { + bail!(PROGRESS_EXCEEDS_MAX_ERR); + } + + if line_width < MIN_LINE_WIDTH { + return Ok(Line::raw(format!("Progress: {progress}/{total} exercises"))); + } + + let mut spans = Vec::with_capacity(4); + spans.push(Span::raw(PREFIX)); + + let width = line_width - WRAPPER_WIDTH; + let filled = (width * progress) / total; + + let mut green_part = String::with_capacity(usize::from(filled + 1)); + for _ in 0..filled { + green_part.push('#'); + } + + if filled < width { + green_part.push('>'); + } + spans.push(green_part.green()); + + let width_minus_filled = width - filled; + if width_minus_filled > 1 { + let red_part_width = width_minus_filled - 1; + let mut red_part = String::with_capacity(usize::from(red_part_width)); + for _ in 0..red_part_width { + red_part.push('-'); + } + spans.push(red_part.red()); + } + + spans.push(Span::raw(format!("] {progress:>3}/{total} exercises"))); + + Ok(Line::from(spans)) +} -- cgit v1.2.3 From a59acf88354c8dfba301e59173653bc9a5f4bfb2 Mon Sep 17 00:00:00 2001 From: mo8it Date: Wed, 10 Apr 2024 14:29:31 +0200 Subject: Show the current exercise path --- src/progress_bar.rs | 2 +- src/watch/state.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src/progress_bar.rs') diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 97c8ad9..d6962b8 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -49,7 +49,7 @@ pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result3}/{total} exercises").unwrap(); + writeln!(line, "] {progress:>3}/{total} exercises").unwrap(); Ok(line) } diff --git a/src/watch/state.rs b/src/watch/state.rs index 751285f..da5ac3d 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -149,6 +149,12 @@ You can keep working on this exercise or jump into the next one by removing the let progress_bar = progress_bar(self.progress, self.exercises.len() as u16, line_width)?; self.writer.write_all(progress_bar.as_bytes())?; + self.writer.write_all(b"Current exercise: ")?; + self.writer.write_fmt(format_args!( + "{}", + self.exercise.path.to_string_lossy().bold() + ))?; + self.show_prompt()?; Ok(()) -- cgit v1.2.3 From c8481d35c120ff99213e6ed73ba889e51cac10c5 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 14 May 2024 01:49:22 +0200 Subject: Done documentation --- src/progress_bar.rs | 3 +++ src/run.rs | 6 +----- src/watch.rs | 5 +++-- src/watch/notify_event.rs | 5 +++-- src/watch/state.rs | 9 +++++---- 5 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src/progress_bar.rs') diff --git a/src/progress_bar.rs b/src/progress_bar.rs index d6962b8..4a54170 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -12,6 +12,7 @@ const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4; const PROGRESS_EXCEEDS_MAX_ERR: &str = "The progress of the progress bar is higher than the maximum"; +/// Terminal progress bar to be used when not using Ratataui. pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result { use crossterm::style::Stylize; @@ -54,6 +55,8 @@ pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result Result> { use ratatui::style::Stylize; diff --git a/src/run.rs b/src/run.rs index ac97414..36899b9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -41,11 +41,7 @@ pub fn run(app_state: &mut AppState) -> Result<()> { match app_state.done_current_exercise(&mut stdout)? { ExercisesProgress::AllDone => (), - ExercisesProgress::CurrentPending => println!( - "Current exercise: {}", - app_state.current_exercise().terminal_link(), - ), - ExercisesProgress::NewPending => println!( + ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => println!( "Next exercise: {}", app_state.current_exercise().terminal_link(), ), diff --git a/src/watch.rs b/src/watch.rs index 2fbc533..88a1230 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -14,7 +14,7 @@ use std::{ use crate::app_state::{AppState, ExercisesProgress}; use self::{ - notify_event::DebounceEventHandler, + notify_event::NotifyEventHandler, state::WatchState, terminal_event::{terminal_event_handler, InputEvent}, }; @@ -40,6 +40,7 @@ pub enum WatchExit { List, } +/// `notify_exercise_names` as None activates the manual run mode. pub fn watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, @@ -52,7 +53,7 @@ pub fn watch( let _debouncer_guard = if let Some(exercise_names) = notify_exercise_names { let mut debouncer = new_debouncer( Duration::from_millis(200), - DebounceEventHandler { + NotifyEventHandler { tx: tx.clone(), exercise_names, }, diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs index a224377..7471640 100644 --- a/src/watch/notify_event.rs +++ b/src/watch/notify_event.rs @@ -3,12 +3,13 @@ use std::sync::mpsc::Sender; use super::WatchEvent; -pub struct DebounceEventHandler { +pub struct NotifyEventHandler { pub tx: Sender, + /// Used to report which exercise was modified. pub exercise_names: &'static [&'static [u8]], } -impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler { +impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler { fn handle_event(&mut self, input_event: DebounceEventResult) { let output_event = match input_event { Ok(input_event) => { diff --git a/src/watch/state.rs b/src/watch/state.rs index abd21fb..14c3f01 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crossterm::{ style::{style, Stylize}, - terminal::size, + terminal, }; use std::io::{self, StdoutLock, Write}; @@ -84,6 +84,7 @@ impl<'a> WatchState<'a> { self.run_current_exercise() } + /// Move on to the next exercise if the current one is done. pub fn next_exercise(&mut self) -> Result { if self.done_status == DoneStatus::Pending { return Ok(ExercisesProgress::CurrentPending); @@ -113,7 +114,7 @@ impl<'a> WatchState<'a> { } pub fn render(&mut self) -> Result<()> { - // Prevent having the first line shifted. + // Prevent having the first line shifted if clearing wasn't successful. self.writer.write_all(b"\n")?; clear_terminal(&mut self.writer)?; @@ -145,11 +146,11 @@ When you are done experimenting, enter `n` to move on to the next exercise 🦀" writeln!( self.writer, "A solution file can be found at {}\n", - style(TerminalFileLink(solution_path)).underlined().green() + style(TerminalFileLink(solution_path)).underlined().green(), )?; } - let line_width = size()?.0; + let line_width = terminal::size()?.0; let progress_bar = progress_bar( self.app_state.n_done(), self.app_state.exercises().len() as u16, -- cgit v1.2.3