#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use std::io::{self, Write};
use std::sync::atomic::{AtomicI32, Ordering};
use crossterm::cursor::{Hide, MoveTo, Show};
use crossterm::style::{
Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor,
};
use crossterm::{queue, terminal};
use crate::ported::crt::{
ColorElements, ColorScheme, ResolvedColor, A_BLINK, A_BOLD, A_DIM, A_REVERSE, A_STANDOUT,
A_UNDERLINE, KEY_F,
};
const FUNCTIONBAR_MAXEVENTS: usize = 11;
const ERR: i32 = -1;
const FunctionBar_FKeys: [&str; 10] = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10"];
const FunctionBar_FLabels: [&str; 10] = [
" ", " ", " ", " ", " ", " ", " ", " ", " ",
" ",
];
const FunctionBar_FEvents: [i32; 10] = [
KEY_F(1),
KEY_F(2),
KEY_F(3),
KEY_F(4),
KEY_F(5),
KEY_F(6),
KEY_F(7),
KEY_F(8),
KEY_F(9),
KEY_F(10),
];
const FunctionBar_EnterEscKeys: [&str; 2] = ["Enter", "Esc"];
const FunctionBar_EnterEscEvents: [i32; 2] = [13, 27];
static currentLen: AtomicI32 = AtomicI32::new(0);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionBar {
pub functions: Vec<String>,
pub keys: Vec<String>,
pub events: Vec<i32>,
pub staticData: bool,
}
pub fn FunctionBar_newEnterEsc(enter: &str, esc: &str) -> FunctionBar {
let functions = [enter, esc];
FunctionBar_new(
Some(&functions),
Some(&FunctionBar_EnterEscKeys),
Some(&FunctionBar_EnterEscEvents),
)
}
pub fn FunctionBar_new(
functions: Option<&[&str]>,
keys: Option<&[&str]>,
events: Option<&[i32]>,
) -> FunctionBar {
let funcs: Vec<String> = match functions {
Some(f) => f.iter().map(|s| s.to_string()).collect(),
None => FunctionBar_FLabels.iter().map(|s| s.to_string()).collect(),
};
debug_assert!(funcs.len() <= FUNCTIONBAR_MAXEVENTS);
match (keys, events) {
(Some(k), Some(e)) => {
let keysv: Vec<String> = (0..funcs.len()).map(|i| k[i].to_string()).collect();
let eventsv: Vec<i32> = (0..funcs.len()).map(|i| e[i]).collect();
FunctionBar {
functions: funcs,
keys: keysv,
events: eventsv,
staticData: false,
}
}
_ => FunctionBar {
functions: funcs,
keys: FunctionBar_FKeys.iter().map(|s| s.to_string()).collect(),
events: FunctionBar_FEvents.to_vec(),
staticData: true,
},
}
}
pub fn FunctionBar_delete(this: FunctionBar) {
let _ = this;
}
pub fn FunctionBar_setLabel(this: &mut FunctionBar, event: i32, text: &str) {
for i in 0..this.functions.len() {
debug_assert!(i < FUNCTIONBAR_MAXEVENTS);
if this.events[i] == event {
this.functions[i] = text.to_string();
break;
}
}
}
pub fn FunctionBar_draw(this: &FunctionBar) -> i32 {
FunctionBar_drawExtra(this, None, -1, false)
}
pub fn FunctionBar_drawExtra(
this: &FunctionBar,
buffer: Option<&str>,
attr: i32,
setCursor: bool,
) -> i32 {
let (cursorX, endX) = this.extra_layout(buffer);
let line = Ncurses::lines() - 1;
let mut out = io::stdout().lock();
Ncurses::attrset(
&mut out,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
Ncurses::mvhline(&mut out, line, 0, ' ', Ncurses::cols());
let mut x = 0i32;
for i in 0..this.functions.len() {
debug_assert!(i < FUNCTIONBAR_MAXEVENTS);
Ncurses::attrset(
&mut out,
ColorElements::FUNCTION_KEY.packed(ColorScheme::active()),
);
Ncurses::mvaddstr(&mut out, line, x, &this.keys[i]);
x += this.keys[i].len() as i32;
Ncurses::attrset(
&mut out,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
Ncurses::mvaddstr(&mut out, line, x, &this.functions[i]);
x += this.functions[i].len() as i32;
}
if let Some(b) = buffer {
let a = if attr == -1 {
ColorElements::FUNCTION_BAR.packed(ColorScheme::active())
} else {
attr
};
Ncurses::attrset(&mut out, a);
Ncurses::mvaddstr(&mut out, line, x, b);
}
Ncurses::attrset(
&mut out,
ColorElements::RESET_COLOR.packed(ColorScheme::active()),
);
Ncurses::curs_set(&mut out, setCursor);
let _ = out.flush();
currentLen.store(endX, Ordering::Relaxed);
cursorX
}
pub fn FunctionBar_append(buffer: &str, attr: i32) {
let cur = currentLen.load(Ordering::Relaxed);
let line = Ncurses::lines() - 1;
let mut out = io::stdout().lock();
let a = if attr == -1 {
ColorElements::FUNCTION_BAR.packed(ColorScheme::active())
} else {
attr
};
Ncurses::attrset(&mut out, a);
Ncurses::mvaddstr(&mut out, line, cur + 1, buffer);
Ncurses::attrset(
&mut out,
ColorElements::RESET_COLOR.packed(ColorScheme::active()),
);
let _ = out.flush();
currentLen.store(cur + buffer.len() as i32 + 1, Ordering::Relaxed);
}
pub fn FunctionBar_getWidth(this: &FunctionBar) -> i32 {
let mut x: i32 = 0;
for i in 0..this.functions.len() {
debug_assert!(i < FUNCTIONBAR_MAXEVENTS);
x += this.keys[i].len() as i32;
x += this.functions[i].len() as i32;
}
x
}
pub fn FunctionBar_synthesizeEvent(this: &FunctionBar, pos: i32) -> i32 {
let mut x: i32 = 0;
for i in 0..this.functions.len() {
debug_assert!(i < FUNCTIONBAR_MAXEVENTS);
x += this.keys[i].len() as i32;
x += this.functions[i].len() as i32;
if pos < x {
return this.events[i];
}
}
ERR
}
impl FunctionBar {
fn extra_layout(&self, buffer: Option<&str>) -> (i32, i32) {
let mut x = 0i32;
for i in 0..self.functions.len() {
x += self.keys[i].len() as i32;
x += self.functions[i].len() as i32;
}
let mut cursor_x = x;
if let Some(b) = buffer {
x += b.len() as i32;
cursor_x = x;
}
(cursor_x, x)
}
}
pub(crate) struct Ncurses;
impl Ncurses {
fn margin() -> i32 {
crate::extensions::overlay::border_margin() as i32
}
pub(crate) fn lines() -> i32 {
let raw = terminal::size().map(|(_c, r)| r as i32).unwrap_or(24);
(raw - 2 * Self::margin()).max(1)
}
pub(crate) fn cols() -> i32 {
let raw = terminal::size().map(|(c, _r)| c as i32).unwrap_or(80);
(raw - 2 * Self::margin()).max(1)
}
fn to_color(n: i16) -> Color {
if let Some(idx) = crate::extensions::colors::remap(n) {
return Color::AnsiValue(idx);
}
match n {
0 => Color::Black,
1 => Color::DarkRed,
2 => Color::DarkGreen,
3 => Color::DarkYellow,
4 => Color::DarkBlue,
5 => Color::DarkMagenta,
6 => Color::DarkCyan,
7 => Color::Grey,
8 => Color::DarkGrey,
_ => Color::Reset,
}
}
pub(crate) fn attrset<W: Write>(out: &mut W, attr: i32) {
let rc = ResolvedColor::from_attr(attr, ColorScheme::active(), true);
let _ = queue!(out, SetAttribute(Attribute::Reset));
let _ = queue!(
out,
SetForegroundColor(Self::to_color(rc.fg)),
SetBackgroundColor(Self::to_color(rc.bg))
);
if rc.attributes & A_BOLD != 0 {
let _ = queue!(out, SetAttribute(Attribute::Bold));
}
if rc.attributes & A_DIM != 0 {
let _ = queue!(out, SetAttribute(Attribute::Dim));
}
if rc.attributes & (A_REVERSE | A_STANDOUT) != 0 {
let _ = queue!(out, SetAttribute(Attribute::Reverse));
}
if rc.attributes & A_UNDERLINE != 0 {
let _ = queue!(out, SetAttribute(Attribute::Underlined));
}
if rc.attributes & A_BLINK != 0 {
let _ = queue!(out, SetAttribute(Attribute::SlowBlink));
}
}
pub(crate) fn mvaddstr<W: Write>(out: &mut W, y: i32, x: i32, s: &str) {
if y < 0 || x < 0 {
return;
}
let m = Self::margin();
let _ = queue!(out, MoveTo((x + m) as u16, (y + m) as u16), Print(s));
}
pub(crate) fn mvaddnstr<W: Write>(out: &mut W, y: i32, x: i32, s: &str, n: i32) {
if y < 0 || x < 0 || n <= 0 {
return;
}
let end = (n as usize).min(s.len());
let m = Self::margin();
let _ = queue!(out, MoveTo((x + m) as u16, (y + m) as u16), Print(&s[..end]));
}
pub(crate) fn mvaddch<W: Write>(out: &mut W, y: i32, x: i32, ch: char) {
if y < 0 || x < 0 {
return;
}
let m = Self::margin();
let _ = queue!(out, MoveTo((x + m) as u16, (y + m) as u16), Print(ch));
}
pub(crate) fn mvhline<W: Write>(out: &mut W, y: i32, x: i32, ch: char, n: i32) {
if y < 0 || x < 0 || n <= 0 {
return;
}
let run: String = std::iter::repeat_n(ch, n as usize).collect();
let m = Self::margin();
let _ = queue!(out, MoveTo((x + m) as u16, (y + m) as u16), Print(run));
}
pub(crate) fn mvvline<W: Write>(out: &mut W, y: i32, x: i32, ch: char, n: i32) {
if y < 0 || x < 0 || n <= 0 {
return;
}
let m = Self::margin();
for k in 0..n {
let _ = queue!(out, MoveTo((x + m) as u16, (y + k + m) as u16), Print(ch));
}
}
pub(crate) fn move_to<W: Write>(out: &mut W, y: i32, x: i32) {
if y < 0 || x < 0 {
return;
}
let m = Self::margin();
let _ = queue!(out, MoveTo((x + m) as u16, (y + m) as u16));
}
pub(crate) fn napms(ms: i32) {
if ms > 0 {
std::thread::sleep(std::time::Duration::from_millis(ms as u64));
}
}
pub(crate) fn curs_set<W: Write>(out: &mut W, on: bool) {
let _ = if on {
queue!(out, Show)
} else {
queue!(out, Hide)
};
}
pub(crate) fn clear<W: Write>(out: &mut W) {
let _ = queue!(out, terminal::Clear(terminal::ClearType::All), MoveTo(0, 0));
}
pub(crate) fn addstr<W: Write>(out: &mut W, s: &str) {
let _ = queue!(out, Print(s));
}
pub(crate) fn beep<W: Write>(out: &mut W) {
let _ = out.write_all(b"\x07");
}
pub(crate) fn refresh<W: Write>(out: &mut W) {
let _ = out.flush();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bar(keys: &[&str], functions: &[&str], events: &[i32]) -> FunctionBar {
FunctionBar {
functions: functions.iter().map(|s| s.to_string()).collect(),
keys: keys.iter().map(|s| s.to_string()).collect(),
events: events.to_vec(),
staticData: false,
}
}
#[test]
fn new_static_data_uses_fkeys_and_fevents() {
let b = FunctionBar_new(None, None, None);
assert!(b.staticData);
assert_eq!(b.functions.len(), 10);
assert_eq!(b.functions[0], " "); assert_eq!(b.keys[0], "F1");
assert_eq!(b.keys[9], "F10");
assert_eq!(b.events[0], KEY_F(1));
assert_eq!(b.events[9], KEY_F(10));
}
#[test]
fn new_with_keys_events_is_dynamic() {
let funcs = ["Help", "Quit"];
let keys = ["F1", "F10"];
let events = [1, 2];
let b = FunctionBar_new(Some(&funcs), Some(&keys), Some(&events));
assert!(!b.staticData);
assert_eq!(b.functions, vec!["Help".to_string(), "Quit".to_string()]);
assert_eq!(b.keys, vec!["F1".to_string(), "F10".to_string()]);
assert_eq!(b.events, vec![1, 2]);
}
#[test]
fn new_enter_esc_builds_two_slots() {
let b = FunctionBar_newEnterEsc("Done ", "Cancel");
assert!(!b.staticData);
assert_eq!(
b.functions,
vec!["Done ".to_string(), "Cancel".to_string()]
);
assert_eq!(b.keys, vec!["Enter".to_string(), "Esc".to_string()]);
assert_eq!(b.events, vec![13, 27]);
}
#[test]
fn setlabel_replaces_matching_event() {
let mut b = bar(&["F1", "F2", "F3"], &["a", "b", "c"], &[1, 2, 3]);
FunctionBar_setLabel(&mut b, 2, "xyz");
assert_eq!(
b.functions,
vec!["a".to_string(), "xyz".to_string(), "c".to_string()]
);
}
#[test]
fn setlabel_no_match_is_noop() {
let mut b = bar(&["F1", "F2"], &["a", "b"], &[1, 2]);
FunctionBar_setLabel(&mut b, 99, "xyz");
assert_eq!(b.functions, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn setlabel_stops_at_first_match() {
let mut b = bar(&["F1", "F2"], &["a", "b"], &[5, 5]);
FunctionBar_setLabel(&mut b, 5, "z");
assert_eq!(b.functions, vec!["z".to_string(), "b".to_string()]);
}
#[test]
fn getwidth_sums_key_and_function_bytes() {
let b = bar(&["F1", "F2"], &[" ", "abc"], &[1, 2]);
assert_eq!(FunctionBar_getWidth(&b), 9);
}
#[test]
fn getwidth_empty_is_zero() {
let b = bar(&[], &[], &[]);
assert_eq!(FunctionBar_getWidth(&b), 0);
}
#[test]
fn synthesize_event_boundaries() {
let b = bar(&["F1", "F2"], &["Help", "Quit"], &[10, 20]);
assert_eq!(FunctionBar_synthesizeEvent(&b, 0), 10);
assert_eq!(FunctionBar_synthesizeEvent(&b, 5), 10);
assert_eq!(FunctionBar_synthesizeEvent(&b, 6), 20);
assert_eq!(FunctionBar_synthesizeEvent(&b, 11), 20);
assert_eq!(FunctionBar_synthesizeEvent(&b, 12), ERR);
assert_eq!(FunctionBar_synthesizeEvent(&b, 100), -1);
}
#[test]
fn synthesize_event_negative_pos_hits_first_slot() {
let b = bar(&["F1"], &["Help"], &[10]);
assert_eq!(FunctionBar_synthesizeEvent(&b, -1), 10);
}
#[test]
fn extra_layout_without_buffer_matches_getwidth() {
let b = bar(&["F1", "F2"], &["Help", "Quit"], &[10, 20]);
let (cursor_x, end_x) = b.extra_layout(None);
assert_eq!(cursor_x, FunctionBar_getWidth(&b));
assert_eq!(end_x, 12);
assert_eq!(cursor_x, 12);
}
#[test]
fn extra_layout_with_buffer_advances_by_buffer_len() {
let b = bar(&["F1"], &["Help"], &[10]); let (cursor_x, end_x) = b.extra_layout(Some("query"));
assert_eq!(end_x, 11);
assert_eq!(cursor_x, 11);
}
#[test]
fn to_color_maps_ansi_and_default() {
assert_eq!(Ncurses::to_color(-1), Color::Reset);
assert_eq!(Ncurses::to_color(0), Color::Black);
assert_eq!(Ncurses::to_color(1), Color::DarkRed);
assert_eq!(Ncurses::to_color(7), Color::Grey);
assert_eq!(Ncurses::to_color(8), Color::DarkGrey);
}
}