Skip to content

Commit

Permalink
Lines are now tagged by an enum
Browse files Browse the repository at this point in the history
  • Loading branch information
spoutn1k committed Sep 26, 2024
1 parent d345567 commit 408b2cf
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 79 deletions.
154 changes: 89 additions & 65 deletions src/draw_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,13 @@ impl std::ops::DerefMut for DrawStateWrapper<'_> {
impl Drop for DrawStateWrapper<'_> {
fn drop(&mut self) {
if let Some(orphaned) = &mut self.orphan_lines {
orphaned.extend(self.state.lines.drain(..self.state.orphan_lines_count));
self.state.orphan_lines_count = 0;
orphaned.extend(
self.state
.lines
.iter()
.filter(|l| matches!(l, LineType::Text(_)))
.map(|l| String::from(l.as_ref())),
);
}
}
}
Expand Down Expand Up @@ -456,15 +461,60 @@ impl RateLimiter {

const MAX_BURST: u8 = 20;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum LineType {
Text(String),
Bar(String),
Empty,
}

impl AsRef<str> for LineType {
fn as_ref(&self) -> &str {
match self {
LineType::Text(s) => &s,
LineType::Bar(s) => &s,
LineType::Empty => "\n",
}
}
}

impl PartialEq<str> for LineType {
fn eq(&self, other: &str) -> bool {
match self {
LineType::Text(s) | LineType::Bar(s) => s == other,
LineType::Empty => other == "\n",
}
}
}

impl LineType {
/// Calculate the number of visual lines in the given line, accounting for
/// line wrapping and non-printable characters.
fn effective_height(&self, term_width: usize) -> usize {
match self {
LineType::Bar(_) | LineType::Empty => 1,
LineType::Text(s) => {
let line_width = console::measure_text_width(s);

// Calculate real height based on terminal width
// This take in account linewrap from terminal
let height = (line_width as f64 / term_width as f64).ceil() as usize;

// If the line is effectively empty (for example when it consists
// solely of ANSI color code sequences, count it the same as a
// new line. If the line is measured to be len = 0, we will
// subtract with overflow later.
usize::max(height, 1)
}
}
}
}

/// The drawn state of an element.
#[derive(Clone, Debug, Default)]
pub(crate) struct DrawState {
/// The lines to print (can contain ANSI codes)
pub(crate) lines: Vec<String>,
/// The number [`Self::lines`] entries that shouldn't be reaped by the next tick.
///
/// Note that this number may be different than the number of visual lines required to draw [`Self::lines`].
pub(crate) orphan_lines_count: usize,
pub(crate) lines: Vec<LineType>,
/// True if we should move the cursor up when possible instead of clearing lines.
pub(crate) move_cursor: bool,
/// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top`
Expand All @@ -473,21 +523,25 @@ pub(crate) struct DrawState {

impl DrawState {
fn draw_to_term(
&mut self,
&self,
term: &(impl TermLike + ?Sized),
last_line_count: &mut VisualLines,
bar_lines: &mut VisualLines,
) -> io::Result<()> {
if panicking() {
return Ok(());
}

let term_width = term.width() as usize;
let mut real_height = VisualLines::default();

if !self.lines.is_empty() && self.move_cursor {
// Move up to first line (assuming the last line doesn't contain a '\n') and then move to then front of the line
term.move_cursor_up(last_line_count.as_usize().saturating_sub(1))?;
// Move up to first line of progress bars (assuming the last line
// doesn't contain a '\n') and then move to then front of the line
term.move_cursor_up(bar_lines.as_usize().saturating_sub(1))?;
term.write_str("\r")?;
} else {
// Fork of console::clear_last_lines that assumes that the last line doesn't contain a '\n'
let n = last_line_count.as_usize();
let n = bar_lines.as_usize();
term.move_cursor_up(n.saturating_sub(1))?;
for i in 0..n {
term.clear_line()?;
Expand All @@ -498,11 +552,10 @@ impl DrawState {
term.move_cursor_up(n.saturating_sub(1))?;
}

let width = term.width() as usize;
let visual_lines = self.visual_line_count(.., width);
let visual_lines = self.visual_line_count(.., term_width);
let shift = match self.alignment {
MultiProgressAlignment::Bottom if visual_lines < *last_line_count => {
let shift = *last_line_count - visual_lines;
MultiProgressAlignment::Bottom if visual_lines < *bar_lines => {
let shift = *bar_lines - visual_lines;
for _ in 0..shift.as_usize() {
term.write_line("")?;
}
Expand All @@ -511,74 +564,45 @@ impl DrawState {
_ => VisualLines::default(),
};

let term_height = term.height() as usize;
let term_width = term.width() as usize;
let len = self.lines.len();
debug_assert!(self.orphan_lines_count <= self.lines.len());
let orphan_visual_line_count =
self.visual_line_count(..self.orphan_lines_count, term_width);
let mut real_len = VisualLines::default();
let mut last_line_filler = 0;
let text_lines = self
.lines
.iter()
.filter(|l| matches!(l, LineType::Text(_)))
.map(|l| l.effective_height(term_width))
.sum::<usize>()
.into();

for (idx, line) in self.lines.iter().enumerate() {
let line_width = console::measure_text_width(line);
let diff = if line.is_empty() {
// Empty line are new line
1
} else {
// Calculate real length based on terminal width
// This take in account linewrap from terminal
let terminal_len = (line_width as f64 / term_width as f64).ceil() as usize;
real_height += line.effective_height(term_width).into();

// If the line is effectively empty (for example when it consists
// solely of ANSI color code sequences, count it the same as a
// new line. If the line is measured to be len = 0, we will
// subtract with overflow later.
usize::max(terminal_len, 1)
}
.into();
// Have all orphan lines been drawn?
if self.orphan_lines_count <= idx {
// If so, then `real_len` should be at least `orphan_visual_line_count`.
debug_assert!(orphan_visual_line_count <= real_len);
// Don't consider orphan lines when comparing to terminal height.
if real_len - orphan_visual_line_count + diff > term_height.into() {
break;
}
}
real_len += diff;
if idx != 0 {
term.write_line("")?;
}

if idx + 1 != len || self.lines.len() == self.orphan_lines_count {
term.write_line(line)?;
} else {
// Don't append a '\n' if this is the last line and we're not
// just going to orphan all the lines
term.write_str(line)?;
}
term.write_str(line.as_ref())?;
}

if idx + 1 == len {
// Keep the cursor on the right terminal side
// So that next user writes/prints will happen on the next line
last_line_filler = term_width.saturating_sub(line_width);
}
// This is the bar lines count, store it for next iteration
*bar_lines = real_height - text_lines + shift;

// We just displayed some text, and if left as is, the progress bar will be appended to the
// text. We add a newline to make sure the progress bar is displayed on its own line.
if bar_lines.as_usize() == 0 && text_lines.as_usize() != 0 {
term.write_line("")?;
}
term.write_str(&" ".repeat(last_line_filler))?;

term.flush()?;
*last_line_count = real_len - orphan_visual_line_count + shift;

Ok(())
}

fn reset(&mut self) {
self.lines.clear();
self.orphan_lines_count = 0;
}

pub(crate) fn visual_line_count(
&self,
range: impl SliceIndex<[String], Output = [String]>,
range: impl SliceIndex<[LineType], Output = [LineType]>,
width: usize,
) -> VisualLines {
visual_line_count(&self.lines[range], width)
Expand Down
15 changes: 9 additions & 6 deletions src/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::thread::panicking;
use std::time::Instant;

use crate::draw_target::{
visual_line_count, DrawState, DrawStateWrapper, LineAdjust, ProgressDrawTarget, VisualLines,
visual_line_count, DrawState, DrawStateWrapper, LineAdjust, LineType, ProgressDrawTarget,
VisualLines,
};
use crate::progress_bar::ProgressBar;
#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -326,16 +327,18 @@ impl MultiState {
};

let mut draw_state = drawable.state();
draw_state.orphan_lines_count = self.orphan_lines.len();
draw_state.alignment = self.alignment;

if let Some(extra_lines) = &extra_lines {
draw_state.lines.extend_from_slice(extra_lines.as_slice());
draw_state.orphan_lines_count += extra_lines.len();
if let Some(ref extra_lines) = extra_lines {
draw_state
.lines
.extend(extra_lines.iter().cloned().map(LineType::Text));
}

// Add lines from `ProgressBar::println` call.
draw_state.lines.append(&mut self.orphan_lines);
draw_state
.lines
.extend(self.orphan_lines.iter().cloned().map(LineType::Text));

for index in &self.ordering {
let member = &self.members[*index];
Expand Down
7 changes: 3 additions & 4 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::time::Instant;
use instant::Instant;
use portable_atomic::{AtomicU64, AtomicU8, Ordering};

use crate::draw_target::ProgressDrawTarget;
use crate::draw_target::{LineType, ProgressDrawTarget};
use crate::style::ProgressStyle;

pub(crate) struct BarState {
Expand Down Expand Up @@ -144,15 +144,14 @@ impl BarState {
};

let mut draw_state = drawable.state();
let lines: Vec<String> = msg.lines().map(Into::into).collect();
let lines: Vec<LineType> = msg.lines().map(|s| LineType::Text(s.into())).collect();
// Empty msg should trigger newline as we are in println
if lines.is_empty() {
draw_state.lines.push(String::new());
draw_state.lines.push(LineType::Empty);
} else {
draw_state.lines.extend(lines);
}

draw_state.orphan_lines_count = draw_state.lines.len();
if let Some(width) = width {
if !matches!(self.state.status, Status::DoneHidden) {
self.style
Expand Down
9 changes: 5 additions & 4 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use instant::Instant;
#[cfg(feature = "unicode-segmentation")]
use unicode_segmentation::UnicodeSegmentation;

use crate::draw_target::LineType;
use crate::format::{
BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, HumanCount, HumanDuration,
HumanFloatCount,
Expand Down Expand Up @@ -226,7 +227,7 @@ impl ProgressStyle {
pub(crate) fn format_state(
&self,
state: &ProgressState,
lines: &mut Vec<String>,
lines: &mut Vec<LineType>,
target_width: u16,
) {
let mut cur = String::new();
Expand Down Expand Up @@ -376,7 +377,7 @@ impl ProgressStyle {

fn push_line(
&self,
lines: &mut Vec<String>,
lines: &mut Vec<LineType>,
cur: &mut String,
state: &ProgressState,
buf: &mut String,
Expand All @@ -394,11 +395,11 @@ impl ProgressStyle {
for (i, line) in expanded.split('\n').enumerate() {
// No newlines found in this case
if i == 0 && line.len() == expanded.len() {
lines.push(expanded);
lines.push(LineType::Bar(expanded));
break;
}

lines.push(line.to_string());
lines.push(LineType::Bar(line.to_string()));
}
}
}
Expand Down

0 comments on commit 408b2cf

Please sign in to comment.