use log::trace;
#[cfg(all(windows, feature = "sys"))]
use windows_sys::Win32::System::Console::{COORD, SMALL_RECT};
use crate::output::ansi::CSI;
use crate::{prelude::Point, sys::Control};
use std::fmt::Debug;
use std::io;
pub trait TerminalCommander {
fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()>;
}
impl<C: Control, W: io::Write> TerminalCommander for (C, W) {
fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()> {
trace!("executing terminal request {request:?}");
let (ref control, ref mut write) = *self;
let mut ansi_tried = false;
if control.is_ansi() {
if request.apply(write)? {
return Ok(());
}
ansi_tried = true
}
let (ref control, ref mut write) = *self;
write.flush()?;
if request.control(control)? {
Ok(())
} else if !ansi_tried && request.apply(write)? {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("invalid request {request:?}"),
))
}
}
}
pub trait TerminalRequest: Debug {
fn apply(&self, _write: impl io::Write) -> io::Result<bool> {
Ok(false)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnterRawMode;
impl TerminalRequest for EnterRawMode {
fn control(&self, _control: &impl Control) -> io::Result<bool> {
_control.enable_raw_mode().map(|()| true)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flush;
impl TerminalRequest for Flush {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
write.flush().map(|()| true)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveCursorBy(pub i16, pub i16);
impl TerminalRequest for MoveCursorBy {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let Self(x, y) = *self;
let mut dimension = |x, p, n| {
if x > 0 {
write.write_all(format!("{CSI}{x}{p}").as_bytes())
} else if x < 0 {
let x = i16::abs(x);
write.write_all(format!("{CSI}{x}{n}").as_bytes())
} else {
Ok(())
}
};
dimension(x, 'C', 'D')?;
dimension(y, 'B', 'A')?;
Ok(true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return {
let Self(x, y) = *self;
let info =
crate::sys::console::output::get_console_screen_buffer_info(_control.output())?;
let COORD {
X: curr_x,
Y: curr_y,
} = info.dwCursorPosition;
let COORD {
X: width,
Y: height,
} = info.dwSize;
let SMALL_RECT {
Left: left,
Top: top,
Right: right,
Bottom: bottom,
} = info.srWindow;
let x = curr_x
.saturating_add(x)
.clamp(left, width.saturating_sub(right).saturating_sub(1))
as u16;
let y = curr_y
.saturating_add(y)
.clamp(top, height.saturating_sub(bottom).saturating_sub(1))
as u16;
crate::sys::console::output::set_console_cursot_position(_control.output(), x, y)?;
Ok(true)
};
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveCursorTo(pub Point);
impl TerminalRequest for MoveCursorTo {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let Self(Point { mut col, mut row }) = self;
col += 1;
row += 1;
write
.write_all(format!("{CSI}{row};{col}H").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return crate::sys::console::output::set_console_cursot_position(
_control.output(),
self.0.col,
self.0.row,
)
.map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResetStyles;
impl TerminalRequest for ResetStyles {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
write
.write_all(format!("{CSI}0m").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return crate::sys::console::output::set_console_text_attribute(
_control.output(),
_control.get_initial_style(),
)
.map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClearScreen;
impl TerminalRequest for ClearScreen {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
write
.write_all(format!("{CSI}2J").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return {
let info =
crate::sys::console::output::get_console_screen_buffer_info(_control.output())?;
let size = info.dwSize;
let style = info.wAttributes;
let start_location = COORD { X: 0, Y: 0 };
let cells_to_write = size.X as u32 * size.Y as u32;
crate::sys::console::output::fill_with_character(
_control.output(),
start_location,
cells_to_write,
' ',
)?;
crate::sys::console::output::fill_with_attribute(
_control.output(),
start_location,
cells_to_write,
style,
)?;
Ok(true)
};
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UseAlternateScreen(pub bool);
impl TerminalRequest for UseAlternateScreen {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let act = if self.0 { 'h' } else { 'l' };
write
.write_all(format!("{CSI}?1049{act}").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return _control.use_alternate_screen(self.0).map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShowCursor(pub bool);
impl TerminalRequest for ShowCursor {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let act = if self.0 { 'h' } else { 'l' };
write
.write_all(format!("{CSI}?25{act}").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return crate::sys::console::output::show_cursor(_control.output(), self.0).map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WrapLines(pub bool);
impl TerminalRequest for WrapLines {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let act = if self.0 { 'h' } else { 'l' };
write
.write_all(format!("{CSI}?7{act}").as_bytes())
.map(|()| true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return crate::sys::console::output::wrap_lines(_control.output(), self.0).map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureMouse(pub bool);
impl TerminalRequest for CaptureMouse {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let act = if self.0 { 'h' } else { 'l' };
let mut modes = [
1000, 1002, 1003, 1015, 1006, ];
if !self.0 {
modes.reverse();
}
for mode in modes {
write.write_all(format!("{CSI}?{mode}{act}").as_bytes())?
}
Ok(true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return crate::sys::console::input::enable_mouse(_control.output(), self.0).map(|()| true);
#[allow(unreachable_code)]
Ok(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureFocus(pub bool);
impl TerminalRequest for CaptureFocus {
fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
let act = if self.0 { 'h' } else { 'l' };
write.write_all(format!("{CSI}?1004{act}").as_bytes())?;
Ok(true)
}
fn control(&self, _control: &impl Control) -> io::Result<bool> {
#[cfg(all(windows, feature = "sys"))]
return Ok(true); #[allow(unreachable_code)]
Ok(false)
}
}