use crate::{
model::common::{Bounds, InputBounds, ScreenBuffer},
pty_manager::PtyManager,
Layout,
};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::{execute, terminal::size};
use regex::Regex;
use std::collections::HashMap;
use std::io::Stdout;
use std::io::{self, Write};
use std::process::Command;
use std::str;
pub fn screen_width() -> usize {
size().unwrap_or((80, 24)).0 as usize
}
pub fn screen_height() -> usize {
size().unwrap_or((80, 24)).1 as usize
}
pub fn screen_bounds() -> Bounds {
Bounds {
x1: 0,
y1: 0,
x2: screen_width().saturating_sub(1), y2: screen_height().saturating_sub(1), }
}
pub fn detached_command(program: &str) -> Command {
use std::os::unix::process::CommandExt;
let mut cmd = Command::new(program);
cmd.stdin(std::process::Stdio::null());
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
cmd
}
pub fn ensure_raw_mode() {
unsafe {
let mut termios: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) != 0 {
return; }
let is_cooked = (termios.c_lflag & (libc::ICANON | libc::ECHO)) != 0;
if is_cooked {
libc::cfmakeraw(&mut termios);
let _ = libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios);
}
}
}
pub fn input_bounds_to_bounds(input_bounds: &InputBounds, parent_bounds: &Bounds) -> Bounds {
let bx1 = parse_percentage(&input_bounds.x1, parent_bounds.width());
let by1 = parse_percentage(&input_bounds.y1, parent_bounds.height());
let bx2 = parse_percentage(&input_bounds.x2, parent_bounds.width());
let by2 = parse_percentage(&input_bounds.y2, parent_bounds.height());
let abs_x1 = parent_bounds.x1 + bx1;
let abs_y1 = parent_bounds.y1 + by1;
let abs_x2 = parent_bounds.x1 + bx2;
let abs_y2 = parent_bounds.y1 + by2;
Bounds {
x1: abs_x1,
y1: abs_y1,
x2: abs_x2,
y2: abs_y2,
}
}
pub fn bounds_to_input_bounds(abs_bounds: &Bounds, parent_bounds: &Bounds) -> InputBounds {
let width = parent_bounds.width();
let height = parent_bounds.height();
let ix1 = if width <= 1 {
0.0
} else {
(abs_bounds.x1 - parent_bounds.x1) as f64 / (width - 1) as f64
};
let iy1 = if height <= 1 {
0.0
} else {
(abs_bounds.y1 - parent_bounds.y1) as f64 / (height - 1) as f64
};
let ix2 = if width <= 1 {
0.0
} else {
(abs_bounds.x2 - parent_bounds.x1) as f64 / (width - 1) as f64
};
let iy2 = if height <= 1 {
0.0
} else {
(abs_bounds.y2 - parent_bounds.y1) as f64 / (height - 1) as f64
};
InputBounds {
x1: format!("{}%", ix1 * 100.0),
y1: format!("{}%", iy1 * 100.0),
x2: format!("{}%", ix2 * 100.0),
y2: format!("{}%", iy2 * 100.0),
}
}
pub fn parse_percentage(value: &str, total: usize) -> usize {
if value.ends_with('%') {
match value.trim_end_matches('%').parse::<f64>() {
Ok(percentage) => {
let normalized = percentage.clamp(0.0, 100.0) / 100.0;
if total == 0 {
0
} else if percentage >= 100.0 {
total - 1 } else {
(normalized * (total - 1) as f64).round() as usize
}
}
Err(_) => 0, }
} else {
value.parse::<usize>().unwrap_or_default()
}
}
pub fn inherit_string_transparent(
child_value: Option<&String>,
parent_value: Option<&String>,
parent_layout_value: Option<&String>,
default_value: Option<&str>,
) -> Option<String> {
if let Some(value) = child_value {
if !value.is_empty() {
return Some(value.clone());
}
}
if let Some(value) = parent_value {
if !value.is_empty() {
return Some(value.clone());
}
}
if let Some(value) = parent_layout_value {
if !value.is_empty() {
return Some(value.clone());
}
}
default_value.map(|s| s.to_string())
}
pub fn inherit_string(
child_value: Option<&String>,
parent_value: Option<&String>,
parent_layout_value: Option<&String>,
default_value: &str,
) -> String {
if let Some(value) = child_value {
if !value.is_empty() {
return value.clone();
}
}
if let Some(value) = parent_value {
if !value.is_empty() {
return value.clone();
}
}
if let Some(value) = parent_layout_value {
if !value.is_empty() {
return value.clone();
}
}
default_value.to_string()
}
pub fn inherit_char(
child_char: Option<&char>,
parent_char: Option<&char>,
parent_layout_char: Option<&char>,
default_char: char,
) -> char {
if let Some(&char) = child_char {
return char;
}
if let Some(&char) = parent_char {
return char;
}
if let Some(&char) = parent_layout_char {
return char;
}
default_char
}
pub fn inherit_bool(
child_bool: Option<&bool>,
parent_bool: Option<&bool>,
parent_layout_bool: Option<&bool>,
default_bool: bool,
) -> bool {
if let Some(bool) = child_bool {
return *bool;
}
if let Some(bool) = parent_bool {
return *bool;
}
if let Some(bool) = parent_layout_bool {
return *bool;
}
default_bool
}
pub fn inherit_u64(
child_value: Option<&u64>,
parent_value: Option<&u64>,
parent_layout_value: Option<&u64>,
default_value: u64,
) -> u64 {
if let Some(value) = child_value {
return *value;
}
if let Some(value) = parent_value {
return *value;
}
if let Some(value) = parent_layout_value {
return *value;
}
default_value
}
pub fn inherit_i64(
child_value: Option<&i64>,
parent_value: Option<&i64>,
parent_layout_value: Option<&i64>,
default_value: i64,
) -> i64 {
if let Some(value) = child_value {
return *value;
}
if let Some(value) = parent_value {
return *value;
}
if let Some(value) = parent_layout_value {
return *value;
}
default_value
}
pub fn inherit_f64(
child_value: Option<&f64>,
parent_value: Option<&f64>,
parent_layout_value: Option<&f64>,
default_value: f64,
) -> f64 {
if let Some(value) = child_value {
return *value;
}
if let Some(value) = parent_value {
return *value;
}
if let Some(value) = parent_layout_value {
return *value;
}
default_value
}
pub fn inherit_optional_string(
child_value: Option<&String>,
parent_value: Option<&String>,
parent_layout_value: Option<&String>,
default_value: Option<String>,
) -> Option<String> {
if let Some(value) = child_value {
if !value.is_empty() {
return Some(value.clone());
}
}
if let Some(value) = parent_value {
if !value.is_empty() {
return Some(value.clone());
}
}
if let Some(value) = parent_layout_value {
if !value.is_empty() {
return Some(value.clone());
}
}
default_value
}
pub fn inherit_optional_char(
child_char: Option<&char>,
parent_char: Option<&char>,
parent_layout_char: Option<&char>,
default_char: Option<char>,
) -> Option<char> {
if let Some(&char) = child_char {
return Some(char);
}
if let Some(&char) = parent_char {
return Some(char);
}
if let Some(&char) = parent_layout_char {
return Some(char);
}
default_char
}
pub fn inherit_optional_bool(
child_bool: Option<&bool>,
parent_bool: Option<&bool>,
parent_layout_bool: Option<&bool>,
default_bool: Option<bool>,
) -> Option<bool> {
if let Some(bool) = child_bool {
return Some(*bool);
}
if let Some(bool) = parent_bool {
return Some(*bool);
}
if let Some(bool) = parent_layout_bool {
return Some(*bool);
}
default_bool
}
pub fn inherit_optional_u64(
child_value: Option<&u64>,
parent_value: Option<&u64>,
parent_layout_value: Option<&u64>,
default_value: Option<u64>,
) -> Option<u64> {
if let Some(value) = child_value {
return Some(*value);
}
if let Some(value) = parent_value {
return Some(*value);
}
if let Some(value) = parent_layout_value {
return Some(*value);
}
default_value
}
pub fn inherit_optional_i64(
child_value: Option<&i64>,
parent_value: Option<&i64>,
parent_layout_value: Option<&i64>,
default_value: Option<i64>,
) -> Option<i64> {
if let Some(value) = child_value {
return Some(*value);
}
if let Some(value) = parent_value {
return Some(*value);
}
if let Some(value) = parent_layout_value {
return Some(*value);
}
default_value
}
pub fn inherit_optional_f64(
child_value: Option<&f64>,
parent_value: Option<&f64>,
parent_layout_value: Option<&f64>,
default_value: Option<f64>,
) -> Option<f64> {
if let Some(value) = child_value {
return Some(*value);
}
if let Some(value) = parent_value {
return Some(*value);
}
if let Some(value) = parent_layout_value {
return Some(*value);
}
default_value
}
pub fn apply_buffer(screen_buffer: &mut ScreenBuffer, stdout: &mut Stdout) {
for y in 0..screen_buffer.height {
for x in 0..screen_buffer.width {
if let Some(cell) = screen_buffer.get(x, y) {
execute!(stdout, crossterm::cursor::MoveTo(x as u16, y as u16)).unwrap();
write!(stdout, "{}{}{}", cell.bg_color, cell.fg_color, cell.ch).unwrap();
}
}
}
stdout.flush().unwrap();
}
pub fn apply_buffer_if_changed(
previous_buffer: &ScreenBuffer,
current_buffer: &ScreenBuffer,
stdout: &mut Stdout,
) {
for y in 0..current_buffer.height {
let mut last_changed_index: Option<u16> = None;
let mut changes = Vec::new();
for x in 0..current_buffer.width {
let current_cell = current_buffer.get(x, y);
let previous_cell = previous_buffer.get(x, y);
if current_cell != previous_cell {
if last_changed_index.is_none() {
last_changed_index = Some(x as u16); }
if let Some(cell) = current_cell {
changes.push(cell.clone()); }
} else {
if let Some(start) = last_changed_index {
execute!(stdout, crossterm::cursor::MoveTo(start, y as u16)).unwrap();
for cell in &changes {
write!(stdout, "{}{}{}", cell.bg_color, cell.fg_color, cell.ch).unwrap();
}
changes.clear();
last_changed_index = None;
}
}
}
if let Some(start) = last_changed_index {
execute!(stdout, crossterm::cursor::MoveTo(start, y as u16)).unwrap();
for cell in changes {
write!(stdout, "{}{}{}", cell.bg_color, cell.fg_color, cell.ch).unwrap();
}
}
}
stdout.flush().unwrap(); }
pub fn find_selected_muxbox_uuid(layout: &Layout) -> Option<String> {
if let Some(children) = &layout.children {
for muxbox in children {
if let Some(selected) = muxbox.selected {
if selected {
return Some(muxbox.id.clone());
}
}
}
}
None
}
pub fn set_terminal_title(title: &str) {
print!("\x1B]0;{}\x07", title);
io::stdout().flush().unwrap();
}
pub fn calculate_tab_order(layout: &Layout) -> Vec<String> {
let mut result: HashMap<String, i32> = HashMap::new();
if let Some(children) = &layout.children {
for muxbox in children {
let tab_order = muxbox.tab_order.clone();
if let Some(order) = tab_order {
result.insert(
muxbox.id.clone(),
order.parse::<i32>().expect("Invalid tab order"),
);
}
}
}
let mut sorted_result: Vec<(String, i32)> = result.into_iter().collect();
sorted_result.sort_by(|a, b| a.1.cmp(&b.1));
let mut tab_order: Vec<String> = Vec::new();
for (key, _) in sorted_result {
tab_order.push(key);
}
tab_order
}
pub fn find_next_muxbox_uuid(layout: &Layout, current_muxbox_uuid: &str) -> Option<String> {
let tab_order = calculate_tab_order(layout);
let mut found_current_muxbox = false;
for muxbox_uuid in tab_order {
if found_current_muxbox {
return Some(muxbox_uuid);
}
if muxbox_uuid == current_muxbox_uuid {
found_current_muxbox = true;
}
}
None
}
pub fn find_previous_muxbox_uuid(layout: &Layout, current_muxbox_uuid: &str) -> Option<String> {
let tab_order = calculate_tab_order(layout);
let mut previous_muxbox_uuid: Option<String> = None;
for muxbox_uuid in tab_order {
if muxbox_uuid == current_muxbox_uuid {
return previous_muxbox_uuid;
}
previous_muxbox_uuid = Some(muxbox_uuid);
}
None
}
pub fn run_script(libs_paths: Option<Vec<String>>, script: &Vec<String>) -> io::Result<String> {
run_script_with_pty(
libs_paths,
script,
&crate::model::common::ExecutionMode::Immediate,
None,
None,
None,
)
}
pub fn run_script_with_pty(
libs_paths: Option<Vec<String>>,
script: &Vec<String>,
execution_mode: &crate::model::common::ExecutionMode,
pty_manager: Option<&PtyManager>,
muxbox_id: Option<String>,
message_sender: Option<(
std::sync::mpsc::Sender<(uuid::Uuid, crate::thread_manager::Message)>,
uuid::Uuid,
)>,
) -> io::Result<String> {
run_script_with_pty_and_redirect(
libs_paths,
script,
execution_mode, pty_manager,
muxbox_id,
message_sender,
None,
)
}
pub fn run_script_with_pty_and_redirect(
libs_paths: Option<Vec<String>>,
script: &Vec<String>,
execution_mode: &crate::model::common::ExecutionMode,
pty_manager: Option<&PtyManager>,
muxbox_id: Option<String>,
message_sender: Option<(
std::sync::mpsc::Sender<(uuid::Uuid, crate::thread_manager::Message)>,
uuid::Uuid,
)>,
redirect_target: Option<String>,
) -> io::Result<String> {
if execution_mode.is_pty() {
if let (Some(pty_mgr), Some(pid), Some((sender, thread_uuid))) =
(pty_manager, muxbox_id, message_sender)
{
if pty_mgr.should_avoid_pty(&pid) {
log::warn!(
"Avoiding PTY for muxbox {} due to recent failures, using regular execution",
pid
);
return run_script_regular(libs_paths, script);
}
match pty_mgr.spawn_pty_script_with_redirect(
pid.clone(),
script,
libs_paths.clone(),
sender,
thread_uuid,
redirect_target,
None, None, ) {
Ok(_) => {
pty_mgr.clear_pty_failures(&pid);
log::info!("PTY started for muxbox: {}", pid);
Ok(String::new())
}
Err(e) => {
log::warn!(
"PTY execution failed for muxbox {}, falling back to regular execution: {}",
pid,
e
);
run_script_regular(libs_paths, script)
}
}
} else {
run_script_regular(libs_paths, script)
}
} else {
run_script_regular(libs_paths, script)
}
}
fn run_script_regular(libs_paths: Option<Vec<String>>, script: &Vec<String>) -> io::Result<String> {
let mut script_content = String::new();
if let Some(paths) = libs_paths {
for lib in paths {
script_content.push_str(&format!("source {}\n", lib));
}
}
for command in script {
script_content.push_str(&format!("{}\n", command));
}
let output = detached_command("bash")
.arg("-c")
.arg(script_content)
.output();
match output {
Ok(output) => {
let mut combined_output = format!(
"{}{}",
str::from_utf8(&output.stdout).unwrap_or(""),
str::from_utf8(&output.stderr).unwrap_or("")
);
combined_output = strip_ansi_codes(&combined_output);
if output.status.success() {
Ok(combined_output)
} else {
let error_message = if combined_output.trim().is_empty() {
format!(
"Script execution failed with exit code: {}",
output.status.code().unwrap_or(-1)
)
} else {
combined_output
};
Err(io::Error::other(error_message))
}
}
Err(e) => Err(io::Error::other(e.to_string())),
}
}
pub fn should_use_pty(muxbox: &crate::model::muxbox::MuxBox) -> bool {
matches!(
muxbox.execution_mode,
crate::model::common::ExecutionMode::Pty
)
}
pub fn should_use_pty_for_choice(choice: &crate::model::choice::Choice) -> bool {
matches!(
choice.execution_mode,
crate::model::common::ExecutionMode::Pty
)
}
pub fn normalize_key_str(key_str: &str) -> String {
key_str.to_lowercase().replace(' ', "")
}
pub fn extract_key_str(event: Event) -> Option<String> {
match event {
Event::Key(KeyEvent {
code, modifiers, ..
}) => match code {
KeyCode::Char(' ') => Some("Space".to_string()),
KeyCode::Enter => Some("Return".to_string()),
KeyCode::Tab => Some("Tab".to_string()),
KeyCode::BackTab => Some("BackTab".to_string()),
KeyCode::Char(c) => {
if modifiers.contains(KeyModifiers::CONTROL) {
Some(format!("Ctrl+{}", c))
} else if modifiers.contains(KeyModifiers::ALT) {
Some(format!("Alt+{}", c))
} else {
Some(c.to_string())
}
}
KeyCode::Left => Some("Left".to_string()),
KeyCode::Right => Some("Right".to_string()),
KeyCode::Up => Some("Up".to_string()),
KeyCode::Down => Some("Down".to_string()),
KeyCode::Backspace => Some("Backspace".to_string()),
KeyCode::Delete => Some("Delete".to_string()),
KeyCode::Esc => Some("Esc".to_string()),
KeyCode::Home => Some("Home".to_string()),
KeyCode::End => Some("End".to_string()),
KeyCode::PageUp => Some("PageUp".to_string()),
KeyCode::PageDown => Some("PageDown".to_string()),
KeyCode::F(n) => Some(format!("F{}", n)),
KeyCode::Insert => Some("Insert".to_string()),
_ => None,
},
_ => None,
}
}
pub fn key_str_to_translate_whitespace(original_str: &str) -> String {
let mut replacements = HashMap::new();
replacements.insert(" ", "Space");
replacements.insert("\n", "Return");
replacements.insert("\t", "Tab");
replacements.insert("\x08", "Backspace");
replacements.insert("\x7f", "Delete");
replacements.insert("\x1b", "Esc");
replacements.insert("\x1b[H", "Home");
replacements.insert("\x1b[F", "End");
replacements.insert("\x1b[5~", "PageUp");
replacements.insert("\x1b[6~", "PageDown");
replacements.insert("\x1b[2~", "Insert");
replacements.insert("\x1bOP", "F1");
replacements.insert("\x1bOQ", "F2");
replacements.insert("\x1bOR", "F3");
replacements.insert("\x1bOS", "F4");
replacements.insert("\x1b[15~", "F5");
replacements.insert("\x1b[17~", "F6");
replacements.insert("\x1b[18~", "F7");
replacements.insert("\x1b[19~", "F8");
replacements.insert("\x1b[20~", "F9");
replacements.insert("\x1b[21~", "F10");
replacements.insert("\x1b[23~", "F11");
replacements.insert("\x1b[24~", "F12");
replacements.insert("\x1b[A", "Up");
replacements.insert("\x1b[B", "Down");
replacements.insert("\x1b[C", "Right");
replacements.insert("\x1b[D", "Left");
if let Some(replacement) = replacements.get(original_str) {
replacement.to_string()
} else {
original_str.to_string()
}
}
pub fn handle_keypress(
key_str: &str,
key_mappings: &HashMap<String, Vec<String>>,
) -> Option<Vec<String>> {
let normalized_key_str = normalize_key_str(&key_str_to_translate_whitespace(key_str));
for (key, actions) in key_mappings.iter() {
if normalize_key_str(key) == normalized_key_str {
return Some(actions.clone());
}
}
None
}
pub fn strip_ansi_codes(input: &str) -> String {
let re = Regex::new(r"\x1B\[[0-?]*[ -/]*[@-~]").unwrap();
re.replace_all(input, "").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::common::{Bounds, InputBounds};
use crate::model::layout::Layout;
use crate::model::muxbox::MuxBox;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use std::collections::HashMap;
#[test]
fn test_run_script_detaches_child_stdin_from_terminal() {
let copied = run_script(None, &vec!["cat".to_string()]).expect("cat should run");
assert_eq!(copied.trim(), "", "child stdin must be empty (detached), not the terminal");
let echoed = run_script(None, &vec!["echo hello".to_string()]).expect("echo should run");
assert_eq!(echoed.trim(), "hello");
let tty = run_script(None, &vec!["test -t 0 && echo TTY || echo NOTTY".to_string()])
.expect("tty check should run");
assert_eq!(tty.trim(), "NOTTY", "child stdin must not be a terminal");
}
fn create_test_bounds(x1: usize, y1: usize, x2: usize, y2: usize) -> Bounds {
Bounds { x1, y1, x2, y2 }
}
fn create_test_input_bounds(x1: &str, y1: &str, x2: &str, y2: &str) -> InputBounds {
InputBounds {
x1: x1.to_string(),
y1: y1.to_string(),
x2: x2.to_string(),
y2: y2.to_string(),
}
}
fn create_test_layout_with_muxboxes(muxboxes: Vec<MuxBox>) -> Layout {
Layout {
id: "test_layout".to_string(),
children: Some(muxboxes),
..Default::default()
}
}
fn create_test_muxbox_with_tab_order(id: &str, tab_order: Option<&str>) -> MuxBox {
MuxBox {
id: id.to_string(),
position: create_test_input_bounds("0%", "0%", "100%", "100%"),
tab_order: tab_order.map(|t| t.to_string()),
..Default::default()
}
}
fn create_test_muxbox_with_selection(id: &str, selected: bool) -> MuxBox {
MuxBox {
id: id.to_string(),
position: create_test_input_bounds("0%", "0%", "100%", "100%"),
selected: Some(selected),
..Default::default()
}
}
#[test]
fn test_screen_bounds() {
let bounds = screen_bounds();
assert_eq!(bounds.x1, 0);
assert_eq!(bounds.y1, 0);
assert!(bounds.x2 > 0);
assert!(bounds.y2 > 0);
}
#[test]
fn test_input_bounds_to_bounds_percentage() {
let parent_bounds = create_test_bounds(10, 20, 110, 120);
let input_bounds = create_test_input_bounds("50%", "25%", "75%", "50%");
let result = input_bounds_to_bounds(&input_bounds, &parent_bounds);
assert_eq!(result.x1, 60); assert_eq!(result.y1, 45); assert_eq!(result.x2, 85); assert_eq!(result.y2, 70); }
#[test]
fn test_input_bounds_to_bounds_absolute() {
let parent_bounds = create_test_bounds(10, 20, 110, 120);
let input_bounds = create_test_input_bounds("5", "10", "15", "25");
let result = input_bounds_to_bounds(&input_bounds, &parent_bounds);
assert_eq!(result.x1, 15); assert_eq!(result.y1, 30); assert_eq!(result.x2, 25); assert_eq!(result.y2, 45); }
#[test]
fn test_bounds_to_input_bounds() {
let parent_bounds = create_test_bounds(0, 0, 100, 100);
let abs_bounds = create_test_bounds(25, 50, 75, 100);
let result = bounds_to_input_bounds(&abs_bounds, &parent_bounds);
assert_eq!(result.x1, "25%");
assert_eq!(result.y1, "50%");
assert_eq!(result.x2, "75%");
assert_eq!(result.y2, "100%");
}
#[test]
fn test_parse_percentage() {
assert_eq!(parse_percentage("50%", 100), 50); assert_eq!(parse_percentage("25%", 200), 50); assert_eq!(parse_percentage("0%", 100), 0);
assert_eq!(parse_percentage("100%", 100), 99); assert_eq!(parse_percentage("33.5%", 100), 33); }
#[test]
fn test_parse_percentage_absolute() {
assert_eq!(parse_percentage("50", 100), 50);
assert_eq!(parse_percentage("0", 100), 0);
assert_eq!(parse_percentage("123", 100), 123);
}
#[test]
fn test_inherit_string_child_priority() {
let child_val = "child".to_string();
let parent_val = "parent".to_string();
let layout_val = "layout".to_string();
let child = Some(&child_val);
let parent = Some(&parent_val);
let layout = Some(&layout_val);
let result = inherit_string(child, parent, layout, "default");
assert_eq!(result, "child");
}
#[test]
fn test_inherit_string_parent_fallback() {
let child_val = "".to_string();
let parent_val = "parent".to_string();
let layout_val = "layout".to_string();
let child = Some(&child_val);
let parent = Some(&parent_val);
let layout = Some(&layout_val);
let result = inherit_string(child, parent, layout, "default");
assert_eq!(result, "parent");
}
#[test]
fn test_inherit_string_default() {
let child = None;
let parent = None;
let layout = None;
let result = inherit_string(child, parent, layout, "default");
assert_eq!(result, "default");
}
#[test]
fn test_inherit_char_child_priority() {
let child = Some(&'A');
let parent = Some(&'B');
let layout = Some(&'C');
let result = inherit_char(child, parent, layout, 'D');
assert_eq!(result, 'A');
}
#[test]
fn test_inherit_char_parent_fallback() {
let child = None;
let parent = Some(&'B');
let layout = Some(&'C');
let result = inherit_char(child, parent, layout, 'D');
assert_eq!(result, 'B');
}
#[test]
fn test_inherit_char_default() {
let child = None;
let parent = None;
let layout = None;
let result = inherit_char(child, parent, layout, 'D');
assert_eq!(result, 'D');
}
#[test]
fn test_inherit_bool_child_priority() {
let child = Some(&true);
let parent = Some(&false);
let layout = Some(&false);
let result = inherit_bool(child, parent, layout, false);
assert_eq!(result, true);
}
#[test]
fn test_inherit_bool_parent_fallback() {
let child = None;
let parent = Some(&true);
let layout = Some(&false);
let result = inherit_bool(child, parent, layout, false);
assert_eq!(result, true);
}
#[test]
fn test_inherit_bool_default() {
let child = None;
let parent = None;
let layout = None;
let result = inherit_bool(child, parent, layout, true);
assert_eq!(result, true);
}
#[test]
fn test_inherit_u64_child_priority() {
let child = Some(&100u64);
let parent = Some(&200u64);
let layout = Some(&300u64);
let result = inherit_u64(child, parent, layout, 400u64);
assert_eq!(result, 100u64);
}
#[test]
fn test_inherit_u64_parent_fallback() {
let child = None;
let parent = Some(&200u64);
let layout = Some(&300u64);
let result = inherit_u64(child, parent, layout, 400u64);
assert_eq!(result, 200u64);
}
#[test]
fn test_inherit_u64_default() {
let child = None;
let parent = None;
let layout = None;
let result = inherit_u64(child, parent, layout, 400u64);
assert_eq!(result, 400u64);
}
#[test]
fn test_inherit_i64_child_priority() {
let child = Some(&-100i64);
let parent = Some(&200i64);
let layout = Some(&-300i64);
let result = inherit_i64(child, parent, layout, 400i64);
assert_eq!(result, -100i64);
}
#[test]
fn test_inherit_f64_child_priority() {
let child = Some(&3.14f64);
let parent = Some(&2.71f64);
let layout = Some(&1.41f64);
let result = inherit_f64(child, parent, layout, 0.0f64);
assert_eq!(result, 3.14f64);
}
#[test]
fn test_inherit_optional_string_child_priority() {
let child_val = "child".to_string();
let parent_val = "parent".to_string();
let layout_val = "layout".to_string();
let child = Some(&child_val);
let parent = Some(&parent_val);
let layout = Some(&layout_val);
let result = inherit_optional_string(child, parent, layout, Some("default".to_string()));
assert_eq!(result, Some("child".to_string()));
}
#[test]
fn test_inherit_optional_string_parent_fallback() {
let child_val = "".to_string();
let parent_val = "parent".to_string();
let layout_val = "layout".to_string();
let child = Some(&child_val);
let parent = Some(&parent_val);
let layout = Some(&layout_val);
let result = inherit_optional_string(child, parent, layout, Some("default".to_string()));
assert_eq!(result, Some("parent".to_string()));
}
#[test]
fn test_inherit_optional_string_none() {
let child = None;
let parent = None;
let layout = None;
let result = inherit_optional_string(child, parent, layout, None);
assert_eq!(result, None);
}
#[test]
fn test_inherit_optional_char_child_priority() {
let child = Some(&'A');
let parent = Some(&'B');
let layout = Some(&'C');
let result = inherit_optional_char(child, parent, layout, Some('D'));
assert_eq!(result, Some('A'));
}
#[test]
fn test_inherit_optional_bool_child_priority() {
let child = Some(&true);
let parent = Some(&false);
let layout = Some(&false);
let result = inherit_optional_bool(child, parent, layout, Some(false));
assert_eq!(result, Some(true));
}
#[test]
fn test_inherit_optional_u64_child_priority() {
let child = Some(&100u64);
let parent = Some(&200u64);
let layout = Some(&300u64);
let result = inherit_optional_u64(child, parent, layout, Some(400u64));
assert_eq!(result, Some(100u64));
}
#[test]
fn test_inherit_optional_i64_child_priority() {
let child = Some(&-100i64);
let parent = Some(&200i64);
let layout = Some(&-300i64);
let result = inherit_optional_i64(child, parent, layout, Some(400i64));
assert_eq!(result, Some(-100i64));
}
#[test]
fn test_inherit_optional_f64_child_priority() {
let child = Some(&3.14f64);
let parent = Some(&2.71f64);
let layout = Some(&1.41f64);
let result = inherit_optional_f64(child, parent, layout, Some(0.0f64));
assert_eq!(result, Some(3.14f64));
}
#[test]
fn test_find_selected_muxbox_uuid() {
let muxbox1 = create_test_muxbox_with_selection("muxbox1", false);
let muxbox2 = create_test_muxbox_with_selection("muxbox2", true);
let muxbox3 = create_test_muxbox_with_selection("muxbox3", false);
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2, muxbox3]);
let result = find_selected_muxbox_uuid(&layout);
assert_eq!(result, Some("muxbox2".to_string()));
}
#[test]
fn test_find_selected_muxbox_uuid_none() {
let muxbox1 = create_test_muxbox_with_selection("muxbox1", false);
let muxbox2 = create_test_muxbox_with_selection("muxbox2", false);
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2]);
let result = find_selected_muxbox_uuid(&layout);
assert_eq!(result, None);
}
#[test]
fn test_find_selected_muxbox_uuid_empty_layout() {
let mut layout = Layout::default();
layout.children = None;
let result = find_selected_muxbox_uuid(&layout);
assert_eq!(result, None);
}
#[test]
fn test_calculate_tab_order() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("3"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("1"));
let muxbox3 = create_test_muxbox_with_tab_order("muxbox3", Some("2"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2, muxbox3]);
let result = calculate_tab_order(&layout);
assert_eq!(result, vec!["muxbox2", "muxbox3", "muxbox1"]);
}
#[test]
fn test_calculate_tab_order_partial() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("2"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", None);
let muxbox3 = create_test_muxbox_with_tab_order("muxbox3", Some("1"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2, muxbox3]);
let result = calculate_tab_order(&layout);
assert_eq!(result, vec!["muxbox3", "muxbox1"]);
}
#[test]
fn test_calculate_tab_order_empty_layout() {
let mut layout = Layout::default();
layout.children = None;
let result = calculate_tab_order(&layout);
assert_eq!(result, Vec::<String>::new());
}
#[test]
fn test_find_next_muxbox_uuid() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let muxbox3 = create_test_muxbox_with_tab_order("muxbox3", Some("3"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2, muxbox3]);
let result = find_next_muxbox_uuid(&layout, "muxbox2");
assert_eq!(result, Some("muxbox3".to_string()));
}
#[test]
fn test_find_next_muxbox_uuid_last() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2]);
let result = find_next_muxbox_uuid(&layout, "muxbox2");
assert_eq!(result, None);
}
#[test]
fn test_find_next_muxbox_uuid_not_found() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2]);
let result = find_next_muxbox_uuid(&layout, "muxbox3");
assert_eq!(result, None);
}
#[test]
fn test_find_previous_muxbox_uuid() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let muxbox3 = create_test_muxbox_with_tab_order("muxbox3", Some("3"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2, muxbox3]);
let result = find_previous_muxbox_uuid(&layout, "muxbox2");
assert_eq!(result, Some("muxbox1".to_string()));
}
#[test]
fn test_find_previous_muxbox_uuid_first() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2]);
let result = find_previous_muxbox_uuid(&layout, "muxbox1");
assert_eq!(result, None);
}
#[test]
fn test_find_previous_muxbox_uuid_not_found() {
let muxbox1 = create_test_muxbox_with_tab_order("muxbox1", Some("1"));
let muxbox2 = create_test_muxbox_with_tab_order("muxbox2", Some("2"));
let layout = create_test_layout_with_muxboxes(vec![muxbox1, muxbox2]);
let result = find_previous_muxbox_uuid(&layout, "muxbox3");
assert_eq!(result, None);
}
#[test]
fn test_run_script_simple() {
let script = vec!["echo 'Hello World'".to_string()];
let result = run_script(None, &script);
assert!(result.is_ok());
assert_eq!(result.unwrap().trim(), "Hello World");
}
#[test]
fn test_run_script_multiple_commands() {
let script = vec!["echo 'First'".to_string(), "echo 'Second'".to_string()];
let result = run_script(None, &script);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("First"));
assert!(output.contains("Second"));
}
#[test]
fn test_run_script_failure() {
let script = vec!["false".to_string()]; let result = run_script(None, &script);
assert!(result.is_err());
}
#[test]
fn test_run_script_with_libs() {
let libs = vec!["/nonexistent/lib1.sh".to_string()];
let script = vec!["echo 'test'".to_string()];
let result = run_script(Some(libs), &script);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_normalize_key_str() {
assert_eq!(normalize_key_str("Ctrl + A"), "ctrl+a");
assert_eq!(normalize_key_str("SHIFT Space"), "shiftspace");
assert_eq!(normalize_key_str("F1"), "f1");
assert_eq!(normalize_key_str("ctrl+c"), "ctrl+c");
}
#[test]
fn test_extract_key_str() {
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Char('a'),
KeyModifiers::NONE
))),
Some("a".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Char(' '),
KeyModifiers::NONE
))),
Some("Space".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Enter,
KeyModifiers::NONE
))),
Some("Return".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE))),
Some("Tab".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Char('c'),
KeyModifiers::CONTROL
))),
Some("Ctrl+c".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Char('x'),
KeyModifiers::ALT
))),
Some("Alt+x".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE))),
Some("Left".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Right,
KeyModifiers::NONE
))),
Some("Right".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE))),
Some("Up".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE))),
Some("Down".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE))),
Some("F1".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::F(12),
KeyModifiers::NONE
))),
Some("F12".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))),
Some("Esc".to_string())
);
assert_eq!(
extract_key_str(Event::Key(KeyEvent::new(
KeyCode::Backspace,
KeyModifiers::NONE
))),
Some("Backspace".to_string())
);
}
#[test]
fn test_key_str_to_translate_whitespace() {
assert_eq!(key_str_to_translate_whitespace(" "), "Space");
assert_eq!(key_str_to_translate_whitespace("\n"), "Return");
assert_eq!(key_str_to_translate_whitespace("\t"), "Tab");
assert_eq!(key_str_to_translate_whitespace("\x1b"), "Esc");
assert_eq!(key_str_to_translate_whitespace("\x1b[A"), "Up");
assert_eq!(key_str_to_translate_whitespace("\x1b[B"), "Down");
assert_eq!(key_str_to_translate_whitespace("\x1b[C"), "Right");
assert_eq!(key_str_to_translate_whitespace("\x1b[D"), "Left");
assert_eq!(key_str_to_translate_whitespace("\x1bOP"), "F1");
assert_eq!(
key_str_to_translate_whitespace("regular text"),
"regular text"
);
}
#[test]
fn test_handle_keypress() {
let mut key_mappings = HashMap::new();
key_mappings.insert("ctrl+c".to_string(), vec!["exit".to_string()]);
key_mappings.insert("space".to_string(), vec!["pause".to_string()]);
key_mappings.insert(
"f1".to_string(),
vec!["help".to_string(), "show".to_string()],
);
let result = handle_keypress("Ctrl+C", &key_mappings);
assert_eq!(result, Some(vec!["exit".to_string()]));
let result = handle_keypress(" ", &key_mappings);
assert_eq!(result, Some(vec!["pause".to_string()]));
let result = handle_keypress("F1", &key_mappings);
assert_eq!(result, Some(vec!["help".to_string(), "show".to_string()]));
let result = handle_keypress("unknown", &key_mappings);
assert_eq!(result, None);
}
#[test]
fn test_handle_keypress_normalized() {
let mut key_mappings = HashMap::new();
key_mappings.insert("Ctrl + C".to_string(), vec!["exit".to_string()]);
let result = handle_keypress("ctrl+c", &key_mappings);
assert_eq!(result, Some(vec!["exit".to_string()]));
let result = handle_keypress("CTRL+C", &key_mappings);
assert_eq!(result, Some(vec!["exit".to_string()]));
}
#[test]
fn test_strip_ansi_codes() {
assert_eq!(strip_ansi_codes("Hello World"), "Hello World");
assert_eq!(
strip_ansi_codes("\x1b[31mHello\x1b[0m World"),
"Hello World"
);
assert_eq!(strip_ansi_codes("\x1b[1;32mGreen\x1b[0m"), "Green");
assert_eq!(strip_ansi_codes("\x1b[2J\x1b[H"), ""); assert_eq!(
strip_ansi_codes("No \x1b[31mANSI\x1b[0m codes"),
"No ANSI codes"
);
}
#[test]
fn test_strip_ansi_codes_complex() {
let input = "\x1b[1;31;40mRed text on black\x1b[0m\x1b[32mGreen\x1b[0m";
let expected = "Red text on blackGreen";
assert_eq!(strip_ansi_codes(input), expected);
}
#[test]
fn test_strip_ansi_codes_empty() {
assert_eq!(strip_ansi_codes(""), "");
assert_eq!(strip_ansi_codes("\x1b[0m"), "");
}
#[test]
fn benchmark_strip_ansi_codes_performance() {
let test_string =
"\x1b[1;31;40mThis is a test string with ANSI codes\x1b[0m\x1b[32mGreen text\x1b[0m";
let start = std::time::Instant::now();
for _ in 0..10000 {
let _ = strip_ansi_codes(test_string);
}
let duration = start.elapsed();
println!("ANSI stripping 10k operations: {:?}", duration);
assert!(
duration.as_secs() < 30,
"ANSI stripping performance regression: {:?}",
duration
);
}
#[test]
fn benchmark_run_script_performance() {
let start = std::time::Instant::now();
for _ in 0..100 {
let _ = run_script(None, &vec!["echo test".to_string()]);
}
let duration = start.elapsed();
println!("Script execution 100 operations: {:?}", duration);
assert!(
duration.as_secs() < 10,
"Script execution performance regression: {:?}",
duration
);
}
#[test]
fn benchmark_handle_keypress_performance() {
let mut key_mappings = std::collections::HashMap::new();
key_mappings.insert("Ctrl + C".to_string(), vec!["exit".to_string()]);
key_mappings.insert("Ctrl + D".to_string(), vec!["quit".to_string()]);
key_mappings.insert("Enter".to_string(), vec!["confirm".to_string()]);
let start = std::time::Instant::now();
for _ in 0..50000 {
let _ = handle_keypress("ctrl+c", &key_mappings);
let _ = handle_keypress("enter", &key_mappings);
let _ = handle_keypress("unknown", &key_mappings);
}
let duration = start.elapsed();
println!("Key mapping 150k operations: {:?}", duration);
assert!(
duration.as_secs() < 30,
"Key mapping performance regression: {:?}",
duration
);
}
#[test]
fn benchmark_bounds_calculation_performance() {
let input_bounds = InputBounds {
x1: "10".to_string(),
y1: "20".to_string(),
x2: "90".to_string(),
y2: "44".to_string(),
};
let parent_bounds = Bounds {
x1: 0,
y1: 0,
x2: 100,
y2: 50,
};
let start = std::time::Instant::now();
for _ in 0..100000 {
let _ = input_bounds_to_bounds(&input_bounds, &parent_bounds);
}
let duration = start.elapsed();
println!("Bounds calculation 100k operations: {:?}", duration);
assert!(
duration.as_millis() < 1300,
"Bounds calculation performance regression: {:?}",
duration
);
}
#[test]
fn benchmark_large_config_parsing() {
let mut muxboxes = Vec::new();
for i in 0..1000 {
muxboxes.push(format!("muxbox_{}", i));
}
let start = std::time::Instant::now();
for _ in 0..1000 {
let _sorted: Vec<_> = muxboxes.iter().collect();
let _filtered: Vec<_> = muxboxes.iter().filter(|p| p.contains("1")).collect();
}
let duration = start.elapsed();
println!("Large config processing 1k operations: {:?}", duration);
assert!(
duration.as_millis() < 4500,
"Large config processing performance regression: {:?}",
duration
);
}
}