use std::io::{self, Write};
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture},
execute,
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
};
use crate::error::Result;
use crate::geometry::Size;
use super::detect::{MultiplexerKind, detect_multiplexer, detect_terminal};
use super::profiles::{merge_multiplexer_limits, profile_for};
use super::traits::{Terminal, TerminalCapabilities};
pub struct CrosstermBackend {
capabilities: TerminalCapabilities,
multiplexer: MultiplexerKind,
raw_mode: bool,
}
impl CrosstermBackend {
pub fn new() -> Self {
let kind = detect_terminal();
let multiplexer = detect_multiplexer();
let mut capabilities = profile_for(kind);
capabilities = merge_multiplexer_limits(capabilities, multiplexer);
Self {
capabilities,
multiplexer,
raw_mode: false,
}
}
pub fn with_capabilities(capabilities: TerminalCapabilities) -> Self {
Self {
capabilities,
multiplexer: MultiplexerKind::None,
raw_mode: false,
}
}
pub fn multiplexer(&self) -> MultiplexerKind {
self.multiplexer
}
}
impl Default for CrosstermBackend {
fn default() -> Self {
Self::new()
}
}
impl Terminal for CrosstermBackend {
fn size(&self) -> Result<Size> {
let (w, h) = terminal::size()?;
Ok(Size::new(w, h))
}
fn capabilities(&self) -> &TerminalCapabilities {
&self.capabilities
}
fn enter_raw_mode(&mut self) -> Result<()> {
if !self.raw_mode {
terminal::enable_raw_mode()?;
execute!(io::stdout(), EnterAlternateScreen)?;
self.raw_mode = true;
}
Ok(())
}
fn exit_raw_mode(&mut self) -> Result<()> {
if self.raw_mode {
execute!(io::stdout(), LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
self.raw_mode = false;
}
Ok(())
}
fn write_raw(&mut self, data: &[u8]) -> Result<()> {
io::stdout().write_all(data)?;
Ok(())
}
fn flush(&mut self) -> Result<()> {
io::stdout().flush()?;
Ok(())
}
fn enable_mouse(&mut self) -> Result<()> {
execute!(io::stdout(), EnableMouseCapture)?;
Ok(())
}
fn disable_mouse(&mut self) -> Result<()> {
execute!(io::stdout(), DisableMouseCapture)?;
Ok(())
}
}
impl Drop for CrosstermBackend {
fn drop(&mut self) {
if self.raw_mode {
let _ = execute!(io::stdout(), LeaveAlternateScreen);
let _ = terminal::disable_raw_mode();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::terminal::detect::TerminalKind;
use crate::terminal::traits::ColorSupport;
#[test]
fn test_new_creates_backend_with_detected_capabilities() {
let backend = CrosstermBackend::new();
assert!(!backend.raw_mode);
}
#[test]
fn test_with_capabilities_uses_provided_caps() {
let caps = TerminalCapabilities {
color: ColorSupport::Extended256,
unicode: true,
synchronized_output: true,
kitty_keyboard: true,
mouse: true,
bracketed_paste: true,
focus_events: true,
hyperlinks: true,
sixel: false,
};
let backend = CrosstermBackend::with_capabilities(caps.clone());
assert_eq!(backend.capabilities(), &caps);
assert_eq!(backend.multiplexer, MultiplexerKind::None);
}
#[test]
fn test_integration_kitty_profile() {
let kind = TerminalKind::Kitty;
let profile = profile_for(kind);
let backend = CrosstermBackend::with_capabilities(profile.clone());
assert_eq!(backend.capabilities().color, ColorSupport::TrueColor);
assert!(backend.capabilities().kitty_keyboard);
assert!(backend.capabilities().synchronized_output);
}
#[test]
fn test_integration_tmux_limits() {
let kind = TerminalKind::Kitty;
let multiplexer = MultiplexerKind::Tmux;
let mut caps = profile_for(kind);
caps = merge_multiplexer_limits(caps, multiplexer);
let backend = CrosstermBackend::with_capabilities(caps);
assert!(!backend.capabilities().synchronized_output);
assert!(backend.capabilities().kitty_keyboard);
}
}