#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
use std::collections::HashMap;
use std::io::{self, Write};
use std::sync::{Mutex, OnceLock};
use crate::ported::zsh_h::{features as features_t, module};
use crate::ported::utils::{zerrnam, zwarnnam};
use std::io::Read;
pub const ZCWF_PERMANENT: u32 = 0x0001; pub const ZCWF_SCROLL: u32 = 0x0002;
pub const ZCURSES_EINVALID: i32 = 1;
pub const ZCURSES_EDEFINED: i32 = 2;
pub const ZCURSES_EUNDEFINED: i32 = 3;
pub const ZCURSES_UNUSED: i32 = 1;
pub const ZCURSES_USED: i32 = 2;
pub const ZCURSES_ATTRON: i32 = 1;
pub const ZCURSES_ATTROFF: i32 = 2;
#[derive(Debug)]
pub struct zc_win {
pub name: String,
pub flags: u32,
pub rows: usize,
pub cols: usize,
pub y: usize,
pub x: usize,
pub cursor_y: usize,
pub cursor_x: usize,
pub keypad: bool,
pub fg: i32,
pub bg: i32,
pub attrs: u32,
pub bg_chtype: u32,
pub timeout_ms: i32,
pub parent: Option<String>,
pub children: Vec<String>,
buffer: Vec<Vec<char>>,
}
impl zc_win {
fn new(name: &str, rows: usize, cols: usize, y: usize, x: usize) -> Self {
Self {
name: name.to_string(),
flags: 0,
rows,
cols,
y,
x,
cursor_y: 0,
cursor_x: 0,
keypad: false,
fg: -1,
bg: -1,
attrs: 0,
bg_chtype: 0,
timeout_ms: -1,
parent: None,
children: Vec::new(),
buffer: vec![vec![' '; cols]; rows],
}
}
fn refresh(&self) -> io::Result<()> {
let mut stdout = io::stdout();
write!(stdout, "\x1b[{};{}H", self.y + 1, self.x + 1)?;
let attr_sgr = sgr_for_attrs(self.attrs);
write!(stdout, "{}", attr_sgr)?;
for (row_idx, row) in self.buffer.iter().enumerate() {
write!(stdout, "\x1b[{};{}H", self.y + row_idx + 1, self.x + 1)?;
let line: String = row.iter().collect();
write!(stdout, "{}", line)?;
}
write!(
stdout,
"\x1b[{};{}H",
self.y + self.cursor_y + 1,
self.x + self.cursor_x + 1
)?;
stdout.flush()
}
}
#[derive(Debug, Clone, Copy)]
pub struct colorpairnode { pub colorpair: i16, }
pub const ZCME_PRESSED: i32 = 0; pub const ZCME_RELEASED: i32 = 1; pub const ZCME_CLICKED: i32 = 2; pub const ZCME_DOUBLE_CLICKED: i32 = 3; pub const ZCME_TRIPLE_CLICKED: i32 = 4;
#[derive(Debug, Clone, Copy)]
pub struct zcurses_mouse_event { pub button: i32, pub what: i32, pub event: u64, }
#[derive(Debug, Clone, Copy)]
pub struct zcurses_namenumberpair {
pub name: &'static str,
pub number: i32,
}
pub const A_BLINK: i32 = 1 << 11;
pub const A_BOLD: i32 = 1 << 13;
pub const A_DIM: i32 = 1 << 12;
pub const A_REVERSE: i32 = 1 << 10;
pub const A_STANDOUT: i32 = 1 << 8;
pub const A_UNDERLINE: i32 = 1 << 9;
pub static zcurses_attributes: &[zcurses_namenumberpair] = &[
zcurses_namenumberpair { name: "blink", number: A_BLINK },
zcurses_namenumberpair { name: "bold", number: A_BOLD },
zcurses_namenumberpair { name: "dim", number: A_DIM },
zcurses_namenumberpair { name: "reverse", number: A_REVERSE },
zcurses_namenumberpair { name: "standout", number: A_STANDOUT },
zcurses_namenumberpair { name: "underline", number: A_UNDERLINE },
];
pub const COLOR_BLACK: i32 = 0;
pub const COLOR_RED: i32 = 1;
pub const COLOR_GREEN: i32 = 2;
pub const COLOR_YELLOW: i32 = 3;
pub const COLOR_BLUE: i32 = 4;
pub const COLOR_MAGENTA: i32 = 5;
pub const COLOR_CYAN: i32 = 6;
pub const COLOR_WHITE: i32 = 7;
pub static zcurses_colors: &[zcurses_namenumberpair] = &[
zcurses_namenumberpair { name: "black", number: COLOR_BLACK },
zcurses_namenumberpair { name: "red", number: COLOR_RED },
zcurses_namenumberpair { name: "green", number: COLOR_GREEN },
zcurses_namenumberpair { name: "yellow", number: COLOR_YELLOW },
zcurses_namenumberpair { name: "blue", number: COLOR_BLUE },
zcurses_namenumberpair { name: "magenta", number: COLOR_MAGENTA },
zcurses_namenumberpair { name: "cyan", number: COLOR_CYAN },
zcurses_namenumberpair { name: "white", number: COLOR_WHITE },
zcurses_namenumberpair { name: "default", number: -1 },
];
static zcurses_windows: OnceLock<Mutex<HashMap<String, zc_win>>> = OnceLock::new();
static WINDOW_ORDER: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
static zc_errno_cell: OnceLock<Mutex<i32>> = OnceLock::new();
static zcurses_colorpairs: OnceLock<Mutex<HashMap<String, i16>>> = OnceLock::new();
static next_cp: OnceLock<Mutex<i16>> = OnceLock::new();
fn windows_lock() -> &'static Mutex<HashMap<String, zc_win>> {
zcurses_windows.get_or_init(|| Mutex::new(HashMap::new()))
}
fn order_lock() -> &'static Mutex<Vec<String>> {
WINDOW_ORDER.get_or_init(|| Mutex::new(Vec::new()))
}
fn errno_lock() -> &'static Mutex<i32> {
zc_errno_cell.get_or_init(|| Mutex::new(0))
}
fn colorpairs_lock() -> &'static Mutex<HashMap<String, i16>> {
zcurses_colorpairs.get_or_init(|| Mutex::new(HashMap::new()))
}
fn next_cp_lock() -> &'static Mutex<i16> {
next_cp.get_or_init(|| Mutex::new(0))
}
fn zc_errno_get() -> i32 {
*errno_lock().lock().unwrap()
}
fn zc_errno_set(v: i32) {
*errno_lock().lock().unwrap() = v;
}
pub(crate) fn zcurses_strerror(err: i32) -> &'static str {
static ERRS: &[&str] = &[
"unknown error",
"window name invalid",
"window already defined",
"window undefined",
];
let idx = if !(1..=3).contains(&err) { 0 } else { err as usize };
ERRS[idx]
}
pub(crate) fn zcurses_getwindowbyname(name: &str) -> bool {
windows_lock().lock().unwrap().contains_key(name)
}
pub(crate) fn zcurses_validate_window(win: &str, criteria: i32) -> bool {
if win.is_empty() {
zc_errno_set(ZCURSES_EINVALID);
return false;
}
let target_present = zcurses_getwindowbyname(win);
if target_present && (criteria & ZCURSES_UNUSED) != 0 {
zc_errno_set(ZCURSES_EDEFINED);
return false;
}
if !target_present && (criteria & ZCURSES_USED) != 0 {
zc_errno_set(ZCURSES_EUNDEFINED);
return false;
}
zc_errno_set(0);
target_present
}
pub(crate) fn zcurses_attrget(_w: &zc_win, attr: &str) -> Option<&'static zcurses_namenumberpair> {
if attr.is_empty() {
return None;
}
zcurses_attributes.iter().find(|p| p.name == attr)
}
pub(crate) fn zcurses_color(color: &str) -> i32 {
for c in zcurses_colors {
if c.name == color {
return c.number;
}
}
-2
}
pub(crate) fn zcurses_free_window(name: &str) -> i32 {
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get(name) {
if w.flags & ZCWF_PERMANENT != 0 {
return 1;
}
}
wins.remove(name);
let mut order = order_lock().lock().unwrap();
order.retain(|n| n != name);
0
}
pub(crate) fn zccmd_init(_nam: &str, _args: &[String]) -> i32 {
if zcurses_getwindowbyname("stdscr") {
if let Ok(saved) = curses_tty_state.lock() {
if let Some(ti) = saved.as_ref() {
unsafe {
libc::tcsetattr(0, libc::TCSANOW, ti as *const _);
}
}
}
return 0;
}
{
let mut saved = curses_tty_state.lock().unwrap();
if saved.is_none() {
let mut ti: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut ti) } == 0 {
*saved = Some(ti);
}
}
}
let mut stdout = io::stdout();
let _ = write!(stdout, "\x1b[?1049h\x1b[2J\x1b[H");
let _ = stdout.flush();
let _ = cbreak();
let (rows, cols) = terminal_size().unwrap_or((24, 80));
let mut stdscr = zc_win::new("stdscr", rows, cols, 0, 0);
stdscr.flags = ZCWF_PERMANENT;
windows_lock().lock().unwrap().insert("stdscr".into(), stdscr);
order_lock().lock().unwrap().push("stdscr".into());
*next_cp_lock().lock().unwrap() = 0;
colorpairs_lock()
.lock()
.unwrap()
.insert("default/default".into(), 0);
0
}
#[allow(non_upper_case_globals)]
static curses_tty_state: std::sync::Mutex<Option<libc::termios>> =
std::sync::Mutex::new(None);
pub(crate) fn zccmd_addwin(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_UNUSED) && zc_errno_get() != 0 {
zerrnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
let nlines: usize = args[1].parse().unwrap_or(0);
let ncols: usize = args[2].parse().unwrap_or(0);
let begin_y: usize = args[3].parse().unwrap_or(0);
let begin_x: usize = args[4].parse().unwrap_or(0);
let mut w = zc_win::new(args[0].as_str(), nlines, ncols, begin_y, begin_x);
if let Some(parent_name) = args.get(5) {
if !zcurses_validate_window(parent_name.as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
w.parent = Some(parent_name.clone());
let mut wins = windows_lock().lock().unwrap();
if let Some(parent) = wins.get_mut(parent_name.as_str()) {
parent.children.push(args[0].clone());
}
}
windows_lock().lock().unwrap().insert(args[0].clone(), w);
order_lock().lock().unwrap().push(args[0].clone());
0
}
pub(crate) fn zccmd_delwin(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
if zcurses_free_window(args[0].as_str()) != 0 {
zwarnnam(nam, "can't delete permanent window");
return 1;
}
0
}
pub(crate) fn zccmd_refresh(nam: &str, args: &[String]) -> i32 {
let wins = windows_lock().lock().unwrap();
if args.is_empty() {
if let Some(stdscr) = wins.get("stdscr") {
let _ = stdscr.refresh();
}
return 0;
}
drop(wins);
for name in args {
if !zcurses_validate_window(name.as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), name),
);
return 1;
}
if let Some(w) = windows_lock().lock().unwrap().get(name.as_str()) {
let _ = w.refresh();
}
}
0
}
pub(crate) fn zccmd_move(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
let new_y: usize = args[1].parse().unwrap_or(0);
let new_x: usize = args[2].parse().unwrap_or(0);
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
if new_y < w.rows && new_x < w.cols {
w.cursor_y = new_y;
w.cursor_x = new_x;
}
}
0
}
pub(crate) fn zccmd_clear(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
for row in &mut w.buffer {
for cell in row {
*cell = ' ';
}
}
w.cursor_y = 0;
w.cursor_x = 0;
}
0
}
pub(crate) fn zccmd_string(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
for ch in args[1].chars() {
if w.cursor_y < w.rows && w.cursor_x < w.cols {
w.buffer[w.cursor_y][w.cursor_x] = ch;
w.cursor_x += 1;
if w.cursor_x >= w.cols {
w.cursor_x = 0;
if w.cursor_y + 1 < w.rows {
w.cursor_y += 1;
}
}
}
}
}
0
}
pub(crate) fn zccmd_attr(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(
nam,
&format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]),
);
return 1;
}
let mut wins = windows_lock().lock().unwrap();
let w = match wins.get_mut(args[0].as_str()) {
Some(w) => w,
None => return 1,
};
for spec in &args[1..] {
let (mode, attr_name) = match spec.as_bytes().first() {
Some(b'+') => (ZCURSES_ATTRON, &spec[1..]),
Some(b'-') => (ZCURSES_ATTROFF, &spec[1..]),
_ => (ZCURSES_ATTRON, spec.as_str()),
};
let entry = match zcurses_attrget(w, attr_name) {
Some(e) => e,
None => {
drop(wins);
zwarnnam(nam, &format!("attribute `{}' not known", attr_name));
return 1;
}
};
match mode {
ZCURSES_ATTRON => w.attrs |= entry.number as u32,
ZCURSES_ATTROFF => w.attrs &= !(entry.number as u32),
_ => {}
}
}
0
}
pub(crate) fn zccmd_endwin(_nam: &str, _args: &[String]) -> i32 {
if !zcurses_getwindowbyname("stdscr") {
return 0;
}
let mut stdout = io::stdout();
let _ = write!(stdout, "\x1b[?1049l\x1b[0m");
let _ = stdout.flush();
windows_lock().lock().unwrap().clear();
order_lock().lock().unwrap().clear();
colorpairs_lock().lock().unwrap().clear();
0
}
pub(crate) fn zccmd_char(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let ch = match args[1].chars().next() {
Some(c) => c,
None => return 1,
};
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
if w.cursor_y < w.rows && w.cursor_x < w.cols {
w.buffer[w.cursor_y][w.cursor_x] = ch;
w.cursor_x += 1;
if w.cursor_x >= w.cols {
w.cursor_x = 0;
if w.cursor_y + 1 < w.rows {
w.cursor_y += 1;
}
}
}
}
0
}
pub(crate) fn zccmd_border(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
if w.rows < 2 || w.cols < 2 {
return 0;
}
for x in 1..w.cols.saturating_sub(1) {
w.buffer[0][x] = '─';
w.buffer[w.rows - 1][x] = '─';
}
for y in 1..w.rows.saturating_sub(1) {
w.buffer[y][0] = '│';
w.buffer[y][w.cols - 1] = '│';
}
w.buffer[0][0] = '┌';
w.buffer[0][w.cols - 1] = '┐';
w.buffer[w.rows - 1][0] = '└';
w.buffer[w.rows - 1][w.cols - 1] = '┘';
}
0
}
pub(crate) fn zccmd_bg(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let mut ret = 0;
let mut ch: u32 = 0;
let mut wins = windows_lock().lock().unwrap();
let w = match wins.get_mut(args[0].as_str()) {
Some(w) => w,
None => return 1,
};
for spec in &args[1..] {
if spec.contains('/') {
let cp = colorpair_get_or_alloc(spec);
if cp < 0 {
ret = 1;
} else {
ch |= (cp as u32) << 16;
}
} else if let Some(rest) = spec.strip_prefix('@') {
if let Some(c) = rest.chars().next() {
ch |= c as u32;
}
} else {
let (mode, attr_name) = match spec.as_bytes().first() {
Some(b'+') => (ZCURSES_ATTRON, &spec[1..]),
Some(b'-') => (ZCURSES_ATTROFF, &spec[1..]),
_ => (ZCURSES_ATTRON, spec.as_str()),
};
match zcurses_attrget(w, attr_name) {
Some(p) => {
if mode == ZCURSES_ATTRON {
ch |= p.number as u32;
} else {
ch &= !(p.number as u32);
}
}
None => {
drop(wins);
zwarnnam(nam, &format!("attribute `{}' not known", attr_name));
return 1;
}
}
}
}
if ret == 0 {
w.bg_chtype = ch;
}
ret
}
pub(crate) fn zccmd_scroll(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let mut wins = windows_lock().lock().unwrap();
let w = match wins.get_mut(args[0].as_str()) {
Some(w) => w,
None => return 1,
};
match args[1].as_str() {
"on" => {
w.flags |= ZCWF_SCROLL;
}
"off" => {
w.flags &= !ZCWF_SCROLL;
}
s => {
let n: i32 = match s.parse() {
Ok(n) => n,
Err(_) => {
drop(wins);
zwarnnam(
nam,
&format!("scroll requires `on', `off' or integer: {}", s),
);
return 1;
}
};
if n > 0 {
for _ in 0..n {
if !w.buffer.is_empty() {
w.buffer.remove(0);
w.buffer.push(vec![' '; w.cols]);
}
}
} else {
for _ in 0..(-n) {
if !w.buffer.is_empty() {
w.buffer.pop();
w.buffer.insert(0, vec![' '; w.cols]);
}
}
}
}
}
0
}
pub(crate) fn zccmd_input(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let want_keypad = args.len() >= 3;
let timeout = windows_lock()
.lock()
.unwrap()
.get(args[0].as_str())
.map(|w| w.timeout_ms)
.unwrap_or(-1);
let (key_str, key_code) = match read_key_sequence(want_keypad, timeout) {
Some(pair) => pair,
None => return 1,
};
let var = args.get(1).map(|v| v.as_str()).unwrap_or("REPLY");
crate::ported::params::setsparam(var, &key_str);
if want_keypad {
if let Some(name) = args.get(2) {
let code_str = if key_code > 0 {
keypad_name(key_code).unwrap_or_else(|| key_code.to_string())
} else {
String::new()
};
crate::ported::params::setsparam(name, &code_str);
}
if args.len() >= 4 {
if let Some(mvar) = args.get(3) {
crate::ported::params::setsparam(mvar, "");
}
}
}
0
}
pub(crate) fn zccmd_timeout(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let to: i32 = match args[1].parse() {
Ok(n) => n,
Err(_) => {
zwarnnam(nam, &format!("timeout requires an integer: {}", args[1]));
return 1;
}
};
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut(args[0].as_str()) {
w.timeout_ms = to;
}
0
}
static zcurses_flags: OnceLock<Mutex<u32>> = OnceLock::new();
static zcurses_mouse_mask: OnceLock<Mutex<u32>> = OnceLock::new();
pub const ZCF_MOUSE_ACTIVE: u32 = 1 << 0;
pub const ZCF_MOUSE_MASK_CHANGED: u32 = 1 << 1;
fn flags_lock() -> &'static Mutex<u32> {
zcurses_flags.get_or_init(|| Mutex::new(0))
}
fn mouse_mask_lock() -> &'static Mutex<u32> {
zcurses_mouse_mask.get_or_init(|| Mutex::new(0x07ffffff))
}
pub const REPORT_MOUSE_POSITION: u32 = 1 << 28;
pub(crate) fn zccmd_mouse(nam: &str, args: &[String]) -> i32 {
let mut idx = 0usize;
while idx < args.len() {
let arg = args[idx].as_str();
if arg == "delay" {
idx += 1;
let v: i32 = match args.get(idx).and_then(|a| a.parse().ok()) {
Some(v) => v,
None => {
zwarnnam(nam, "mouse delay requires an integer argument");
return 1;
}
};
let _ = v;
idx += 1;
} else {
let (onoff, name) = match arg.as_bytes().first() {
Some(b'+') => (true, &arg[1..]),
Some(b'-') => (false, &arg[1..]),
_ => (true, arg),
};
if name == "motion" {
let mut mask = mouse_mask_lock().lock().unwrap();
let old = *mask;
if onoff {
*mask |= REPORT_MOUSE_POSITION;
} else {
*mask &= !REPORT_MOUSE_POSITION;
}
if old != *mask {
*flags_lock().lock().unwrap() |= ZCF_MOUSE_MASK_CHANGED;
}
} else {
zwarnnam(nam, &format!("unrecognised mouse command: {}", name));
return 1;
}
idx += 1;
}
}
0
}
pub(crate) fn zccmd_position(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let wins = windows_lock().lock().unwrap();
let w = match wins.get(args[0].as_str()) {
Some(w) => w,
None => return 1,
};
let arr = vec![
w.cursor_y.to_string(),
w.cursor_x.to_string(),
w.y.to_string(),
w.x.to_string(),
w.rows.to_string(),
w.cols.to_string(),
];
drop(wins);
crate::ported::params::setsparam(&args[1], &arr.join(":"));
0
}
pub(crate) fn zccmd_querychar(nam: &str, args: &[String]) -> i32 {
if !zcurses_validate_window(args[0].as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), args[0]));
return 1;
}
let var = args.get(1).cloned().unwrap_or_else(|| "reply".to_string());
let wins = windows_lock().lock().unwrap();
let w = match wins.get(args[0].as_str()) {
Some(w) => w,
None => return 1,
};
let ch = if w.cursor_y < w.rows && w.cursor_x < w.cols {
w.buffer[w.cursor_y][w.cursor_x]
} else {
' '
};
let mut clist: Vec<String> = Vec::new();
clist.push(ch.to_string());
clist.push("default".into());
for entry in zcurses_attributes {
if w.attrs & entry.number as u32 != 0 {
clist.push(entry.name.to_string());
}
}
drop(wins);
crate::ported::params::setsparam(&var, &clist.join(":"));
0
}
pub(crate) fn zccmd_touch(nam: &str, args: &[String]) -> i32 {
for name in args {
if !zcurses_validate_window(name.as_str(), ZCURSES_USED) {
zwarnnam(nam, &format!("{}: {}", zcurses_strerror(zc_errno_get()), name));
return 1;
}
}
0
}
pub(crate) fn zccmd_resize(nam: &str, args: &[String]) -> i32 {
if !zcurses_getwindowbyname("stdscr") {
return 1;
}
let y: usize = args[0].parse().unwrap_or(0);
let x: usize = args[1].parse().unwrap_or(0);
if let Some(third) = args.get(2) {
match third.as_str() {
"endwin" | "nosave" | "endwin_nosave" => {}
other => {
zwarnnam(
nam,
&format!(
"`resize' expects `endwin', `nosave' or `endwin_nosave' for third argument, if given: {}",
other
),
);
return 1;
}
}
}
if y == 0 && x == 0 && args.get(2).is_none() {
return 0;
}
let mut wins = windows_lock().lock().unwrap();
if let Some(w) = wins.get_mut("stdscr") {
w.rows = y;
w.cols = x;
w.buffer = vec![vec![' '; x]; y];
w.cursor_y = w.cursor_y.min(y.saturating_sub(1));
w.cursor_x = w.cursor_x.min(x.saturating_sub(1));
}
0
}
struct zcurses_subcommand {
name: &'static str,
cmd: fn(&str, &[String]) -> i32, minargs: i32,
maxargs: i32,
}
static SCS: &[zcurses_subcommand] = &[
zcurses_subcommand { name: "init", cmd: zccmd_init, minargs: 0, maxargs: 0 },
zcurses_subcommand { name: "addwin", cmd: zccmd_addwin, minargs: 5, maxargs: 6 },
zcurses_subcommand { name: "delwin", cmd: zccmd_delwin, minargs: 1, maxargs: 1 },
zcurses_subcommand { name: "refresh", cmd: zccmd_refresh, minargs: 0, maxargs: -1 },
zcurses_subcommand { name: "move", cmd: zccmd_move, minargs: 3, maxargs: 3 },
zcurses_subcommand { name: "clear", cmd: zccmd_clear, minargs: 1, maxargs: 2 },
zcurses_subcommand { name: "position", cmd: zccmd_position, minargs: 2, maxargs: 2 },
zcurses_subcommand { name: "char", cmd: zccmd_char, minargs: 2, maxargs: 2 },
zcurses_subcommand { name: "string", cmd: zccmd_string, minargs: 2, maxargs: 2 },
zcurses_subcommand { name: "border", cmd: zccmd_border, minargs: 1, maxargs: 1 },
zcurses_subcommand { name: "end", cmd: zccmd_endwin, minargs: 0, maxargs: 0 },
zcurses_subcommand { name: "attr", cmd: zccmd_attr, minargs: 2, maxargs: -1 },
zcurses_subcommand { name: "bg", cmd: zccmd_bg, minargs: 2, maxargs: -1 },
zcurses_subcommand { name: "scroll", cmd: zccmd_scroll, minargs: 2, maxargs: 2 },
zcurses_subcommand { name: "input", cmd: zccmd_input, minargs: 1, maxargs: 4 },
zcurses_subcommand { name: "timeout", cmd: zccmd_timeout, minargs: 2, maxargs: 2 },
zcurses_subcommand { name: "mouse", cmd: zccmd_mouse, minargs: 0, maxargs: -1 },
zcurses_subcommand { name: "querychar", cmd: zccmd_querychar, minargs: 1, maxargs: 2 },
zcurses_subcommand { name: "touch", cmd: zccmd_touch, minargs: 1, maxargs: -1 },
zcurses_subcommand { name: "resize", cmd: zccmd_resize, minargs: 2, maxargs: 3 },
];
pub(crate) fn colorpair_get_or_alloc(spec: &str) -> i32 {
let mut pairs = colorpairs_lock().lock().unwrap();
if let Some(&n) = pairs.get(spec) {
return n as i32;
}
let slash = match spec.find('/') {
Some(p) => p,
None => return -1,
};
let fg = &spec[..slash];
let bg = &spec[slash + 1..];
let _fg_n = zcurses_color(fg);
let _bg_n = zcurses_color(bg);
if _fg_n == -2 || _bg_n == -2 {
return -1;
}
let mut np = next_cp_lock().lock().unwrap();
*np = np.saturating_add(1);
let cp = *np;
pairs.insert(spec.to_string(), cp);
cp as i32
}
pub(crate) fn bin_zcurses(nam: &str, args: &[String],
_ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if args.is_empty() {
zwarnnam(nam, "subcommand required");
return 1;
}
let cmd_name = args[0].as_str();
let entry = match SCS.iter().find(|sc| sc.name == cmd_name) {
Some(e) => e,
None => {
zwarnnam(nam, &format!("unknown subcommand: {}", cmd_name));
return 1;
}
};
let sub_args = &args[1..];
let num_args = sub_args.len() as i32;
if num_args < entry.minargs {
zwarnnam(nam, &format!("too few arguments for subcommand: {}", cmd_name));
return 1;
}
if entry.maxargs >= 0 && num_args > entry.maxargs {
zwarnnam(nam, &format!("too many arguments for subcommand: {}", cmd_name));
return 1;
}
let is_init = matches!(cmd_name, "init");
let is_end = matches!(cmd_name, "end");
if !is_init && !is_end && !zcurses_getwindowbyname("stdscr") {
zwarnnam(
nam,
&format!("command `{}' can't be used before `zcurses init'", cmd_name),
);
return 1;
}
(entry.cmd)(nam, sub_args)
}
fn read_key_sequence(want_keypad: bool, _timeout_ms: i32) -> Option<(String, i32)> {
#[cfg(unix)]
{
let mut buf = [0u8; 1];
let n = io::stdin().read(&mut buf).ok()?;
if n == 0 {
return None;
}
let c = buf[0];
if c == 0x1b && want_keypad {
let mut seq = vec![c];
for _ in 0..6 {
let mut next = [0u8; 1];
if io::stdin().read(&mut next).ok()? == 0 {
break;
}
seq.push(next[0]);
if seq.len() >= 3 && (seq[2] >= 0x40 && seq[2] <= 0x7e) {
break;
}
if seq.len() >= 4 && seq[seq.len() - 1] >= 0x40 && seq[seq.len() - 1] <= 0x7e {
break;
}
}
if let Some(code) = csi_to_keypad(&seq) {
return Some((String::new(), code));
}
}
Some(((c as char).to_string(), 0))
}
#[cfg(not(unix))]
{
let _ = (want_keypad, _timeout_ms);
None
}
}
fn csi_to_keypad(seq: &[u8]) -> Option<i32> {
if seq.len() < 3 || seq[0] != 0x1b {
return None;
}
if seq[1] == b'[' {
match seq[2] {
b'A' => Some(KEY_UP),
b'B' => Some(KEY_DOWN),
b'C' => Some(KEY_RIGHT),
b'D' => Some(KEY_LEFT),
b'H' => Some(KEY_HOME),
b'F' => Some(KEY_END),
b'1' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_HOME),
b'2' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_IC),
b'3' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_DC),
b'4' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_END),
b'5' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_PPAGE),
b'6' if seq.len() >= 4 && seq[3] == b'~' => Some(KEY_NPAGE),
_ => None,
}
} else if seq[1] == b'O' {
match seq[2] {
b'A' => Some(KEY_UP),
b'B' => Some(KEY_DOWN),
b'C' => Some(KEY_RIGHT),
b'D' => Some(KEY_LEFT),
b'P' => Some(KEY_F0 + 1),
b'Q' => Some(KEY_F0 + 2),
b'R' => Some(KEY_F0 + 3),
b'S' => Some(KEY_F0 + 4),
_ => None,
}
} else {
None
}
}
pub const KEY_DOWN: i32 = 0o402;
pub const KEY_UP: i32 = 0o403;
pub const KEY_LEFT: i32 = 0o404;
pub const KEY_RIGHT: i32 = 0o405;
pub const KEY_HOME: i32 = 0o406;
pub const KEY_DC: i32 = 0o512;
pub const KEY_IC: i32 = 0o513;
pub const KEY_NPAGE: i32 = 0o522;
pub const KEY_PPAGE: i32 = 0o523;
pub const KEY_END: i32 = 0o550;
pub const KEY_F0: i32 = 0o410;
fn keypad_name(code: i32) -> Option<String> {
let name = match code {
KEY_DOWN => "DOWN",
KEY_UP => "UP",
KEY_LEFT => "LEFT",
KEY_RIGHT => "RIGHT",
KEY_HOME => "HOME",
KEY_END => "END",
KEY_DC => "DC",
KEY_IC => "IC",
KEY_NPAGE => "NPAGE",
KEY_PPAGE => "PPAGE",
c if c > KEY_F0 => return Some(format!("F{}", c - KEY_F0)),
_ => return None,
};
Some(name.to_string())
}
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features_t {
bn_list: None, bn_size: 1, cd_list: None, cd_size: 0,
mf_list: None, mf_size: 0,
pd_list: None, pd_size: 0,
n_abstract: 0, })
})
}
pub fn zcurses_pairs_to_array(nnps: &[zcurses_namenumberpair]) -> Vec<String>
{
nnps.iter().map(|p| p.name.to_string()).collect() }
pub fn zcurses_colornode(name: &str, pair: i16, cp: i16) -> Option<String> { if pair == cp { Some(name.to_string()) } else { None } }
pub fn zcurses_colorget_reverse(cp: i16) -> Option<String> { let g = colorpairs_lock().lock().ok()?; if g.is_empty() { return None; } for (name, pair) in g.iter() { if let Some(matched) = zcurses_colornode(name, *pair, cp) { return Some(matched); }
}
None
}
pub fn zcurses_colorget(nam: &str, colorpair: &str) -> Option<i16> { {
let g = colorpairs_lock().lock().ok()?;
if let Some(&cp) = g.get(colorpair) { return Some(cp);
}
}
let (cp_str, bg_str) = match colorpair.split_once('/') { Some(t) => t,
None => return None, };
let f: i16 = if !cp_str.is_empty()
&& cp_str.chars().next().unwrap_or('x').is_ascii_digit() { cp_str.parse::<i16>().unwrap_or(-2) } else {
zcurses_color(cp_str) as i16 };
let b: i16 = if !bg_str.is_empty()
&& bg_str.chars().next().unwrap_or('x').is_ascii_digit() { bg_str.parse::<i16>().unwrap_or(-2) } else {
zcurses_color(bg_str) as i16 };
if f == -2 || b == -2 { if f == -2 {
crate::ported::utils::zwarnnam(nam, &format!("foreground color `{}' not known", cp_str));
}
if b == -2 {
crate::ported::utils::zwarnnam(nam, &format!("background color `{}' not known", bg_str));
}
return None; }
let mut np = next_cp_lock().lock().ok()?;
*np += 1; let cp_slot = *np;
if cp_slot < 0 || cp_slot >= 1024 { return None;
}
let _ = (f, b); drop(np);
let mut g = colorpairs_lock().lock().ok()?;
g.insert(colorpair.to_string(), cp_slot); Some(cp_slot) }
pub fn freecolorpairnode(hn: &str) { }
pub fn zcurses_colorsarrgetfn() -> Vec<String> { zcurses_pairs_to_array(zcurses_colors) }
pub fn zcurses_attrgetfn() -> Vec<String> { zcurses_pairs_to_array(zcurses_attributes) }
pub fn zcurses_keycodesgetfn() -> Vec<String> { let mut out = Vec::new();
for code in [KEY_DOWN, KEY_UP, KEY_LEFT, KEY_RIGHT, KEY_HOME, KEY_END,
KEY_DC, KEY_IC, KEY_NPAGE, KEY_PPAGE] {
if let Some(n) = keypad_name(code) { out.push(n); }
}
for f in 1..=64 { if let Some(n) = keypad_name(KEY_F0 + f) { out.push(n); }
}
out
}
pub fn zcurses_windowsgetfn() -> Vec<String> { order_lock().lock().map(|g| g.clone()).unwrap_or_default() }
pub fn zcurses_colorsintgetfn() -> i64 { let cap = std::ffi::CString::new("colors").unwrap();
let n = unsafe { libc_tigetnum(cap.as_ptr()) }; if n < 0 { 0 } else { n as i64 }
}
pub fn zcurses_colorpairsintgetfn() -> i64 { let cap = std::ffi::CString::new("pairs").unwrap();
let n = unsafe { libc_tigetnum(cap.as_ptr()) }; if n < 0 { 0 } else { n as i64 }
}
#[link(name = "ncurses")]
extern "C" {
#[link_name = "tigetnum"]
fn libc_tigetnum(name: *const libc::c_char) -> libc::c_int;
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables)
}
pub fn boot_(_m: *const module) -> i32 { 0
}
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None)
}
pub fn finish_(_m: *const module) -> i32 { 0
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:zcurses".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn terminal_size() -> Option<(usize, usize)> {
#[cfg(unix)]
{
let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
let result = unsafe { libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut ws) };
if result == 0 && ws.ws_row > 0 && ws.ws_col > 0 {
return Some((ws.ws_row as usize, ws.ws_col as usize));
}
}
let lines = crate::ported::params::getiparam("LINES");
let cols = crate::ported::params::getiparam("COLUMNS");
if lines > 0 && cols > 0 {
Some((lines as usize, cols as usize))
} else {
None
}
}
#[cfg(unix)]
fn cbreak() -> io::Result<()> {
let mut termios: libc::termios = unsafe { std::mem::zeroed() };
unsafe {
if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) < 0 {
return Err(io::Error::last_os_error());
}
termios.c_lflag &= !(libc::ICANON | libc::ECHO);
termios.c_cc[libc::VMIN] = 1;
termios.c_cc[libc::VTIME] = 0;
if libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
#[cfg(not(unix))]
fn cbreak() -> io::Result<()> {
Ok(())
}
fn sgr_for_attrs(attrs: u32) -> String {
let mut s = "\x1b[0".to_string();
if attrs & A_BOLD as u32 != 0 {
s.push_str(";1");
}
if attrs & A_DIM as u32 != 0 {
s.push_str(";2");
}
if attrs & A_UNDERLINE as u32 != 0 {
s.push_str(";4");
}
if attrs & A_BLINK as u32 != 0 {
s.push_str(";5");
}
if attrs & A_REVERSE as u32 != 0 || attrs & A_STANDOUT as u32 != 0 {
s.push_str(";7");
}
s.push('m');
s
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
static TEST_SERIAL: StdMutex<()> = StdMutex::new(());
#[allow(non_upper_case_globals)]
const _test_ops_: crate::ported::zsh_h::options =
crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
fn reset() -> std::sync::MutexGuard<'static, ()> {
let guard = TEST_SERIAL.lock().unwrap_or_else(|e| {
TEST_SERIAL.clear_poison();
e.into_inner()
});
windows_lock().lock().unwrap_or_else(|e| {
windows_lock().clear_poison();
e.into_inner()
}).clear();
order_lock().lock().unwrap_or_else(|e| {
order_lock().clear_poison();
e.into_inner()
}).clear();
colorpairs_lock().lock().unwrap_or_else(|e| {
colorpairs_lock().clear_poison();
e.into_inner()
}).clear();
zc_errno_set(0);
guard
}
#[test]
fn test_zcurses_strerror_table() {
assert_eq!(zcurses_strerror(0), "unknown error");
assert_eq!(zcurses_strerror(1), "window name invalid");
assert_eq!(zcurses_strerror(2), "window already defined");
assert_eq!(zcurses_strerror(3), "window undefined");
assert_eq!(zcurses_strerror(99), "unknown error");
}
#[test]
fn test_zcurses_color_lookup() {
assert_eq!(zcurses_color("red"), COLOR_RED);
assert_eq!(zcurses_color("default"), -1);
assert_eq!(zcurses_color("magenta"), COLOR_MAGENTA);
assert_eq!(zcurses_color("unknown"), -2);
}
#[test]
fn test_zcurses_validate_window_invalid_name() {
let _g = reset();
assert!(!zcurses_validate_window("", ZCURSES_UNUSED));
assert_eq!(zc_errno_get(), ZCURSES_EINVALID);
}
#[test]
fn test_zcurses_validate_window_unused_when_already_defined() {
let _g = reset();
windows_lock().lock().unwrap().insert(
"win1".into(),
zc_win::new("win1", 5, 10, 0, 0),
);
assert!(!zcurses_validate_window("win1", ZCURSES_UNUSED));
assert_eq!(zc_errno_get(), ZCURSES_EDEFINED);
}
#[test]
fn test_zcurses_validate_window_used_when_undefined() {
let _g = reset();
assert!(!zcurses_validate_window("nope", ZCURSES_USED));
assert_eq!(zc_errno_get(), ZCURSES_EUNDEFINED);
}
#[test]
fn test_zcurses_validate_window_success_used() {
let _g = reset();
windows_lock().lock().unwrap().insert(
"stdscr".into(),
zc_win::new("stdscr", 24, 80, 0, 0),
);
assert!(zcurses_validate_window("stdscr", ZCURSES_USED));
assert_eq!(zc_errno_get(), 0);
}
#[test]
fn test_zcurses_attrget_lookup() {
let w = zc_win::new("test", 5, 10, 0, 0);
let bold = zcurses_attrget(&w, "bold").expect("bold should resolve");
assert_eq!(bold.number, A_BOLD);
assert!(zcurses_attrget(&w, "unknown_attr").is_none());
}
#[test]
fn test_features_returns_bintab_names() {
let mut features: Vec<String> = Vec::new();
let rc = features_(std::ptr::null(), &mut features);
assert_eq!(rc, 0);
assert_eq!(features, vec!["b:zcurses"]);
}
#[test]
fn test_cleanup_returns_zero() {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
#[test]
fn test_bin_zcurses_init_then_addwin_then_delwin() {
let _g = reset();
let init_args: Vec<String> = vec!["init".into()];
assert_eq!(bin_zcurses("zcurses", &init_args, &_test_ops_, 0), 0);
assert!(zcurses_getwindowbyname("stdscr"));
let add_args: Vec<String> = vec![
"addwin".into(),
"win1".into(),
"5".into(),
"10".into(),
"0".into(),
"0".into(),
];
assert_eq!(bin_zcurses("zcurses", &add_args, &_test_ops_, 0), 0);
assert!(zcurses_getwindowbyname("win1"));
let del_args: Vec<String> = vec!["delwin".into(), "win1".into()];
assert_eq!(bin_zcurses("zcurses", &del_args, &_test_ops_, 0), 0);
assert!(!zcurses_getwindowbyname("win1"));
}
#[test]
fn test_bin_zcurses_addwin_before_init_rejected() {
let _g = reset();
let add_args: Vec<String> = vec![
"addwin".into(),
"win1".into(),
"5".into(),
"10".into(),
"0".into(),
"0".into(),
];
assert_eq!(bin_zcurses("zcurses", &add_args, &_test_ops_, 0), 1);
}
#[test]
fn test_bin_zcurses_addwin_duplicate_rejected() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add_args: Vec<String> = vec![
"addwin".into(),
"win1".into(),
"5".into(),
"10".into(),
"0".into(),
"0".into(),
];
assert_eq!(bin_zcurses("zcurses", &add_args, &_test_ops_, 0), 0);
assert_eq!(bin_zcurses("zcurses", &add_args, &_test_ops_, 0), 1);
assert_eq!(zc_errno_get(), ZCURSES_EDEFINED);
}
#[test]
fn test_bin_zcurses_delwin_undefined_rejected() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let del_args: Vec<String> = vec!["delwin".into(), "ghost".into()];
assert_eq!(bin_zcurses("zcurses", &del_args, &_test_ops_, 0), 1);
assert_eq!(zc_errno_get(), ZCURSES_EUNDEFINED);
}
#[test]
fn test_bin_zcurses_delwin_stdscr_rejected() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let del_args: Vec<String> = vec!["delwin".into(), "stdscr".into()];
assert_eq!(bin_zcurses("zcurses", &del_args, &_test_ops_, 0), 1);
}
#[test]
fn test_bin_zcurses_too_few_args() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let bad_args: Vec<String> = vec!["addwin".into(), "win1".into()];
assert_eq!(bin_zcurses("zcurses", &bad_args, &_test_ops_, 0), 1);
}
#[test]
fn test_bin_zcurses_unknown_subcommand() {
let _g = reset();
assert_eq!(
bin_zcurses("zcurses", &["nope".into()], &_test_ops_, 0),
1
);
}
#[test]
fn test_bin_zcurses_no_args() {
let _g = reset();
assert_eq!(bin_zcurses("zcurses", &[], &_test_ops_, 0), 1);
}
#[test]
fn test_zccmd_char_writes_into_buffer() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "w".into(), "5".into(), "10".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
let ch: Vec<String> = vec!["char".into(), "w".into(), "X".into()];
assert_eq!(bin_zcurses("zcurses", &ch, &_test_ops_, 0), 0);
let wins = windows_lock().lock().unwrap();
assert_eq!(wins.get("w").unwrap().buffer[0][0], 'X');
assert_eq!(wins.get("w").unwrap().cursor_x, 1);
}
#[test]
fn test_zccmd_border_draws_box() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "box".into(), "3".into(), "5".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
assert_eq!(
bin_zcurses("zcurses", &["border".into(), "box".into()], &_test_ops_, 0),
0
);
let wins = windows_lock().lock().unwrap();
let b = &wins.get("box").unwrap().buffer;
assert_eq!(b[0][0], '┌');
assert_eq!(b[0][4], '┐');
assert_eq!(b[2][0], '└');
assert_eq!(b[2][4], '┘');
assert_eq!(b[1][0], '│');
assert_eq!(b[0][2], '─');
}
#[test]
fn test_zccmd_scroll_on_off() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "sw".into(), "5".into(), "10".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
let on: Vec<String> = vec!["scroll".into(), "sw".into(), "on".into()];
assert_eq!(bin_zcurses("zcurses", &on, &_test_ops_, 0), 0);
assert_eq!(
windows_lock().lock().unwrap().get("sw").unwrap().flags & ZCWF_SCROLL,
ZCWF_SCROLL
);
let off: Vec<String> = vec!["scroll".into(), "sw".into(), "off".into()];
assert_eq!(bin_zcurses("zcurses", &off, &_test_ops_, 0), 0);
assert_eq!(
windows_lock().lock().unwrap().get("sw").unwrap().flags & ZCWF_SCROLL,
0
);
}
#[test]
fn test_zccmd_scroll_integer_advances_buffer() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "sw".into(), "3".into(), "5".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
windows_lock().lock().unwrap().get_mut("sw").unwrap().buffer[0][0] = 'A';
let scr: Vec<String> = vec!["scroll".into(), "sw".into(), "1".into()];
assert_eq!(bin_zcurses("zcurses", &scr, &_test_ops_, 0), 0);
let wins = windows_lock().lock().unwrap();
assert_eq!(wins.get("sw").unwrap().buffer[0][0], ' ');
}
#[test]
fn test_zccmd_timeout_stores_value() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "tw".into(), "5".into(), "10".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
let to: Vec<String> = vec!["timeout".into(), "tw".into(), "100".into()];
assert_eq!(bin_zcurses("zcurses", &to, &_test_ops_, 0), 0);
assert_eq!(
windows_lock().lock().unwrap().get("tw").unwrap().timeout_ms,
100
);
}
#[test]
fn test_zccmd_touch_validates_each() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let touch_ok: Vec<String> = vec!["touch".into(), "stdscr".into()];
assert_eq!(bin_zcurses("zcurses", &touch_ok, &_test_ops_, 0), 0);
let touch_bad: Vec<String> = vec!["touch".into(), "ghost".into()];
assert_eq!(bin_zcurses("zcurses", &touch_bad, &_test_ops_, 0), 1);
assert_eq!(zc_errno_get(), ZCURSES_EUNDEFINED);
}
#[test]
fn test_zccmd_resize_changes_stdscr() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let rs: Vec<String> = vec!["resize".into(), "30".into(), "100".into()];
assert_eq!(bin_zcurses("zcurses", &rs, &_test_ops_, 0), 0);
let wins = windows_lock().lock().unwrap();
let stdscr = wins.get("stdscr").unwrap();
assert_eq!(stdscr.rows, 30);
assert_eq!(stdscr.cols, 100);
assert_eq!(stdscr.buffer.len(), 30);
assert_eq!(stdscr.buffer[0].len(), 100);
}
#[test]
fn test_zccmd_resize_bad_third_arg() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let rs: Vec<String> = vec![
"resize".into(),
"30".into(),
"100".into(),
"junk".into(),
];
assert_eq!(bin_zcurses("zcurses", &rs, &_test_ops_, 0), 1);
}
#[test]
fn test_zccmd_mouse_motion_toggle() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let mouse_on: Vec<String> = vec!["mouse".into(), "+motion".into()];
assert_eq!(bin_zcurses("zcurses", &mouse_on, &_test_ops_, 0), 0);
assert_ne!(
*mouse_mask_lock().lock().unwrap() & REPORT_MOUSE_POSITION,
0
);
let mouse_off: Vec<String> = vec!["mouse".into(), "-motion".into()];
assert_eq!(bin_zcurses("zcurses", &mouse_off, &_test_ops_, 0), 0);
assert_eq!(
*mouse_mask_lock().lock().unwrap() & REPORT_MOUSE_POSITION,
0
);
}
#[test]
fn test_zccmd_bg_with_color_pair() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let add: Vec<String> = vec![
"addwin".into(), "bgw".into(), "5".into(), "10".into(),
"0".into(), "0".into(),
];
bin_zcurses("zcurses", &add, &_test_ops_, 0);
let bg: Vec<String> = vec!["bg".into(), "bgw".into(), "red/black".into()];
assert_eq!(bin_zcurses("zcurses", &bg, &_test_ops_, 0), 0);
assert_ne!(
windows_lock().lock().unwrap().get("bgw").unwrap().bg_chtype,
0
);
}
#[test]
fn test_addwin_with_parent() {
let _g = reset();
bin_zcurses("zcurses", &["init".into()], &_test_ops_, 0);
let parent_args: Vec<String> = vec![
"addwin".into(),
"parent".into(),
"10".into(),
"20".into(),
"0".into(),
"0".into(),
];
assert_eq!(bin_zcurses("zcurses", &parent_args, &_test_ops_, 0), 0);
let child_args: Vec<String> = vec![
"addwin".into(),
"child".into(),
"5".into(),
"10".into(),
"1".into(),
"1".into(),
"parent".into(),
];
assert_eq!(bin_zcurses("zcurses", &child_args, &_test_ops_, 0), 0);
let wins = windows_lock().lock().unwrap();
assert_eq!(wins.get("child").unwrap().parent.as_deref(), Some("parent"));
assert_eq!(wins.get("parent").unwrap().children, vec!["child".to_string()]);
}
}