use napi::bindgen_prelude::*;
use napi_derive::napi;
use std::sync::Mutex;
use crate::terminal::{Backend, TerminalOptions};
use super::types::{TerminalInfoNapi, TerminalOptionsNapi};
static BACKEND: Mutex<Option<Backend>> = Mutex::new(None);
#[napi(js_name = "initTerminal")]
#[allow(clippy::disallowed_macros)]
pub fn init_terminal() -> Result<()> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if guard.is_some() {
return Err(Error::new(
Status::GenericFailure,
"Terminal already initialized",
));
}
let mut backend = Backend::new().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to create backend: {}", e),
)
})?;
backend.init().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to init terminal: {}", e),
)
})?;
*guard = Some(backend);
Ok(())
}
#[napi(js_name = "initTerminalWithMouse")]
#[allow(clippy::disallowed_macros)]
pub fn init_terminal_with_mouse() -> Result<()> {
init_terminal_with_options(TerminalOptionsNapi {
raw_mode: Some(true),
alternate_screen: Some(true),
mouse: Some(true),
bracketed_paste: Some(true),
hide_cursor: Some(true),
})
}
#[napi(js_name = "initTerminalWithOptions")]
#[allow(clippy::disallowed_macros)]
pub fn init_terminal_with_options(options: TerminalOptionsNapi) -> Result<()> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if guard.is_some() {
return Err(Error::new(
Status::GenericFailure,
"Terminal already initialized",
));
}
let mut backend = Backend::new().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to create backend: {}", e),
)
})?;
backend
.init_with_options(TerminalOptions {
raw_mode: options.raw_mode.unwrap_or(true),
alternate_screen: options.alternate_screen.unwrap_or(false),
mouse_capture: options.mouse.unwrap_or(false),
bracketed_paste: options.bracketed_paste.unwrap_or(true),
hide_cursor: options.hide_cursor.unwrap_or(true),
})
.map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to init terminal: {}", e),
)
})?;
*guard = Some(backend);
Ok(())
}
#[napi(js_name = "restoreTerminal")]
#[allow(clippy::disallowed_macros)]
pub fn restore_terminal() -> Result<()> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if let Some(ref mut backend) = *guard {
backend.restore().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to restore terminal: {}", e),
)
})?;
}
*guard = None;
Ok(())
}
#[napi(js_name = "getTerminalInfo")]
#[allow(clippy::disallowed_macros)]
pub fn get_terminal_info() -> Result<TerminalInfoNapi> {
let (width, height) = crossterm::terminal::size()
.map_err(|e| Error::new(Status::GenericFailure, format!("Failed to get size: {}", e)))?;
Ok(TerminalInfoNapi {
width: width as i32,
height: height as i32,
colors: true, true_color: std::env::var("COLORTERM")
.map(|v| v == "truecolor" || v == "24bit")
.unwrap_or(false),
})
}
#[napi(js_name = "clearScreen")]
#[allow(clippy::disallowed_macros)]
pub fn clear_screen() -> Result<()> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if let Some(ref mut backend) = *guard {
backend
.clear()
.map_err(|e| Error::new(Status::GenericFailure, format!("Failed to clear: {}", e)))?;
}
Ok(())
}
#[napi(js_name = "flushTerminal")]
#[allow(clippy::disallowed_macros)]
pub fn flush_terminal() -> Result<()> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if let Some(ref mut backend) = *guard {
backend
.flush()
.map_err(|e| Error::new(Status::GenericFailure, format!("Failed to flush: {}", e)))?;
}
Ok(())
}
#[napi(js_name = "syncTerminalSize")]
#[allow(clippy::disallowed_macros)]
pub fn sync_terminal_size() -> Result<bool> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if let Some(ref mut backend) = *guard {
let changed = backend.sync_size().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to sync size: {}", e),
)
})?;
Ok(changed)
} else {
Ok(false)
}
}
#[allow(clippy::disallowed_macros)]
pub(crate) fn with_backend<T, F: FnOnce(&mut Backend) -> T>(f: F) -> Result<T> {
let mut guard = BACKEND
.lock()
.map_err(|e| Error::new(Status::GenericFailure, format!("Lock error: {}", e)))?;
if let Some(ref mut backend) = *guard {
Ok(f(backend))
} else {
Err(Error::new(
Status::GenericFailure,
"Terminal not initialized",
))
}
}