#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::ffi::c_int;
use std::io::{self, Write};
use crossterm::queue;
use crossterm::terminal::{Clear, ClearType};
use crate::ported::crt::{ColorElements, ColorScheme, ERR, KEY_F, KEY_RESIZE};
use crate::ported::functionbar::{FunctionBar, FunctionBar_new, FunctionBar_setLabel, Ncurses};
use crate::ported::incset::{
IncSet, IncSet_activate, IncSet_delete, IncSet_drawBar, IncSet_filter, IncSet_getListItemValue,
IncSet_handleKey, IncSet_new, IncType,
};
use crate::ported::listitem::{ListItem, ListItem_append, ListItem_new};
use crate::ported::object::{Object, ObjectClass};
use crate::ported::panel::{
Panel, PanelItem, Panel_delete, Panel_draw, Panel_get, Panel_getCh, Panel_new, Panel_onKey,
Panel_resize, Panel_setHeader, Panel_size,
};
use crate::ported::process::Process;
use crate::ported::vector::{Vector, Vector_add, Vector_delete, Vector_new, Vector_prune};
use crate::ported::xutils::String_contains_i;
const VECTOR_DEFAULT_SIZE: c_int = 10;
const InfoScreenFunctions: [&str; 4] = ["Search ", "Filter ", "Refresh", "Done "];
const InfoScreenKeys: [&str; 4] = ["F3", "F4", "F5", "Esc"];
const InfoScreenEvents: [c_int; 4] = [KEY_F(3), KEY_F(4), KEY_F(5), 27];
pub struct InfoScreen {
pub process: *const Process,
pub display: Panel,
pub inc: IncSet,
pub lines: Vector,
}
impl InfoScreen {
fn empty() -> InfoScreen {
let list_item_class: &'static ObjectClass = ListItem_new("", 0).klass();
InfoScreen {
process: core::ptr::null(),
display: Panel_new(0, 0, 0, 0, None),
inc: IncSet_new(None),
lines: Vector_new(list_item_class, true, VECTOR_DEFAULT_SIZE),
}
}
}
pub trait InfoScreenClass {
fn super_InfoScreen(&mut self) -> &mut InfoScreen;
fn draw(&mut self);
fn scan(&mut self) {}
fn onErr(&mut self) {}
fn onKey(&mut self, ch: c_int) -> bool {
let _ = ch;
false
}
fn has_scan(&self) -> bool {
false
}
fn has_onErr(&self) -> bool {
false
}
fn has_onKey(&self) -> bool {
false
}
}
pub fn InfoScreen_init<'a>(
this: &'a mut InfoScreen,
process: *const Process,
bar: Option<FunctionBar>,
height: c_int,
panelHeader: &str,
) -> &'a mut InfoScreen {
this.process = process;
let bar = bar.unwrap_or_else(|| {
FunctionBar_new(
Some(&InfoScreenFunctions[..]),
Some(&InfoScreenKeys[..]),
Some(&InfoScreenEvents[..]),
)
});
this.display = Panel_new(0, 1, Ncurses::cols(), height, Some(bar.clone()));
this.inc = IncSet_new(Some(bar));
let list_item_class: &'static ObjectClass = ListItem_new("", 0).klass();
this.lines = Vector_new(list_item_class, true, VECTOR_DEFAULT_SIZE);
Panel_setHeader(&mut this.display, panelHeader);
this
}
pub fn InfoScreen_done(this: InfoScreen) {
let InfoScreen {
display,
inc,
lines,
process,
} = this;
Panel_delete(display);
IncSet_delete(inc);
Vector_delete(lines);
let _ = process;
}
pub fn InfoScreen_drawTitled(this: &mut InfoScreen, title: &str) {
let cols = Ncurses::cols();
let len = title.len() as c_int;
let bound = if cols < 0 {
0
} else {
(cols as usize).min(title.len())
};
let mut buf: Vec<u8> = title.as_bytes()[..bound].to_vec();
if len > cols && cols >= 3 {
let start = (cols - 3) as usize;
for b in &mut buf[start..start + 3] {
*b = b'.';
}
}
let mut i = 0usize;
while i < buf.len() {
let c = buf[i];
if c < b' ' || c == 0x7F {
buf[i] = b'?';
} else if c == 0xC2 && i + 1 < buf.len() && (0x80..=0x9F).contains(&buf[i + 1]) {
buf[i] = b'?';
buf[i + 1] = b'?';
i += 1;
}
i += 1;
}
let title = String::from_utf8_lossy(&buf);
{
let mut out = io::stdout().lock();
Ncurses::attrset(
&mut out,
ColorElements::METER_TEXT.packed(ColorScheme::active()),
);
Ncurses::mvhline(&mut out, 0, 0, ' ', cols);
Ncurses::mvaddstr(&mut out, 0, 0, &title);
Ncurses::attrset(
&mut out,
ColorElements::DEFAULT_COLOR.packed(ColorScheme::active()),
);
let _ = out.flush();
}
Panel_draw(&mut this.display, true, true, true, false);
IncSet_drawBar(
&mut this.inc,
&mut this.display,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
}
pub fn InfoScreen_addLine(this: &mut InfoScreen, line: &str) {
Vector_add(&mut this.lines, Box::new(ListItem_new(line, 0)));
let show = match IncSet_filter(&this.inc) {
None => true,
Some(incFilter) => String_contains_i(line, incFilter, true),
};
if show {
let last = this.lines.array.len() - 1;
let ptr: *mut dyn Object = &mut **this.lines.array[last]
.as_mut()
.expect("just-added lines slot is non-NULL");
this.display.items.push(PanelItem::Borrowed(ptr));
this.display.prevSelected = -1;
this.display.needsRedraw = true;
}
}
pub fn InfoScreen_appendLine(this: &mut InfoScreen, line: &str) {
if this.lines.array.is_empty() {
InfoScreen_addLine(this, line);
return;
}
let last_idx = this.lines.array.len() - 1;
{
let obj: &mut dyn Object = &mut **this.lines.array[last_idx]
.as_mut()
.expect("last lines slot is non-NULL");
let any: &mut dyn core::any::Any = obj;
let last_item = any
.downcast_mut::<ListItem>()
.expect("lines items are ListItem");
ListItem_append(last_item, line);
}
let last_ptr: *mut dyn Object = &mut **this.lines.array[last_idx]
.as_mut()
.expect("last lines slot is non-NULL");
let last_id = last_ptr as *const dyn Object as *const ();
let incFilter = IncSet_filter(&this.inc);
let display_size = Panel_size(&this.display);
let displayLast_id: *const () = if display_size != 0 {
Panel_get(&this.display, display_size - 1) as *const dyn Object as *const ()
} else {
core::ptr::null()
};
if let Some(incFilter) = incFilter {
if displayLast_id != last_id && String_contains_i(line, incFilter, true) {
this.display.items.push(PanelItem::Borrowed(last_ptr));
this.display.prevSelected = -1;
this.display.needsRedraw = true;
}
}
}
pub fn InfoScreen_run(this: &mut dyn InfoScreenClass) {
let clear = || {
let mut out = io::stdout().lock();
let _ = queue!(out, Clear(ClearType::All));
let _ = out.flush();
};
if this.has_scan() {
this.scan();
}
this.draw();
let mut looping = true;
while looping {
Panel_draw(
&mut this.super_InfoScreen().display,
false,
true,
true,
false,
);
let screen = this.super_InfoScreen();
IncSet_drawBar(
&mut screen.inc,
&mut screen.display,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
let filtering = this.super_InfoScreen().inc.filtering;
if let Some(bar) = this.super_InfoScreen().display.defaultBar.as_mut() {
FunctionBar_setLabel(bar, KEY_F(4), if filtering { "FILTER " } else { "Filter " });
}
let ch = Panel_getCh(&this.super_InfoScreen().display);
if ch == ERR && this.has_onErr() {
this.onErr();
continue;
}
if this.super_InfoScreen().inc.active.is_some() {
let screen = this.super_InfoScreen();
IncSet_handleKey(
&mut screen.inc,
ch,
&mut screen.display,
IncSet_getListItemValue,
&mut screen.lines,
);
continue;
}
const F3: c_int = KEY_F(3);
const F4: c_int = KEY_F(4);
const F5: c_int = KEY_F(5);
const F10: c_int = KEY_F(10);
const SLASH: c_int = b'/' as c_int;
const BACKSLASH: c_int = b'\\' as c_int;
const CTRL_L: c_int = 0o14; const Q: c_int = b'q' as c_int;
match ch {
ERR => continue,
F3 | SLASH => {
let screen = this.super_InfoScreen();
IncSet_activate(&mut screen.inc, IncType::INC_SEARCH, &mut screen.display);
}
F4 | BACKSLASH => {
let screen = this.super_InfoScreen();
IncSet_activate(&mut screen.inc, IncType::INC_FILTER, &mut screen.display);
}
F5 => {
clear();
if this.has_scan() {
Vector_prune(&mut this.super_InfoScreen().lines);
this.scan();
}
this.draw();
}
CTRL_L => {
clear();
this.draw();
}
27 | Q | F10 => {
looping = false;
}
KEY_RESIZE => {
Panel_resize(
&mut this.super_InfoScreen().display,
Ncurses::cols(),
Ncurses::lines() - 2,
);
if this.has_scan() {
Vector_prune(&mut this.super_InfoScreen().lines);
this.scan();
}
this.draw();
}
_ => {
if this.has_onKey() && this.onKey(ch) {
continue;
}
Panel_onKey(&mut this.super_InfoScreen().display, ch);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::incset::IncSet_setFilter;
use crate::ported::listitem::ListItem;
use crate::ported::panel::{Panel_get, Panel_headerHeight, Panel_size};
use crate::ported::vector::{Vector_get, Vector_size};
fn line_value(lines: &Vector, idx: usize) -> String {
let any: &dyn std::any::Any = Vector_get(lines, idx);
any.downcast_ref::<ListItem>().unwrap().value.clone()
}
fn panel_value(p: &Panel, i: i32) -> String {
let any: &dyn std::any::Any = Panel_get(p, i);
any.downcast_ref::<ListItem>().unwrap().value.clone()
}
fn fresh(height: c_int, header: &str) -> InfoScreen {
let mut this = InfoScreen::empty();
InfoScreen_init(&mut this, core::ptr::null(), None, height, header);
this
}
#[test]
fn init_sets_fields_and_geometry() {
let mut this = InfoScreen::empty();
InfoScreen_init(&mut this, core::ptr::null(), None, 22, "HEADER");
assert!(this.process.is_null());
assert_eq!(Vector_size(&this.lines), 0);
assert_eq!(Panel_size(&this.display), 0);
assert_eq!(this.display.x, 0);
assert_eq!(this.display.y, 1);
assert_eq!(this.display.h, 22);
assert_eq!(this.display.w, Ncurses::cols());
assert_eq!(Panel_headerHeight(&this.display), 1);
assert!(IncSet_filter(&this.inc).is_none());
}
#[test]
fn init_builds_default_bar_when_none() {
let this = fresh(10, " ");
let bar = this.display.defaultBar.as_ref().expect("default bar built");
assert_eq!(bar.functions, InfoScreenFunctions.to_vec());
assert_eq!(bar.keys, InfoScreenKeys.to_vec());
assert_eq!(bar.events, InfoScreenEvents.to_vec());
let inc_bar = this.inc.defaultBar.as_ref().expect("inc default bar");
assert_eq!(inc_bar.functions, InfoScreenFunctions.to_vec());
}
#[test]
fn init_uses_supplied_bar() {
let mut this = InfoScreen::empty();
let custom = FunctionBar_new(Some(&["Only "][..]), Some(&["F1"][..]), Some(&[1][..]));
InfoScreen_init(&mut this, core::ptr::null(), Some(custom), 5, "H");
assert_eq!(
this.display.defaultBar.as_ref().unwrap().functions,
vec!["Only ".to_string()]
);
assert_eq!(
this.inc.defaultBar.as_ref().unwrap().functions,
vec!["Only ".to_string()]
);
}
#[test]
fn add_line_grows_lines_and_panel_without_filter() {
let mut this = fresh(10, "H");
InfoScreen_addLine(&mut this, "alpha");
InfoScreen_addLine(&mut this, "beta");
InfoScreen_addLine(&mut this, "gamma");
assert_eq!(Vector_size(&this.lines), 3);
assert_eq!(line_value(&this.lines, 0), "alpha");
assert_eq!(line_value(&this.lines, 1), "beta");
assert_eq!(line_value(&this.lines, 2), "gamma");
assert_eq!(Panel_size(&this.display), 3);
assert_eq!(panel_value(&this.display, 0), "alpha");
assert_eq!(panel_value(&this.display, 2), "gamma");
}
#[test]
fn add_line_filter_gates_panel_but_not_lines() {
let mut this = fresh(10, "H");
IncSet_setFilter(&mut this.inc, "sh");
assert_eq!(IncSet_filter(&this.inc), Some("sh"));
InfoScreen_addLine(&mut this, "bash"); InfoScreen_addLine(&mut this, "xyz"); InfoScreen_addLine(&mut this, "zsh");
assert_eq!(Vector_size(&this.lines), 3);
assert_eq!(line_value(&this.lines, 1), "xyz");
assert_eq!(Panel_size(&this.display), 2);
assert_eq!(panel_value(&this.display, 0), "bash");
assert_eq!(panel_value(&this.display, 1), "zsh");
}
#[test]
fn add_line_filter_is_case_insensitive() {
let mut this = fresh(10, "H");
IncSet_setFilter(&mut this.inc, "SH"); InfoScreen_addLine(&mut this, "bash"); assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "bash");
}
#[test]
fn add_line_empty_string_is_recorded() {
let mut this = fresh(10, "H");
InfoScreen_addLine(&mut this, "");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "");
assert_eq!(Panel_size(&this.display), 1);
}
#[test]
fn append_line_on_empty_defers_to_add_line() {
let mut this = fresh(10, "H");
InfoScreen_appendLine(&mut this, "hello");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "hello");
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "hello");
}
#[test]
fn append_line_concatenates_onto_last_line() {
let mut this = fresh(10, "H");
InfoScreen_addLine(&mut this, "foo");
InfoScreen_appendLine(&mut this, "bar");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "foobar");
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "foobar");
}
#[test]
fn append_line_filter_match_adds_to_display() {
let mut this = fresh(10, "H");
IncSet_setFilter(&mut this.inc, "sh");
InfoScreen_addLine(&mut this, "xyz");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(Panel_size(&this.display), 0);
InfoScreen_appendLine(&mut this, "bash");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "xyzbash");
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "xyzbash");
}
#[test]
fn append_line_identity_guard_prevents_double_add() {
let mut this = fresh(10, "H");
IncSet_setFilter(&mut this.inc, "sh");
InfoScreen_addLine(&mut this, "bash");
assert_eq!(Panel_size(&this.display), 1);
InfoScreen_appendLine(&mut this, "shzz");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "bashshzz");
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "bashshzz");
}
#[test]
fn append_line_no_filter_never_double_adds() {
let mut this = fresh(10, "H");
InfoScreen_addLine(&mut this, "a");
InfoScreen_appendLine(&mut this, "b");
InfoScreen_appendLine(&mut this, "c");
assert_eq!(Vector_size(&this.lines), 1);
assert_eq!(line_value(&this.lines, 0), "abc");
assert_eq!(Panel_size(&this.display), 1);
assert_eq!(panel_value(&this.display, 0), "abc");
}
struct FullScreen {
base: InfoScreen,
scans: u32,
draws: u32,
errs: u32,
keys: Vec<c_int>,
onkey_ret: bool,
}
impl InfoScreenClass for FullScreen {
fn super_InfoScreen(&mut self) -> &mut InfoScreen {
&mut self.base
}
fn draw(&mut self) {
self.draws += 1;
}
fn scan(&mut self) {
self.scans += 1;
}
fn onErr(&mut self) {
self.errs += 1;
}
fn onKey(&mut self, ch: c_int) -> bool {
self.keys.push(ch);
self.onkey_ret
}
fn has_scan(&self) -> bool {
true
}
fn has_onErr(&self) -> bool {
true
}
fn has_onKey(&self) -> bool {
true
}
}
struct BareScreen {
base: InfoScreen,
draws: u32,
}
impl InfoScreenClass for BareScreen {
fn super_InfoScreen(&mut self) -> &mut InfoScreen {
&mut self.base
}
fn draw(&mut self) {
self.draws += 1;
}
}
#[test]
fn vtable_defaults_model_null_slots() {
let mut s = BareScreen {
base: InfoScreen::empty(),
draws: 0,
};
assert!(!s.has_scan());
assert!(!s.has_onErr());
assert!(!s.has_onKey());
s.scan();
s.onErr();
assert!(!s.onKey(42));
s.draw();
assert_eq!(s.draws, 1);
}
#[test]
fn vtable_overrides_report_present_and_dispatch() {
let mut s = FullScreen {
base: InfoScreen::empty(),
scans: 0,
draws: 0,
errs: 0,
keys: Vec::new(),
onkey_ret: true,
};
assert!(s.has_scan());
assert!(s.has_onErr());
assert!(s.has_onKey());
s.scan();
s.scan();
s.draw();
s.onErr();
assert!(s.onKey(7)); assert_eq!(s.scans, 2);
assert_eq!(s.draws, 1);
assert_eq!(s.errs, 1);
assert_eq!(s.keys, vec![7]);
}
#[test]
fn onkey_return_flows_back_like_c_bool() {
let mut s = FullScreen {
base: InfoScreen::empty(),
scans: 0,
draws: 0,
errs: 0,
keys: Vec::new(),
onkey_ret: false,
};
assert!(!s.onKey(99)); assert_eq!(s.keys, vec![99]);
}
#[test]
fn dyn_dispatch_reaches_concrete_impl_and_super_base() {
let mut s = FullScreen {
base: InfoScreen::empty(),
scans: 0,
draws: 0,
errs: 0,
keys: Vec::new(),
onkey_ret: false,
};
let dynref: &mut dyn InfoScreenClass = &mut s;
if dynref.has_scan() {
dynref.scan();
}
dynref.draw();
InfoScreen_addLine(dynref.super_InfoScreen(), "alpha");
Panel_resize(&mut dynref.super_InfoScreen().display, 123, 45);
assert_eq!(s.scans, 1);
assert_eq!(s.draws, 1);
assert_eq!(Vector_size(&s.base.lines), 1);
assert_eq!(s.base.display.w, 123);
assert_eq!(s.base.display.h, 45);
}
}