#[derive(Clone, PartialEq, Eq, Debug)]
pub struct EditorLaunch {
pub argv: Vec<String>,
pub inline: bool,
}
pub fn editor_launch(file: &str, env: impl Fn(&str) -> Option<String>) -> EditorLaunch {
let editor = resolve_editor(&env);
if env("ZELLIJ").is_some() {
EditorLaunch {
argv: vec![
"zellij".into(),
"action".into(),
"new-pane".into(),
"--close-on-exit".into(),
"--".into(),
editor,
file.to_string(),
],
inline: false,
}
} else if env("TMUX").is_some() {
EditorLaunch {
argv: vec![
"tmux".into(),
"split-window".into(),
editor,
file.to_string(),
],
inline: false,
}
} else if env("KITTY_WINDOW_ID").is_some() {
EditorLaunch {
argv: vec![
"kitty".into(),
"@".into(),
"launch".into(),
"--type=window".into(),
editor,
file.to_string(),
],
inline: false,
}
} else {
EditorLaunch {
argv: vec![editor, file.to_string()],
inline: true,
}
}
}
fn resolve_editor(env: &impl Fn(&str) -> Option<String>) -> String {
env("VISUAL")
.or_else(|| env("EDITOR"))
.unwrap_or_else(|| "vi".to_string())
}
pub fn inline_editor(file: &str, env: impl Fn(&str) -> Option<String>) -> Vec<String> {
vec![resolve_editor(&env), file.to_string()]
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn env_of(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
move |k: &str| map.get(k).cloned()
}
#[test]
fn plain_terminal_runs_editor_inline() {
let launch = editor_launch("/tmp/x.json", env_of(&[("EDITOR", "nvim")]));
assert_eq!(launch.argv, vec!["nvim", "/tmp/x.json"]);
assert!(launch.inline);
}
#[test]
fn visual_wins_over_editor_and_default() {
let launch = editor_launch("/tmp/x", env_of(&[("VISUAL", "code"), ("EDITOR", "vi")]));
assert_eq!(launch.argv[0], "code");
let bare = editor_launch("/tmp/x", env_of(&[]));
assert_eq!(bare.argv[0], "vi");
}
#[test]
fn zellij_opens_a_new_pane_not_inline() {
let launch = editor_launch("/tmp/x", env_of(&[("ZELLIJ", "0"), ("EDITOR", "hx")]));
assert!(!launch.inline);
assert_eq!(launch.argv[0], "zellij");
assert!(launch.argv.contains(&"new-pane".to_string()));
assert_eq!(launch.argv.last().unwrap(), "/tmp/x");
}
#[test]
fn tmux_preferred_after_zellij() {
let both = editor_launch("/f", env_of(&[("ZELLIJ", "0"), ("TMUX", "/tmp/t")]));
assert_eq!(both.argv[0], "zellij");
let launch = editor_launch("/f", env_of(&[("TMUX", "/tmp/t"), ("EDITOR", "vim")]));
assert_eq!(launch.argv, vec!["tmux", "split-window", "vim", "/f"]);
}
#[test]
fn kitty_launches_a_window() {
let launch = editor_launch("/f", env_of(&[("KITTY_WINDOW_ID", "3"), ("EDITOR", "vim")]));
assert_eq!(launch.argv[0], "kitty");
assert!(launch.argv.contains(&"launch".to_string()));
assert!(!launch.inline);
}
#[test]
fn inline_editor_ignores_multiplexers() {
let argv = inline_editor(
"/tmp/sp-edit.json",
env_of(&[("ZELLIJ", "0"), ("TMUX", "/tmp/t"), ("EDITOR", "hx")]),
);
assert_eq!(argv, vec!["hx", "/tmp/sp-edit.json"]);
let bare = inline_editor("/f", env_of(&[]));
assert_eq!(bare, vec!["vi", "/f"]);
}
}