Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultiProgress: add println and suspend implementation #351

Merged
merged 6 commits into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@ fn main() {
let pb3 = m.insert_after(&pb2, ProgressBar::new(1024));
pb3.set_style(sty);

m.println("starting!").unwrap();

let m_clone = m.clone();
let h1 = thread::spawn(move || {
for i in 0..128 {
pb.set_message(format!("item #{}", i + 1));
pb.inc(1);
thread::sleep(Duration::from_millis(15));
}
m_clone.println("pb1 is done!").unwrap();
pb.finish_with_message("done");
});

let m_clone = m.clone();
let h2 = thread::spawn(move || {
for _ in 0..3 {
pb2.set_position(0);
Expand All @@ -38,15 +43,18 @@ fn main() {
thread::sleep(Duration::from_millis(8));
}
}
m_clone.println("pb2 is done!").unwrap();
pb2.finish_with_message("done");
});

let m_clone = m.clone();
let _ = thread::spawn(move || {
for i in 0..1024 {
pb3.set_message(format!("item #{}", i + 1));
pb3.inc(1);
thread::sleep(Duration::from_millis(2));
}
m_clone.println("pb3 is done!").unwrap();
pb3.finish_with_message("done");
});

Expand Down
2 changes: 1 addition & 1 deletion src/draw_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl<'a> Drawable<'a> {
force_draw,
now,
..
} => state.draw(force_draw, now),
} => state.draw(force_draw, None, now),
Drawable::TermLike {
term_like,
last_line_count,
Expand Down
71 changes: 55 additions & 16 deletions src/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,47 @@ impl TermLike for InMemoryTerm {
}

fn move_cursor_up(&self, n: usize) -> std::io::Result<()> {
self.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}A", n))
match n {
chris-laplante marked this conversation as resolved.
Show resolved Hide resolved
0 => Ok(()),
_ => self
.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}A", n)),
}
}

fn move_cursor_down(&self, n: usize) -> std::io::Result<()> {
self.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}B", n))
match n {
0 => Ok(()),
_ => self
.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}B", n)),
}
}

fn move_cursor_right(&self, n: usize) -> std::io::Result<()> {
self.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}C", n))
match n {
0 => Ok(()),
_ => self
.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}C", n)),
}
}

fn move_cursor_left(&self, n: usize) -> std::io::Result<()> {
self.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}D", n))
match n {
0 => Ok(()),
_ => self
.state
.lock()
.unwrap()
.write_str(&*format!("\x1b[{}D", n)),
}
}

fn write_line(&self, s: &str) -> std::io::Result<()> {
Expand Down Expand Up @@ -211,4 +227,27 @@ mod test {

assert_eq!(in_mem.contents(), "LINE ONE\nLINE TWO\n\nLINE FOUR");
}

#[test]
fn cursor_zero_movement() {
let in_mem = InMemoryTerm::new(10, 80);
in_mem.write_line("LINE ONE").unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 0));

// Check that moving zero rows/cols does not actually move cursor
in_mem.move_cursor_up(0).unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 0));

in_mem.move_cursor_down(0).unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 0));

in_mem.move_cursor_right(1).unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 1));

in_mem.move_cursor_left(0).unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 1));

in_mem.move_cursor_right(0).unwrap();
assert_eq!(cursor_pos(&in_mem), (1, 1));
}
}
66 changes: 60 additions & 6 deletions src/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::draw_target::{DrawState, DrawStateWrapper, ProgressDrawTarget};
use crate::progress_bar::ProgressBar;

/// Manages multiple progress bars from different threads
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MultiProgress {
pub(crate) state: Arc<RwLock<MultiState>>,
}
Expand Down Expand Up @@ -131,6 +131,29 @@ impl MultiProgress {
pb
}

/// Print a log line above all progress bars in the [`MultiProgress`]
///
/// If the draw target is hidden (e.g. when standard output is not a terminal), `println()`
/// will not do anything.
pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
let mut state = self.state.write().unwrap();
state.println(msg, Instant::now())
}

/// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`]
///
/// Executes 'f' even if the draw target is hidden.
///
/// Useful for external code that writes to the standard output.
///
/// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
/// anything on the progress bar will be blocked until `f` finishes.
/// Therefore, it is recommended to avoid long-running operations in `f`.
pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
let mut state = self.state.write().unwrap();
state.suspend(f, Instant::now())
}

pub fn clear(&self) -> io::Result<()> {
self.state.write().unwrap().clear(Instant::now())
}
Expand Down Expand Up @@ -168,7 +191,12 @@ impl MultiState {
}
}

pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
pub(crate) fn draw(
&mut self,
mut force_draw: bool,
extra_lines: Option<Vec<String>>,
now: Instant,
) -> io::Result<()> {
let orphan_lines_count = self.orphan_lines.len();
force_draw |= orphan_lines_count > 0;
let mut drawable = match self.draw_target.drawable(force_draw, now) {
Expand All @@ -179,6 +207,11 @@ impl MultiState {
let mut draw_state = drawable.state();
draw_state.orphan_lines = orphan_lines_count;

if let Some(extra_lines) = &extra_lines {
draw_state.lines.extend_from_slice(extra_lines.as_slice());
draw_state.orphan_lines += extra_lines.len();
}

// Make orphaned lines appear at the top, so they can be properly forgotten.
draw_state.lines.append(&mut self.orphan_lines);

Expand All @@ -192,6 +225,18 @@ impl MultiState {
drawable.draw()
}

pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> {
let msg = msg.as_ref();

// If msg is "", make sure a line is still printed
let lines: Vec<String> = match msg.is_empty() {
false => msg.lines().map(Into::into).collect(),
true => vec![String::new()],
};

self.draw(true, Some(lines), now)
}

pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> {
let (states, orphans) = (&mut self.draw_states, &mut self.orphan_lines);
let state = match states.get_mut(idx) {
Expand All @@ -209,6 +254,13 @@ impl MultiState {
DrawStateWrapper::for_multi(state, orphans)
}

pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R {
self.clear(now).unwrap();
let ret = f();
self.draw(true, None, Instant::now()).unwrap();
ret
}

pub(crate) fn width(&self) -> u16 {
self.draw_target.width()
}
Expand Down Expand Up @@ -247,8 +299,9 @@ impl MultiState {
}
}

assert!(
self.len() == self.ordering.len(),
assert_eq!(
self.len(),
self.ordering.len(),
"Draw state is inconsistent"
);

Expand All @@ -271,8 +324,9 @@ impl MultiState {
self.free_set.push(idx);
self.ordering.retain(|&x| x != idx);

assert!(
self.len() == self.ordering.len(),
assert_eq!(
self.len(),
self.ordering.len(),
"Draw state is inconsistent"
);
}
Expand Down
Loading