Skip to content

Commit

Permalink
Move all height calculation to LineType; add comments
Browse files Browse the repository at this point in the history
  • Loading branch information
spoutn1k committed Nov 11, 2024
1 parent 49f468b commit 7e5a0bc
Showing 1 changed file with 56 additions and 36 deletions.
92 changes: 56 additions & 36 deletions src/draw_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,22 @@ impl LineType {
LineType::Empty => "",
}
}

fn console_width(&self) -> usize {
console::measure_text_width(self.inner())
}

fn wrapped_height(&self, width: usize) -> VisualLines {
// Calculate real length based on terminal width
// This take in account linewrap from terminal
let terminal_len = (self.console_width() as f64 / 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(terminal_len, 1).into()
}
}

impl AsRef<str> for LineType {
Expand All @@ -500,6 +516,10 @@ impl PartialEq<str> for LineType {
}

impl DrawState {
/// Draw the current state to the terminal
/// We expect a few things:
/// - self.lines contains n lines of text/empty then m lines of bars
/// - None of those lines contain newlines
fn draw_to_term(
&mut self,
term: &(impl TermLike + ?Sized),
Expand Down Expand Up @@ -539,9 +559,18 @@ impl DrawState {

// Sanity checks
debug_assert!(full_height == text_height + bar_height);
debug_assert!(self.orphan_lines_count <= self.lines.len());
debug_assert!(
self.orphan_lines_count
== self
.lines
.iter()
.filter(|l| matches!(l, LineType::Text(_) | LineType::Empty))
.count()
);

let shift = match self.alignment {
// If we align to the bottom and the new height is less than before, clear the lines
// that are not used by the new content.
MultiProgressAlignment::Bottom if full_height < *bar_count => {
let shift = *bar_count - full_height;
for _ in 0..shift.as_usize() {
Expand All @@ -558,44 +587,34 @@ impl DrawState {
let mut real_height = VisualLines::default();

for (idx, line) in self.lines.iter().enumerate() {
let line_width = console::measure_text_width(line.inner());
let diff = if line.inner().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;

// 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();
let line_height = line.wrapped_height(term_width);

// Have all orphan lines been drawn?
// Check here for bar lines that exceed the terminal height
if self.orphan_lines_count <= idx {
// If so, then `real_height` should be at least `orphan_visual_line_count`.
// If all the orphan lines have been drawn, then `real_height` should be
// at least `orphan_visual_line_count`.
debug_assert!(text_height <= real_height);

// Don't consider orphan lines when comparing to terminal height.
if real_height - text_height + diff > term.height().into() {
if real_height - text_height + line_height > term.height().into() {
break;
}
}

real_height += diff;
real_height += line_height;

// Print a new line if this is not the first line printed this tick
// the first line will automatically wrap due to the filler below
if idx != 0 {
term.write_line("")?;
}

term.write_str(line.inner())?;

if idx + 1 == self.lines.len() {
// Keep the cursor on the right terminal side
// So that next user writes/prints will happen on the next line
let last_line_filler = term_width.saturating_sub(line_width);
// For the last line of the output, keep the cursor on the right terminal
// side so that next user writes/prints will happen on the next line
let last_line_filler = line_height.as_usize() * term_width - line.console_width();
term.write_str(&" ".repeat(last_line_filler))?;
}
}
Expand Down Expand Up @@ -667,21 +686,15 @@ impl Sub for VisualLines {

/// Calculate the number of visual lines in the given lines, after
/// accounting for line wrapping and non-printable characters.
pub(crate) fn visual_line_count(lines: &[impl AsRef<str>], width: usize) -> VisualLines {
let mut real_lines = 0;
for line in lines {
let effective_line_length = console::measure_text_width(line.as_ref());
real_lines += usize::max(
(effective_line_length as f64 / width as f64).ceil() as usize,
1,
);
}

real_lines.into()
pub(crate) fn visual_line_count(lines: &[LineType], width: usize) -> VisualLines {
lines.iter().fold(VisualLines::default(), |acc, line| {
acc.saturating_add(line.wrapped_height(width))
})
}

#[cfg(test)]
mod tests {
use crate::draw_target::LineType;
use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};

#[test]
Expand Down Expand Up @@ -775,7 +788,14 @@ mod tests {
];

for case in lines_and_expectations.iter() {
let result = super::visual_line_count(case.lines, case.width);
let result = super::visual_line_count(
&case
.lines
.iter()
.map(|s| LineType::Text(s.to_string()))
.collect::<Vec<_>>(),
case.width,
);
assert_eq!(result, case.expectation.into(), "case: {:?}", case);
}
}
Expand Down

0 comments on commit 7e5a0bc

Please sign in to comment.