focal 0.2.8

Terminal focus library - focus terminal windows and multiplexer panes
Documentation
//! VS Code and Cursor IDE focus handler.
//!
//! Provides window-level focus for VS Code-based IDEs with integrated terminals.
//!
//! # Supported Applications
//!
//! - **Visual Studio Code** (process name: "Code", "code", "Code - Insiders")
//! - **Cursor** (process name: "Cursor", "cursor")
//!
//! # Limitations
//!
//! VS Code and Cursor do not expose a CLI or IPC API for enumerating terminal
//! panes/splits or focusing specific ones. This handler provides window-level
//! focus only:
//!
//! - **Terminal-level**: Cannot focus a specific terminal tab or split
//! - **Multi-window**: With multiple windows, `SingleWindow` mode will raise
//!   the frontmost window, which may not be the one containing the target
//!   terminal. This is still preferable to `ActivateApp` which raises ALL windows.
//!
//! # Focus Modes
//!
//! - **SingleWindow**: Uses System Events AXRaise to bring only the specific
//!   window to the front. Requires accessibility permissions on macOS.
//!
//! - **ActivateApp**: Uses traditional app activation which brings ALL
//!   windows of the IDE to the front. No special permissions required.

use crate::FocusMode;

/// Try to focus a VS Code window.
///
/// Note: Terminal-level focus is not available due to VS Code lacking
/// a terminal enumeration API. This function focuses the VS Code window.
///
/// # Arguments
/// * `app_name` - The app name to activate ("Code" for VS Code, "Cursor" for Cursor)
/// * `_tty_device` - TTY device name (currently unused, reserved for future terminal support)
/// * `mode` - The focus mode to use
///
/// # Returns
/// `true` if focusing succeeded, `false` otherwise.
pub fn try_focus(app_name: &str, _tty_device: &str, mode: FocusMode) -> bool {
    bring_to_front(app_name, mode)
}

/// Bring the IDE window to front using the specified mode.
fn bring_to_front(app_name: &str, mode: FocusMode) -> bool {
    // First try the forceful `open -a` approach which works reliably from any terminal
    if bring_to_front_open_command(app_name) {
        return true;
    }

    // Fallback to JXA methods
    match mode {
        FocusMode::SingleWindow => bring_to_front_single_window(app_name),
        FocusMode::ActivateApp => bring_to_front_activate_app(app_name),
    }
}

/// Bring the IDE to front using `open -a` command.
///
/// This is the most reliable method as it works from any terminal including
/// iTerm2 which has aggressive focus management.
#[cfg(target_os = "macos")]
fn bring_to_front_open_command(app_name: &str) -> bool {
    std::process::Command::new("open")
        .args(["-a", app_name])
        .status()
        .is_ok_and(|s| s.success())
}

#[cfg(not(target_os = "macos"))]
fn bring_to_front_open_command(_app_name: &str) -> bool {
    false
}

/// Bring the IDE to front using app activation (brings ALL windows to front).
fn bring_to_front_activate_app(app_name: &str) -> bool {
    super::jxa::activate_app(app_name)
}

/// Bring only the frontmost IDE window to front using AXRaise.
fn bring_to_front_single_window(app_name: &str) -> bool {
    super::jxa::raise_front_window(app_name)
}

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

    // NOTE: The following tests are ignored because they have side effects -
    // they will steal window focus when VS Code or Cursor is running.
    // Run manually with: cargo test --package focal -- --ignored

    #[test]
    #[ignore = "steals window focus when VS Code is running"]
    fn test_try_focus_vscode_single_window() {
        // This will fail gracefully when VS Code is not running
        let result = try_focus("Code", "ttys999999", FocusMode::SingleWindow);
        // We just verify it doesn't panic - actual result depends on whether
        // VS Code is running
        let _ = result;
    }

    #[test]
    #[ignore = "steals window focus when Cursor is running"]
    fn test_try_focus_cursor_single_window() {
        // This will fail gracefully when Cursor is not running
        let result = try_focus("Cursor", "ttys999999", FocusMode::SingleWindow);
        let _ = result;
    }

    #[test]
    #[ignore = "steals window focus when VS Code is running"]
    fn test_try_focus_activate_app() {
        let result = try_focus("Code", "ttys999999", FocusMode::ActivateApp);
        let _ = result;
    }
}