pub mod activate;
pub mod detect;
pub mod mux;
pub mod terminals;
pub mod tty;
pub mod util;
pub use detect::{Terminal, TerminalKind};
pub use mux::{Focuser, Multiplexer, MuxFocusResult};
struct DefaultFocuser {
mode: FocusMode,
}
impl Focuser for DefaultFocuser {
fn focus(&self, tty: &str, client_pid: Option<i32>) -> bool {
focus_terminal_by_tty_with_fallback(tty, client_pid, self.mode)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FocusMode {
#[default]
SingleWindow,
ActivateApp,
}
#[derive(Debug, Clone)]
pub enum FocusResult {
Success,
MuxSwitched {
mux: String,
target: String,
},
Unsupported(String),
NoTty,
NotFound,
Error(String),
}
pub fn focus_session(pid: i32, mux_hint: Option<&str>) -> FocusResult {
focus_session_with_mode(pid, mux_hint, FocusMode::default())
}
pub fn focus_session_with_mode(pid: i32, mux_hint: Option<&str>, mode: FocusMode) -> FocusResult {
let parent_map = prock::build_parent_map();
let focuser = DefaultFocuser { mode };
let detected_mux = match mux_hint {
Some("tmux") => Some(Multiplexer::Tmux),
Some("screen") => Some(Multiplexer::Screen),
Some("zellij") => Some(Multiplexer::Zellij),
_ => mux::detect(pid, &parent_map),
};
if let Some(mux) = detected_mux {
let mux_name = match mux {
Multiplexer::Tmux => "tmux",
Multiplexer::Screen => "screen",
Multiplexer::Zellij => "zellij",
};
match mux::focus(mux, pid, &parent_map, &focuser) {
MuxFocusResult::Switched {
session,
window,
pane,
} => {
return FocusResult::MuxSwitched {
mux: mux_name.to_string(),
target: format!("{session}:{window}.{pane}"),
};
}
MuxFocusResult::NoClient { session } => {
return FocusResult::Error(format!(
"No terminal attached to {mux_name} session '{session}'"
));
}
MuxFocusResult::Error(e) => {
return FocusResult::Error(e);
}
MuxFocusResult::NotFound => {
}
}
}
let tty_device = tty::get_device(pid);
if let Some(ref tty) = tty_device
&& focus_terminal_by_tty_and_pid(tty, Some(pid), mode)
{
return FocusResult::Success;
}
if let Some(terminal) = detect::terminal_from_pid(pid) {
let dummy_tty = tty_device.as_deref().unwrap_or("");
if terminals::focus(&terminal, dummy_tty, mode) {
return FocusResult::Success;
}
return FocusResult::NotFound;
}
if tty_device.is_none() {
FocusResult::NoTty
} else {
FocusResult::NotFound
}
}
pub fn focus_by_tty(tty_device: &str) -> bool {
focus_by_tty_with_mode(tty_device, FocusMode::default())
}
pub fn focus_by_tty_with_mode(tty_device: &str, mode: FocusMode) -> bool {
focus_terminal_by_tty(tty_device, mode)
}
fn focus_terminal_by_tty(tty_device: &str, mode: FocusMode) -> bool {
focus_terminal_by_tty_and_pid(tty_device, None, mode)
}
fn focus_terminal_by_tty_and_pid(tty_device: &str, pid: Option<i32>, mode: FocusMode) -> bool {
let Some(terminal) = detect::terminal(tty_device) else {
return false;
};
if let Some(pid) = pid {
terminals::focus_by_pid(&terminal, pid, tty_device, mode)
} else {
terminals::focus(&terminal, tty_device, mode)
}
}
fn focus_terminal_by_tty_with_fallback(
tty_device: &str,
client_pid: Option<i32>,
mode: FocusMode,
) -> bool {
if let Some(terminal) = detect::terminal(tty_device) {
return terminals::focus(&terminal, tty_device, mode);
}
if let Some(pid) = client_pid
&& let Some(terminal) = detect::terminal_from_pid(pid)
{
return terminals::focus(&terminal, tty_device, mode);
}
false
}
#[must_use]
pub fn get_tty_device(pid: i32) -> Option<String> {
tty::get_device(pid)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_focus_session_nonexistent_pid() {
let result = focus_session(999_999_999, None);
assert!(matches!(result, FocusResult::NoTty));
}
#[test]
fn test_focus_by_tty_nonexistent() {
let result = focus_by_tty("ttys999999");
assert!(!result);
}
}