use std::fmt;
use std::io::{self, Write};
use crate::terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate};
pub trait Command {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result;
#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()>;
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
super::ansi_support::supports_ansi()
}
}
impl<T: Command + ?Sized> Command for &T {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
(**self).write_ansi(f)
}
#[inline]
#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()> {
T::execute_winapi(self)
}
#[cfg(windows)]
#[inline]
fn is_ansi_code_supported(&self) -> bool {
T::is_ansi_code_supported(self)
}
}
pub trait QueueableCommand {
fn queue(&mut self, command: impl Command) -> io::Result<&mut Self>;
}
pub trait ExecutableCommand {
fn execute(&mut self, command: impl Command) -> io::Result<&mut Self>;
}
impl<T: Write + ?Sized> QueueableCommand for T {
fn queue(&mut self, command: impl Command) -> io::Result<&mut Self> {
#[cfg(windows)]
if !command.is_ansi_code_supported() {
self.flush()?;
command.execute_winapi()?;
return Ok(self);
}
write_command_ansi(self, command)?;
Ok(self)
}
}
impl<T: Write + ?Sized> ExecutableCommand for T {
fn execute(&mut self, command: impl Command) -> io::Result<&mut Self> {
self.queue(command)?;
self.flush()?;
Ok(self)
}
}
pub trait SynchronizedUpdate {
fn sync_update<T>(&mut self, operations: impl FnOnce(&mut Self) -> T) -> io::Result<T>;
}
impl<W: std::io::Write + ?Sized> SynchronizedUpdate for W {
fn sync_update<T>(&mut self, operations: impl FnOnce(&mut Self) -> T) -> io::Result<T> {
self.queue(BeginSynchronizedUpdate)?;
let result = operations(self);
self.execute(EndSynchronizedUpdate)?;
Ok(result)
}
}
fn write_command_ansi<C: Command>(
io: &mut (impl io::Write + ?Sized),
command: C,
) -> io::Result<()> {
struct Adapter<T> {
inner: T,
res: io::Result<()>,
}
impl<T: Write> fmt::Write for Adapter<T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.inner.write_all(s.as_bytes()).map_err(|e| {
self.res = Err(e);
fmt::Error
})
}
}
let mut adapter = Adapter {
inner: io,
res: Ok(()),
};
command
.write_ansi(&mut adapter)
.map_err(|fmt::Error| match adapter.res {
Ok(()) => panic!(
"<{}>::write_ansi incorrectly errored",
std::any::type_name::<C>()
),
Err(e) => e,
})
}
pub(crate) fn execute_fmt(f: &mut impl fmt::Write, command: impl Command) -> fmt::Result {
#[cfg(windows)]
if !command.is_ansi_code_supported() {
return command.execute_winapi().map_err(|_| fmt::Error);
}
command.write_ansi(f)
}