focal 0.2.8

Terminal focus library - focus terminal windows and multiplexer panes
Documentation
//! Shared utilities for the focal crate.

use std::process::{Command, Output};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

/// Default timeout for external commands (5 seconds).
///
/// This prevents indefinite blocking if a command hangs.
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);

/// Run a command with timeout protection.
///
/// Spawns a thread to run the command and waits with a timeout to prevent
/// indefinite blocking if the command is unresponsive.
///
/// # Arguments
/// * `cmd` - The command name (e.g., "tmux", "zellij")
/// * `args` - Command arguments
/// * `timeout` - Maximum time to wait for the command
///
/// # Returns
/// The command output if it completed within the timeout, or `None` if it timed out.
pub fn run_command_with_timeout(cmd: &str, args: &[&str], timeout: Duration) -> Option<Output> {
    let cmd_owned = cmd.to_string();
    let args_owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let output = Command::new(&cmd_owned).args(&args_owned).output();
        let _ = tx.send(output);
    });

    match rx.recv_timeout(timeout) {
        Ok(Ok(output)) => Some(output),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_run_command_with_timeout_success() {
        // Run a simple command that should succeed
        let result = run_command_with_timeout("echo", &["hello"], DEFAULT_TIMEOUT);
        assert!(result.is_some());
        let output = result.unwrap();
        assert!(output.status.success());
    }

    #[test]
    fn test_run_command_with_timeout_nonexistent() {
        // Run a command that doesn't exist - should return None or error
        let result =
            run_command_with_timeout("nonexistent_command_12345", &[], Duration::from_millis(100));
        // Either None (timeout/error) or Some with failed status
        if let Some(output) = result {
            assert!(!output.status.success());
        }
    }
}