From ceff36fa2284769e3ebed81abd273df684af755d Mon Sep 17 00:00:00 2001 From: Funami580 <63090225+Funami580@users.noreply.github.com> Date: Sat, 30 Dec 2023 03:01:11 +0100 Subject: [PATCH] synchronized output --- Cargo.toml | 1 + src/draw_target.rs | 8 +++++++- src/in_memory.rs | 31 +++++++++++++++++++++++++++++++ src/term_like.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 211ed5bd..db65d4d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ console = { version = "0.15", default-features = false, features = ["ansi-parsin futures-core = { version = "0.3", default-features = false, optional = true } number_prefix = "0.4" portable-atomic = "1.0.0" +once_cell = "1.19.0" rayon = { version = "1.1", optional = true } tokio = { version = "1", optional = true, features = ["io-util"] } unicode-segmentation = { version = "1", optional = true } diff --git a/src/draw_target.rs b/src/draw_target.rs index 003dfd5c..1740e4c9 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -10,7 +10,7 @@ use console::Term; use instant::Instant; use crate::multi::{MultiProgressAlignment, MultiState}; -use crate::TermLike; +use crate::{term_like, TermLike}; /// Target for draw operations /// @@ -470,6 +470,9 @@ impl DrawState { return Ok(()); } + // Begin synchronized update + let sync_guard = term_like::SyncGuard::begin_sync(term)?; + if !self.lines.is_empty() && self.move_cursor { term.move_cursor_up(*last_line_count)?; } else { @@ -542,6 +545,9 @@ impl DrawState { } term.write_str(&" ".repeat(last_line_filler))?; + // End synchronized update + sync_guard.finish_sync()?; + term.flush()?; *last_line_count = real_len - orphan_visual_line_count + shift; Ok(()) diff --git a/src/in_memory.rs b/src/in_memory.rs index 046ae14a..b36419c4 100644 --- a/src/in_memory.rs +++ b/src/in_memory.rs @@ -190,6 +190,10 @@ impl TermLike for InMemoryTerm { state.history.push(Move::Flush); state.parser.flush() } + + fn supports_ansi_codes(&self) -> bool { + true + } } struct InMemoryTermState { @@ -234,6 +238,8 @@ enum Move { #[cfg(test)] mod test { + use crate::term_like; + use super::*; fn cursor_pos(in_mem: &InMemoryTerm) -> (u16, u16) { @@ -396,4 +402,29 @@ NewLine in_mem.move_cursor_right(0).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 1)); } + + #[test] + fn sync_update() { + let in_mem = InMemoryTerm::new(10, 80); + assert_eq!(cursor_pos(&in_mem), (0, 0)); + + let sync_guard = term_like::SyncGuard::begin_sync(&in_mem).unwrap(); + in_mem.write_line("LINE ONE").unwrap(); + assert_eq!(cursor_pos(&in_mem), (1, 0)); + assert_eq!( + in_mem.moves_since_last_check(), + r#"Str("\u{1b}[?2026h") +Str("LINE ONE") +NewLine +"# + ); + + sync_guard.finish_sync().unwrap(); + assert_eq!(cursor_pos(&in_mem), (1, 0)); + assert_eq!( + in_mem.moves_since_last_check(), + r#"Str("\u{1b}[?2026l") +"# + ); + } } diff --git a/src/term_like.rs b/src/term_like.rs index b489b655..4c6722b2 100644 --- a/src/term_like.rs +++ b/src/term_like.rs @@ -1,7 +1,9 @@ +use std::cell::Cell; use std::fmt::Debug; use std::io; use console::Term; +use once_cell::sync::OnceCell; /// A trait for minimal terminal-like behavior. /// @@ -34,6 +36,9 @@ pub trait TermLike: Debug + Send + Sync { fn clear_line(&self) -> io::Result<()>; fn flush(&self) -> io::Result<()>; + + // Whether ANSI escape sequences are supported + fn supports_ansi_codes(&self) -> bool; } impl TermLike for Term { @@ -76,4 +81,44 @@ impl TermLike for Term { fn flush(&self) -> io::Result<()> { self.flush() } + + fn supports_ansi_codes(&self) -> bool { + static SUPPORTS_ANSI: OnceCell = OnceCell::new(); + *SUPPORTS_ANSI.get_or_init(|| self.features().colors_supported()) + } +} + +pub(crate) struct SyncGuard<'a, T: TermLike + ?Sized> { + term_like: Cell>, +} + +impl<'a, T: TermLike + ?Sized> SyncGuard<'a, T> { + pub(crate) fn begin_sync(term_like: &'a T) -> io::Result { + if term_like.supports_ansi_codes() { + term_like.write_str("\x1b[?2026h")?; + } + + Ok(Self { + term_like: Cell::new(Some(term_like)), + }) + } + + pub(crate) fn finish_sync(self) -> io::Result<()> { + self.finish_sync_inner() + } + + fn finish_sync_inner(&self) -> io::Result<()> { + if let Some(term_like) = self.term_like.take() { + if term_like.supports_ansi_codes() { + term_like.write_str("\x1b[?2026l")?; + } + } + Ok(()) + } +} + +impl Drop for SyncGuard<'_, T> { + fn drop(&mut self) { + let _ = self.finish_sync_inner(); + } }