mod iterm2;
pub(crate) mod jxa;
mod kitty;
mod terminal_app;
mod vscode;
mod wezterm;
use crate::FocusMode;
use crate::activate;
use crate::detect::{Terminal, TerminalKind};
pub trait TerminalFocusHandler: Send + Sync {
fn app_name(&self) -> &'static str;
fn try_focus(&self, _tty_device: &str, mode: FocusMode) -> bool {
match mode {
FocusMode::SingleWindow => jxa::raise_front_window(self.app_name()),
FocusMode::ActivateApp => jxa::activate_app(self.app_name()),
}
}
fn try_focus_by_pid(&self, _pid: i32, tty_device: &str, mode: FocusMode) -> bool {
self.try_focus(tty_device, mode)
}
}
struct WaveHandler;
impl TerminalFocusHandler for WaveHandler {
fn app_name(&self) -> &'static str {
"Wave"
}
}
struct TabbyHandler;
impl TerminalFocusHandler for TabbyHandler {
fn app_name(&self) -> &'static str {
"Tabby"
}
}
struct GhosttyHandler;
impl TerminalFocusHandler for GhosttyHandler {
fn app_name(&self) -> &'static str {
"Ghostty"
}
}
struct WarpHandler;
impl TerminalFocusHandler for WarpHandler {
fn app_name(&self) -> &'static str {
"Warp"
}
}
struct ITerm2Handler;
impl TerminalFocusHandler for ITerm2Handler {
fn app_name(&self) -> &'static str {
"iTerm2"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
iterm2::try_focus(tty_device, mode)
}
}
struct TerminalAppHandler;
impl TerminalFocusHandler for TerminalAppHandler {
fn app_name(&self) -> &'static str {
"Terminal"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
terminal_app::try_focus(tty_device, mode)
}
}
struct WezTermHandler;
impl TerminalFocusHandler for WezTermHandler {
fn app_name(&self) -> &'static str {
"WezTerm"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
wezterm::try_focus(tty_device, mode)
}
}
struct KittyHandler;
impl TerminalFocusHandler for KittyHandler {
fn app_name(&self) -> &'static str {
"kitty"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
kitty::try_focus(tty_device, mode)
}
fn try_focus_by_pid(&self, pid: i32, tty_device: &str, mode: FocusMode) -> bool {
kitty::try_focus_by_pid(pid, tty_device, mode)
}
}
struct VSCodeHandler;
impl TerminalFocusHandler for VSCodeHandler {
fn app_name(&self) -> &'static str {
"Code"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
vscode::try_focus(self.app_name(), tty_device, mode)
}
}
struct CursorHandler;
impl TerminalFocusHandler for CursorHandler {
fn app_name(&self) -> &'static str {
"Cursor"
}
fn try_focus(&self, tty_device: &str, mode: FocusMode) -> bool {
vscode::try_focus(self.app_name(), tty_device, mode)
}
}
fn get_handler(kind: TerminalKind) -> Option<&'static dyn TerminalFocusHandler> {
match kind {
TerminalKind::Wave => Some(&WaveHandler),
TerminalKind::Tabby => Some(&TabbyHandler),
TerminalKind::Ghostty => Some(&GhosttyHandler),
TerminalKind::Warp => Some(&WarpHandler),
TerminalKind::ITerm2 => Some(&ITerm2Handler),
TerminalKind::TerminalApp => Some(&TerminalAppHandler),
TerminalKind::WezTerm => Some(&WezTermHandler),
TerminalKind::Kitty => Some(&KittyHandler),
TerminalKind::VSCode => Some(&VSCodeHandler),
TerminalKind::Cursor => Some(&CursorHandler),
_ => None,
}
}
pub fn try_focus(terminal: &Terminal, tty_device: &str, mode: FocusMode) -> Option<bool> {
get_handler(terminal.kind).map(|h| h.try_focus(tty_device, mode))
}
pub fn try_focus_by_pid(
terminal: &Terminal,
pid: i32,
tty_device: &str,
mode: FocusMode,
) -> Option<bool> {
get_handler(terminal.kind).map(|h| h.try_focus_by_pid(pid, tty_device, mode))
}
pub fn focus(terminal: &Terminal, tty_device: &str, mode: FocusMode) -> bool {
if let Some(success) = try_focus(terminal, tty_device, mode) {
return success;
}
activate::window(&terminal.process_name)
}
pub fn focus_by_pid(terminal: &Terminal, pid: i32, tty_device: &str, mode: FocusMode) -> bool {
if let Some(success) = try_focus_by_pid(terminal, pid, tty_device, mode) {
return success;
}
activate::window(&terminal.process_name)
}
#[cfg(test)]
mod tests {
use super::*;
struct TestHandler;
impl TerminalFocusHandler for TestHandler {
fn app_name(&self) -> &'static str {
"NonExistentTestApp12345"
}
}
#[test]
fn test_default_try_focus_single_window() {
let result = TestHandler.try_focus("ttys999999", FocusMode::SingleWindow);
assert!(!result);
}
#[test]
fn test_default_try_focus_activate_app() {
let result = TestHandler.try_focus("ttys999999", FocusMode::ActivateApp);
assert!(!result);
}
#[test]
fn test_default_try_focus_by_pid_delegates() {
let result = TestHandler.try_focus_by_pid(12345, "ttys999999", FocusMode::SingleWindow);
assert!(!result);
}
#[test]
fn test_get_handler_returns_some_for_supported() {
assert!(get_handler(TerminalKind::Wave).is_some());
assert!(get_handler(TerminalKind::Tabby).is_some());
assert!(get_handler(TerminalKind::Ghostty).is_some());
assert!(get_handler(TerminalKind::Warp).is_some());
assert!(get_handler(TerminalKind::ITerm2).is_some());
assert!(get_handler(TerminalKind::TerminalApp).is_some());
assert!(get_handler(TerminalKind::WezTerm).is_some());
assert!(get_handler(TerminalKind::Kitty).is_some());
assert!(get_handler(TerminalKind::VSCode).is_some());
assert!(get_handler(TerminalKind::Cursor).is_some());
}
#[test]
fn test_get_handler_returns_none_for_unsupported() {
assert!(get_handler(TerminalKind::Alacritty).is_none());
assert!(get_handler(TerminalKind::GnomeTerminal).is_none());
assert!(get_handler(TerminalKind::Unknown).is_none());
}
}