use super::TerminalImpl;
use crossterm::{
Command,
event::{self, DisableMouseCapture, EventStream},
execute,
};
use futures::{StreamExt, stream::BoxStream};
use ratatui::{Frame, TerminalOptions};
use std::{
fmt,
io::{self, Write},
};
struct EnableButtonMouseCapture;
impl Command for EnableButtonMouseCapture {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str("\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h")
}
#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()> {
event::EnableMouseCapture.execute_winapi()
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
event::EnableMouseCapture.is_ansi_code_supported()
}
}
pub struct CrossTerminal {
terminal: ratatui::DefaultTerminal,
}
impl CrossTerminal {
pub fn new() -> io::Result<Self> {
Ok(Self {
terminal: ratatui::init(),
})
}
pub fn new_with_mouse(mouse: bool) -> io::Result<Self> {
let this = Self::new()?;
if mouse {
set_mouse_capture(true)?;
}
Ok(this)
}
pub fn with_options(options: TerminalOptions) -> io::Result<Self> {
Ok(Self {
terminal: ratatui::init_with_options(options),
})
}
pub fn with_options_and_mouse(options: TerminalOptions, mouse: bool) -> io::Result<Self> {
let this = Self::with_options(options)?;
if mouse {
set_mouse_capture(true)?;
}
Ok(this)
}
}
pub(crate) fn set_mouse_capture(enabled: bool) -> io::Result<()> {
if enabled {
execute!(io::stdout(), EnableButtonMouseCapture)
} else {
execute!(io::stdout(), DisableMouseCapture)
}
}
impl TerminalImpl for CrossTerminal {
type Event = event::Event;
fn event_stream(&mut self) -> io::Result<BoxStream<'static, Self::Event>> {
Ok(EventStream::new()
.filter_map(|event| async move { event.ok() })
.boxed())
}
fn received_ctrl_c(event: Self::Event) -> bool {
matches!(
event,
event::Event::Key(event::KeyEvent {
code: event::KeyCode::Char('c'),
modifiers: event::KeyModifiers::CONTROL,
kind: event::KeyEventKind::Press,
..
})
)
}
fn draw<F>(&mut self, f: F) -> io::Result<()>
where
F: FnOnce(&mut Frame),
{
self.terminal.draw(f)?;
Ok(())
}
fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
where
F: FnOnce(&mut ratatui::prelude::Buffer),
{
self.terminal.insert_before(height, draw_fn)?;
Ok(())
}
fn enable_any_motion(&mut self) -> io::Result<()> {
let mut out = io::stdout();
out.write_all(b"\x1b[?1003h")?;
out.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn initial_capture_reports_buttons_and_drags_without_all_motion() {
let mut ansi = String::new();
EnableButtonMouseCapture.write_ansi(&mut ansi).unwrap();
assert!(ansi.contains("\x1b[?1000h"));
assert!(ansi.contains("\x1b[?1002h"));
assert!(ansi.contains("\x1b[?1006h"));
assert!(!ansi.contains("\x1b[?1003h"));
}
}