Skip to content

Commit ade6307

Browse files
committed
synchronized output
1 parent 2a8b2ea commit ade6307

File tree

4 files changed

+142
-39
lines changed

4 files changed

+142
-39
lines changed

src/draw_target.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use console::Term;
1212
use instant::Instant;
1313

1414
use crate::multi::{MultiProgressAlignment, MultiState};
15-
use crate::TermLike;
15+
use crate::{term_like, TermLike};
1616

1717
/// Target for draw operations
1818
///
@@ -71,9 +71,12 @@ impl ProgressDrawTarget {
7171
///
7272
/// Will panic if `refresh_rate` is `0`.
7373
pub fn term(term: Term, refresh_rate: u8) -> Self {
74+
let supports_ansi_codes = term.supports_ansi_codes();
75+
7476
Self {
7577
kind: TargetKind::Term {
7678
term,
79+
supports_ansi_codes,
7780
last_line_count: VisualLines::default(),
7881
rate_limiter: RateLimiter::new(refresh_rate),
7982
draw_state: DrawState::default(),
@@ -83,9 +86,12 @@ impl ProgressDrawTarget {
8386

8487
/// Draw to a boxed object that implements the [`TermLike`] trait.
8588
pub fn term_like(term_like: Box<dyn TermLike>) -> Self {
89+
let supports_ansi_codes = term_like.supports_ansi_codes();
90+
8691
Self {
8792
kind: TargetKind::TermLike {
8893
inner: term_like,
94+
supports_ansi_codes,
8995
last_line_count: VisualLines::default(),
9096
rate_limiter: None,
9197
draw_state: DrawState::default(),
@@ -96,9 +102,12 @@ impl ProgressDrawTarget {
96102
/// Draw to a boxed object that implements the [`TermLike`] trait,
97103
/// with a specific refresh rate.
98104
pub fn term_like_with_hz(term_like: Box<dyn TermLike>, refresh_rate: u8) -> Self {
105+
let supports_ansi_codes = term_like.supports_ansi_codes();
106+
99107
Self {
100108
kind: TargetKind::TermLike {
101109
inner: term_like,
110+
supports_ansi_codes,
102111
last_line_count: VisualLines::default(),
103112
rate_limiter: Option::from(RateLimiter::new(refresh_rate)),
104113
draw_state: DrawState::default(),
@@ -151,6 +160,7 @@ impl ProgressDrawTarget {
151160
match &mut self.kind {
152161
TargetKind::Term {
153162
term,
163+
supports_ansi_codes,
154164
last_line_count,
155165
rate_limiter,
156166
draw_state,
@@ -162,6 +172,7 @@ impl ProgressDrawTarget {
162172
match force_draw || rate_limiter.allow(now) {
163173
true => Some(Drawable::Term {
164174
term,
175+
supports_ansi_codes: *supports_ansi_codes,
165176
last_line_count,
166177
draw_state,
167178
}),
@@ -179,12 +190,14 @@ impl ProgressDrawTarget {
179190
}
180191
TargetKind::TermLike {
181192
inner,
193+
supports_ansi_codes,
182194
last_line_count,
183195
rate_limiter,
184196
draw_state,
185197
} => match force_draw || rate_limiter.as_mut().map_or(true, |r| r.allow(now)) {
186198
true => Some(Drawable::TermLike {
187199
term_like: &**inner,
200+
supports_ansi_codes: *supports_ansi_codes,
188201
last_line_count,
189202
draw_state,
190203
}),
@@ -230,6 +243,7 @@ impl ProgressDrawTarget {
230243
enum TargetKind {
231244
Term {
232245
term: Term,
246+
supports_ansi_codes: bool,
233247
last_line_count: VisualLines,
234248
rate_limiter: RateLimiter,
235249
draw_state: DrawState,
@@ -241,6 +255,7 @@ enum TargetKind {
241255
Hidden,
242256
TermLike {
243257
inner: Box<dyn TermLike>,
258+
supports_ansi_codes: bool,
244259
last_line_count: VisualLines,
245260
rate_limiter: Option<RateLimiter>,
246261
draw_state: DrawState,
@@ -270,6 +285,7 @@ impl TargetKind {
270285
pub(crate) enum Drawable<'a> {
271286
Term {
272287
term: &'a Term,
288+
supports_ansi_codes: bool,
273289
last_line_count: &'a mut VisualLines,
274290
draw_state: &'a mut DrawState,
275291
},
@@ -281,6 +297,7 @@ pub(crate) enum Drawable<'a> {
281297
},
282298
TermLike {
283299
term_like: &'a dyn TermLike,
300+
supports_ansi_codes: bool,
284301
last_line_count: &'a mut VisualLines,
285302
draw_state: &'a mut DrawState,
286303
},
@@ -326,9 +343,10 @@ impl<'a> Drawable<'a> {
326343
match self {
327344
Drawable::Term {
328345
term,
346+
supports_ansi_codes,
329347
last_line_count,
330348
draw_state,
331-
} => draw_state.draw_to_term(term, last_line_count),
349+
} => draw_state.draw_to_term(term, supports_ansi_codes, last_line_count),
332350
Drawable::Multi {
333351
mut state,
334352
force_draw,
@@ -337,9 +355,10 @@ impl<'a> Drawable<'a> {
337355
} => state.draw(force_draw, None, now),
338356
Drawable::TermLike {
339357
term_like,
358+
supports_ansi_codes,
340359
last_line_count,
341360
draw_state,
342-
} => draw_state.draw_to_term(term_like, last_line_count),
361+
} => draw_state.draw_to_term(term_like, supports_ansi_codes, last_line_count),
343362
}
344363
}
345364
}
@@ -466,12 +485,20 @@ impl DrawState {
466485
fn draw_to_term(
467486
&mut self,
468487
term: &(impl TermLike + ?Sized),
488+
supports_ansi_codes: bool,
469489
last_line_count: &mut VisualLines,
470490
) -> io::Result<()> {
471491
if panicking() {
472492
return Ok(());
473493
}
474494

495+
// Begin synchronized update
496+
let sync_guard = if supports_ansi_codes {
497+
Some(term_like::SyncGuard::begin_sync(term)?)
498+
} else {
499+
None
500+
};
501+
475502
if !self.lines.is_empty() && self.move_cursor {
476503
term.move_cursor_up(last_line_count.as_usize())?;
477504
} else {
@@ -547,6 +574,11 @@ impl DrawState {
547574
}
548575
term.write_str(&" ".repeat(last_line_filler))?;
549576

577+
// End synchronized update
578+
if let Some(sync_guard) = sync_guard {
579+
sync_guard.finish_sync()?;
580+
}
581+
550582
term.flush()?;
551583
*last_line_count = real_len - orphan_visual_line_count + shift;
552584
Ok(())

src/in_memory.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ use crate::TermLike;
1313
#[derive(Debug, Clone)]
1414
pub struct InMemoryTerm {
1515
state: Arc<Mutex<InMemoryTermState>>,
16+
supports_ansi_codes: bool,
1617
}
1718

1819
impl InMemoryTerm {
19-
pub fn new(rows: u16, cols: u16) -> InMemoryTerm {
20+
pub fn new(rows: u16, cols: u16, supports_ansi_codes: bool) -> InMemoryTerm {
2021
assert!(rows > 0, "rows must be > 0");
2122
assert!(cols > 0, "cols must be > 0");
2223
InMemoryTerm {
2324
state: Arc::new(Mutex::new(InMemoryTermState::new(rows, cols))),
25+
supports_ansi_codes,
2426
}
2527
}
2628

@@ -190,6 +192,10 @@ impl TermLike for InMemoryTerm {
190192
state.history.push(Move::Flush);
191193
state.parser.flush()
192194
}
195+
196+
fn supports_ansi_codes(&self) -> bool {
197+
self.supports_ansi_codes
198+
}
193199
}
194200

195201
struct InMemoryTermState {
@@ -234,6 +240,8 @@ enum Move {
234240

235241
#[cfg(test)]
236242
mod test {
243+
use crate::term_like;
244+
237245
use super::*;
238246

239247
fn cursor_pos(in_mem: &InMemoryTerm) -> (u16, u16) {
@@ -248,7 +256,7 @@ mod test {
248256

249257
#[test]
250258
fn line_wrapping() {
251-
let in_mem = InMemoryTerm::new(10, 5);
259+
let in_mem = InMemoryTerm::new(10, 5, false);
252260
assert_eq!(cursor_pos(&in_mem), (0, 0));
253261

254262
in_mem.write_str("ABCDE").unwrap();
@@ -282,7 +290,7 @@ mod test {
282290

283291
#[test]
284292
fn write_line() {
285-
let in_mem = InMemoryTerm::new(10, 5);
293+
let in_mem = InMemoryTerm::new(10, 5, false);
286294
assert_eq!(cursor_pos(&in_mem), (0, 0));
287295

288296
in_mem.write_line("A").unwrap();
@@ -318,7 +326,7 @@ NewLine
318326

319327
#[test]
320328
fn basic_functionality() {
321-
let in_mem = InMemoryTerm::new(10, 80);
329+
let in_mem = InMemoryTerm::new(10, 80, false);
322330

323331
in_mem.write_line("This is a test line").unwrap();
324332
assert_eq!(in_mem.contents(), "This is a test line");
@@ -352,7 +360,7 @@ Str("TEST")
352360

353361
#[test]
354362
fn newlines() {
355-
let in_mem = InMemoryTerm::new(10, 10);
363+
let in_mem = InMemoryTerm::new(10, 10, false);
356364
in_mem.write_line("LINE ONE").unwrap();
357365
in_mem.write_line("LINE TWO").unwrap();
358366
in_mem.write_line("").unwrap();
@@ -376,7 +384,7 @@ NewLine
376384

377385
#[test]
378386
fn cursor_zero_movement() {
379-
let in_mem = InMemoryTerm::new(10, 80);
387+
let in_mem = InMemoryTerm::new(10, 80, false);
380388
in_mem.write_line("LINE ONE").unwrap();
381389
assert_eq!(cursor_pos(&in_mem), (1, 0));
382390

@@ -396,4 +404,29 @@ NewLine
396404
in_mem.move_cursor_right(0).unwrap();
397405
assert_eq!(cursor_pos(&in_mem), (1, 1));
398406
}
407+
408+
#[test]
409+
fn sync_update() {
410+
let in_mem = InMemoryTerm::new(10, 80, true);
411+
assert_eq!(cursor_pos(&in_mem), (0, 0));
412+
413+
let sync_guard = term_like::SyncGuard::begin_sync(&in_mem).unwrap();
414+
in_mem.write_line("LINE ONE").unwrap();
415+
assert_eq!(cursor_pos(&in_mem), (1, 0));
416+
assert_eq!(
417+
in_mem.moves_since_last_check(),
418+
r#"Str("\u{1b}[?2026h")
419+
Str("LINE ONE")
420+
NewLine
421+
"#
422+
);
423+
424+
sync_guard.finish_sync().unwrap();
425+
assert_eq!(cursor_pos(&in_mem), (1, 0));
426+
assert_eq!(
427+
in_mem.moves_since_last_check(),
428+
r#"Str("\u{1b}[?2026l")
429+
"#
430+
);
431+
}
399432
}

src/term_like.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::cell::Cell;
12
use std::fmt::Debug;
23
use std::io;
34

@@ -34,6 +35,9 @@ pub trait TermLike: Debug + Send + Sync {
3435
fn clear_line(&self) -> io::Result<()>;
3536

3637
fn flush(&self) -> io::Result<()>;
38+
39+
// Whether ANSI escape sequences are supported
40+
fn supports_ansi_codes(&self) -> bool;
3741
}
3842

3943
impl TermLike for Term {
@@ -76,4 +80,38 @@ impl TermLike for Term {
7680
fn flush(&self) -> io::Result<()> {
7781
self.flush()
7882
}
83+
84+
fn supports_ansi_codes(&self) -> bool {
85+
self.features().colors_supported()
86+
}
87+
}
88+
89+
pub(crate) struct SyncGuard<'a, T: TermLike + ?Sized> {
90+
term_like: Cell<Option<&'a T>>,
91+
}
92+
93+
impl<'a, T: TermLike + ?Sized> SyncGuard<'a, T> {
94+
pub(crate) fn begin_sync(term_like: &'a T) -> io::Result<Self> {
95+
term_like.write_str("\x1b[?2026h")?;
96+
Ok(Self {
97+
term_like: Cell::new(Some(term_like)),
98+
})
99+
}
100+
101+
pub(crate) fn finish_sync(self) -> io::Result<()> {
102+
self.finish_sync_inner()
103+
}
104+
105+
fn finish_sync_inner(&self) -> io::Result<()> {
106+
if let Some(term_like) = self.term_like.take() {
107+
term_like.write_str("\x1b[?2026l")?;
108+
}
109+
Ok(())
110+
}
111+
}
112+
113+
impl<T: TermLike + ?Sized> Drop for SyncGuard<'_, T> {
114+
fn drop(&mut self) {
115+
let _ = self.finish_sync_inner();
116+
}
79117
}

0 commit comments

Comments
 (0)