Add synchronized output/update (#756)
This commit is contained in:
parent
e7fc698f24
commit
e065a56536
@ -27,6 +27,7 @@ Available tests:
|
|||||||
2. color (foreground, background)
|
2. color (foreground, background)
|
||||||
3. attributes (bold, italic, ...)
|
3. attributes (bold, italic, ...)
|
||||||
4. input
|
4. input
|
||||||
|
5. synchronized output
|
||||||
|
|
||||||
Select test to run ('1', '2', ...) or hit 'q' to quit.
|
Select test to run ('1', '2', ...) or hit 'q' to quit.
|
||||||
"#;
|
"#;
|
||||||
@ -59,6 +60,7 @@ where
|
|||||||
'2' => test::color::run(w)?,
|
'2' => test::color::run(w)?,
|
||||||
'3' => test::attribute::run(w)?,
|
'3' => test::attribute::run(w)?,
|
||||||
'4' => test::event::run(w)?,
|
'4' => test::event::run(w)?,
|
||||||
|
'5' => test::synchronized_output::run(w)?,
|
||||||
'q' => break,
|
'q' => break,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
@ -2,3 +2,4 @@ pub mod attribute;
|
|||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod synchronized_output;
|
||||||
|
43
examples/interactive-demo/src/test/synchronized_output.rs
Normal file
43
examples/interactive-demo/src/test/synchronized_output.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crossterm::{cursor, execute, style::Print, SynchronizedUpdate};
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
fn render_slowly<W>(w: &mut W) -> Result<()>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
for i in 1..10 {
|
||||||
|
execute!(w, Print(format!("{}", i)))?;
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_slow_rendering<W>(w: &mut W) -> Result<()>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
execute!(w, Print("Rendering without synchronized update:"))?;
|
||||||
|
execute!(w, cursor::MoveToNextLine(1))?;
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
render_slowly(w)?;
|
||||||
|
|
||||||
|
execute!(w, cursor::MoveToNextLine(1))?;
|
||||||
|
execute!(w, Print("Rendering with synchronized update:"))?;
|
||||||
|
execute!(w, cursor::MoveToNextLine(1))?;
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
w.sync_update(render_slowly)??;
|
||||||
|
|
||||||
|
execute!(w, cursor::MoveToNextLine(1))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<W>(w: &mut W) -> Result<()>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
run_tests!(w, test_slow_rendering,);
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use crate::terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate};
|
||||||
|
|
||||||
use super::error::Result;
|
use super::error::Result;
|
||||||
|
|
||||||
/// An interface for a command that performs an action on the terminal.
|
/// An interface for a command that performs an action on the terminal.
|
||||||
@ -184,6 +186,74 @@ impl<T: Write + ?Sized> ExecutableCommand for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An interface for types that support synchronized updates.
|
||||||
|
pub trait SynchronizedUpdate {
|
||||||
|
/// Performs a set of actions against the given type.
|
||||||
|
fn sync_update<T>(&mut self, operations: impl FnOnce(&mut Self) -> T) -> Result<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: std::io::Write + ?Sized> SynchronizedUpdate for W {
|
||||||
|
/// Performs a set of actions within a synchronous update.
|
||||||
|
///
|
||||||
|
/// Updates will be suspended in the terminal, the function will be executed against self,
|
||||||
|
/// updates will be resumed, and a flush will be performed.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - Function
|
||||||
|
///
|
||||||
|
/// A function that performs the operations that must execute in a synchronized update.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::io::{Write, stdout};
|
||||||
|
///
|
||||||
|
/// use crossterm::{Result, ExecutableCommand, SynchronizedUpdate, style::Print};
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<()> {
|
||||||
|
/// let mut stdout = stdout();
|
||||||
|
///
|
||||||
|
/// stdout.sync_update(|stdout| {
|
||||||
|
/// stdout.execute(Print("foo 1\n".to_string()))?;
|
||||||
|
/// stdout.execute(Print("foo 2".to_string()))?;
|
||||||
|
/// // The effects of the print command will not be present in the terminal
|
||||||
|
/// // buffer, but not visible in the terminal.
|
||||||
|
/// crossterm::Result::Ok(())
|
||||||
|
/// })?;
|
||||||
|
///
|
||||||
|
/// // The effects of the commands will be visible.
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
///
|
||||||
|
/// // ==== Output ====
|
||||||
|
/// // foo 1
|
||||||
|
/// // foo 2
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// This command is performed only using ANSI codes, and will do nothing on terminals that do not support ANSI
|
||||||
|
/// codes, or this specific extension.
|
||||||
|
///
|
||||||
|
/// When rendering the screen of the terminal, the Emulator usually iterates through each visible grid cell and
|
||||||
|
/// renders its current state. With applications updating the screen a at higher frequency this can cause tearing.
|
||||||
|
///
|
||||||
|
/// This mode attempts to mitigate that.
|
||||||
|
///
|
||||||
|
/// When the synchronization mode is enabled following render calls will keep rendering the last rendered state.
|
||||||
|
/// The terminal Emulator keeps processing incoming text and sequences. When the synchronized update mode is disabled
|
||||||
|
/// again the renderer may fetch the latest screen buffer state again, effectively avoiding the tearing effect
|
||||||
|
/// by unintentionally rendering in the middle a of an application screen update.
|
||||||
|
///
|
||||||
|
fn sync_update<T>(&mut self, operations: impl FnOnce(&mut Self) -> T) -> Result<T> {
|
||||||
|
self.queue(BeginSynchronizedUpdate)?;
|
||||||
|
let result = operations(self);
|
||||||
|
self.execute(EndSynchronizedUpdate)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Writes the ANSI representation of a command to the given writer.
|
/// Writes the ANSI representation of a command to the given writer.
|
||||||
fn write_command_ansi<C: Command>(
|
fn write_command_ansi<C: Command>(
|
||||||
io: &mut (impl io::Write + ?Sized),
|
io: &mut (impl io::Write + ?Sized),
|
||||||
|
@ -235,7 +235,7 @@
|
|||||||
//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
|
//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
command::{Command, ExecutableCommand, QueueableCommand},
|
command::{Command, ExecutableCommand, QueueableCommand, SynchronizedUpdate},
|
||||||
error::{ErrorKind, Result},
|
error::{ErrorKind, Result},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
106
src/terminal.rs
106
src/terminal.rs
@ -378,6 +378,112 @@ impl<T: fmt::Display> Command for SetTitle<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A command that instructs the terminal emulator to being a synchronized frame.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// * Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
|
/// * Use [EndSynchronizedUpdate](./struct.EndSynchronizedUpdate.html) command to leave the entered alternate screen.
|
||||||
|
///
|
||||||
|
/// When rendering the screen of the terminal, the Emulator usually iterates through each visible grid cell and
|
||||||
|
/// renders its current state. With applications updating the screen a at higher frequency this can cause tearing.
|
||||||
|
///
|
||||||
|
/// This mode attempts to mitigate that.
|
||||||
|
///
|
||||||
|
/// When the synchronization mode is enabled following render calls will keep rendering the last rendered state.
|
||||||
|
/// The terminal Emulator keeps processing incoming text and sequences. When the synchronized update mode is disabled
|
||||||
|
/// again the renderer may fetch the latest screen buffer state again, effectively avoiding the tearing effect
|
||||||
|
/// by unintentionally rendering in the middle a of an application screen update.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::{stdout, Write};
|
||||||
|
/// use crossterm::{execute, Result, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<()> {
|
||||||
|
/// execute!(stdout(), BeginSynchronizedUpdate)?;
|
||||||
|
///
|
||||||
|
/// // Anything performed here will not be rendered until EndSynchronizedUpdate is called.
|
||||||
|
///
|
||||||
|
/// execute!(stdout(), EndSynchronizedUpdate)?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct BeginSynchronizedUpdate;
|
||||||
|
|
||||||
|
impl Command for BeginSynchronizedUpdate {
|
||||||
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
|
f.write_str(csi!("?2026h"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[inline]
|
||||||
|
fn is_ansi_code_supported(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command that instructs the terminal to end a synchronized frame.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// * Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
|
/// * Use [BeginSynchronizedUpdate](./struct.BeginSynchronizedUpdate.html) to enter the alternate screen.
|
||||||
|
///
|
||||||
|
/// When rendering the screen of the terminal, the Emulator usually iterates through each visible grid cell and
|
||||||
|
/// renders its current state. With applications updating the screen a at higher frequency this can cause tearing.
|
||||||
|
///
|
||||||
|
/// This mode attempts to mitigate that.
|
||||||
|
///
|
||||||
|
/// When the synchronization mode is enabled following render calls will keep rendering the last rendered state.
|
||||||
|
/// The terminal Emulator keeps processing incoming text and sequences. When the synchronized update mode is disabled
|
||||||
|
/// again the renderer may fetch the latest screen buffer state again, effectively avoiding the tearing effect
|
||||||
|
/// by unintentionally rendering in the middle a of an application screen update.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::{stdout, Write};
|
||||||
|
/// use crossterm::{execute, Result, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<()> {
|
||||||
|
/// execute!(stdout(), BeginSynchronizedUpdate)?;
|
||||||
|
///
|
||||||
|
/// // Anything performed here will not be rendered until EndSynchronizedUpdate is called.
|
||||||
|
///
|
||||||
|
/// execute!(stdout(), EndSynchronizedUpdate)?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct EndSynchronizedUpdate;
|
||||||
|
|
||||||
|
impl Command for EndSynchronizedUpdate {
|
||||||
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
|
f.write_str(csi!("?2026l"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[inline]
|
||||||
|
fn is_ansi_code_supported(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_display!(for ScrollUp);
|
impl_display!(for ScrollUp);
|
||||||
impl_display!(for ScrollDown);
|
impl_display!(for ScrollDown);
|
||||||
impl_display!(for SetSize);
|
impl_display!(for SetSize);
|
||||||
|
Loading…
Reference in New Issue
Block a user