//! Detects whether kigi is running inside an editor's embedded `:terminal` //! (Neovim/Vim `:terminal`, Emacs `vterm`). //! //! WHY this matters: inside an editor `:terminal` the *immediate* terminal //! emulator is the editor's own libvterm, not tmux — even though the `TMUX` //! env var is inherited through the editor. A tmux DCS passthrough envelope //! (`\x1bPtmux;…\x1b\\`) is only understood by tmux, so emitting it into the //! editor's libvterm renders the wrapper as visible garbage text. Detection //! records this on [`super::TerminalContext`] so clipboard routing emits a //! plain OSC 52 sequence in that case instead. use std::collections::HashMap; use super::env_get; /// Which embedded editor `:terminal` kigi is running inside. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum EmbeddedEditor { /// Neovim `:terminal` (sets `NVIM`, or legacy `NVIM_LISTEN_ADDRESS`). Neovim, /// Vim 8/9 `:terminal` (sets `VIM_TERMINAL`). Vim, /// Emacs (sets `INSIDE_EMACS`; `vterm` uses libvterm — same bug). Emacs, } /// Detect the embedded editor terminal from an injected environment map. /// /// Checks, in order: `NVIM` / `NVIM_LISTEN_ADDRESS` → [`EmbeddedEditor::Neovim`]; /// `VIM_TERMINAL` → [`EmbeddedEditor::Vim`]; `INSIDE_EMACS` → /// [`EmbeddedEditor::Emacs`]; else `None`. Empty values are treated as absent /// (matching the sibling `detect_*_from_env` detectors via `env_get`). /// /// Including a new env marker here requires extending /// `HOST_TERMINAL_ENV_VARS` in `kigi-pager-pty-harness/src/pty.rs` /// (test-env hygiene). pub fn embedded_editor_from_env(env: &HashMap) -> Option { // Markers can't distinguish editor-inside-tmux (the 100%-repro bug; don't wrap) // from the inverted tmux-inside-editor; we target the former and the latter still // works since plain OSC 52 is forwarded. // NVIM_LISTEN_ADDRESS is legacy (modern nvim unsets it at startup); a // stray/user-exported marker only degrades to plain OSC 52, never garbage. if env_get(env, "NVIM").is_some() || env_get(env, "NVIM_LISTEN_ADDRESS").is_some() { return Some(EmbeddedEditor::Neovim); } if env_get(env, "VIM_TERMINAL").is_some() { return Some(EmbeddedEditor::Vim); } if env_get(env, "INSIDE_EMACS").is_some() { return Some(EmbeddedEditor::Emacs); } None } #[cfg(test)] mod tests { use super::*; use crate::terminal::env_from; #[test] fn nvim_detected_as_neovim() { let env = env_from(&[("NVIM", "/tmp/nvim.12345.0")]); assert_eq!(embedded_editor_from_env(&env), Some(EmbeddedEditor::Neovim)); } #[test] fn nvim_listen_address_detected_as_neovim() { let env = env_from(&[("NVIM_LISTEN_ADDRESS", "/tmp/nvim.sock")]); assert_eq!(embedded_editor_from_env(&env), Some(EmbeddedEditor::Neovim)); } #[test] fn vim_terminal_detected_as_vim() { let env = env_from(&[("VIM_TERMINAL", "8.2")]); assert_eq!(embedded_editor_from_env(&env), Some(EmbeddedEditor::Vim)); } #[test] fn inside_emacs_detected_as_emacs() { let env = env_from(&[("INSIDE_EMACS", "30.1,vterm")]); assert_eq!(embedded_editor_from_env(&env), Some(EmbeddedEditor::Emacs)); } #[test] fn no_editor_markers_is_none() { let env = env_from(&[ ("TERM", "xterm-256color"), ("TMUX", "/tmp/tmux-501/default,1,0"), ]); assert_eq!(embedded_editor_from_env(&env), None); } #[test] fn empty_value_treated_as_absent() { let env = env_from(&[("NVIM", ""), ("VIM_TERMINAL", ""), ("INSIDE_EMACS", "")]); assert_eq!(embedded_editor_from_env(&env), None); } #[test] fn nvim_beats_vim_and_emacs() { let env = env_from(&[ ("NVIM", "/tmp/nvim.12345.0"), ("VIM_TERMINAL", "8.2"), ("INSIDE_EMACS", "30.1,vterm"), ]); assert_eq!(embedded_editor_from_env(&env), Some(EmbeddedEditor::Neovim)); } }