use std::process::Command;
#[derive(Debug, Clone)]
pub struct TmuxConfig {
pub extended_keys: bool,
pub extended_keys_format: String,
pub version: String,
}
impl Default for TmuxConfig {
fn default() -> Self {
Self {
extended_keys: false,
extended_keys_format: String::new(),
version: String::new(),
}
}
}
pub fn detect_tmux() -> Result<Option<TmuxConfig>, TmuxError> {
if std::env::var("TMUX").is_err() {
return Ok(None);
}
let version = run_tmux_command(&["display-message", "-p", "#{version}"])
.map(|s| s.trim().to_string())
.unwrap_or_default();
let extended_keys = run_tmux_command(&["show", "-gv", "extended-keys"])
.map(|s| s.trim().eq_ignore_ascii_case("on"))
.unwrap_or(false);
let extended_keys_format = run_tmux_command(&["show", "-gv", "extended-keys-format"])
.map(|s| s.trim().to_string())
.unwrap_or_default();
Ok(Some(TmuxConfig {
extended_keys,
extended_keys_format,
version,
}))
}
pub fn is_tmux_keyboard_configured() -> bool {
match detect_tmux() {
Ok(Some(config)) => config.extended_keys && config.extended_keys_format.contains("Kr"),
_ => false,
}
}
pub fn show_tmux_warning() -> String {
let in_tmux = std::env::var("TMUX").is_ok();
if in_tmux {
match detect_tmux() {
Ok(Some(config)) => {
if config.extended_keys && config.extended_keys_format.contains("Kr") {
return String::new();
}
}
_ => {}
}
}
if !in_tmux {
return String::new(); }
let fix_instructions = r#"Warning: tmux keyboard mode may not work correctly.
Some key combinations (e.g., Ctrl+Arrow, Alt+keys) may not function properly.
To fix, add the following to ~/.tmux.conf:
set -g extended-keys on
set -g extended-keys-format 'KrOCa1'
Then reload tmux config: Ctrl+b then :source-file ~/.tmux.conf"#;
fix_instructions.to_string()
}
pub fn supports_extended_keys() -> bool {
is_tmux_keyboard_configured()
}
#[derive(Debug, Clone)]
pub struct TmuxInfo {
pub in_tmux: bool,
pub version: Option<String>,
pub extended_keys_enabled: bool,
pub extended_keys_format: Option<String>,
pub keyboard_configured: bool,
}
impl TmuxInfo {
pub fn gather() -> Self {
let in_tmux = std::env::var("TMUX").is_ok();
if !in_tmux {
return Self {
in_tmux: false,
version: None,
extended_keys_enabled: false,
extended_keys_format: None,
keyboard_configured: false,
};
}
match detect_tmux() {
Ok(Some(config)) => {
let keyboard_configured =
config.extended_keys && config.extended_keys_format.contains("Kr");
Self {
in_tmux: true,
version: if config.version.is_empty() {
None
} else {
Some(config.version)
},
extended_keys_enabled: config.extended_keys,
extended_keys_format: if config.extended_keys_format.is_empty() {
None
} else {
Some(config.extended_keys_format)
},
keyboard_configured,
}
}
_ => Self {
in_tmux: true,
version: None,
extended_keys_enabled: false,
extended_keys_format: None,
keyboard_configured: false,
},
}
}
pub fn format(&self) -> String {
if !self.in_tmux {
return "Not running in tmux".to_string();
}
let mut parts = vec![format!(
"tmux {}",
self.version.as_deref().unwrap_or("unknown")
)];
if self.extended_keys_enabled {
parts.push("extended-keys: on".to_string());
} else {
parts.push("extended-keys: off".to_string());
}
if let Some(ref format) = self.extended_keys_format {
parts.push(format!("extended-keys-format: {}", format));
}
if self.keyboard_configured {
parts.push("keyboard: configured".to_string());
} else {
parts.push("keyboard: not configured".to_string());
}
parts.join(" | ")
}
}
#[derive(Debug, Clone)]
pub struct TmuxError {
message: String,
}
impl std::fmt::Display for TmuxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for TmuxError {}
impl TmuxError {
fn new(msg: &str) -> Self {
Self {
message: msg.to_string(),
}
}
}
fn run_tmux_command(args: &[&str]) -> Result<String, TmuxError> {
let output = Command::new("tmux")
.args(args)
.output()
.map_err(|e| TmuxError::new(&format!("Failed to run tmux command: {}", e)))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(TmuxError::new(&format!(
"tmux command failed: {}",
String::from_utf8_lossy(&output.stderr)
)))
}
}
pub fn set_extended_keys(enabled: bool) -> Result<(), TmuxError> {
if !std::env::var("TMUX").is_ok() {
return Err(TmuxError::new("Not running inside tmux"));
}
let value = if enabled { "on" } else { "off" };
run_tmux_command(&["set", "-g", "extended-keys", value])?;
if enabled {
run_tmux_command(&["set", "-g", "extended-keys-format", "KrOCa1"])?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tmux_config_default() {
let config = TmuxConfig::default();
assert!(!config.extended_keys);
assert!(config.extended_keys_format.is_empty());
assert!(config.version.is_empty());
}
#[test]
fn test_tmux_config_with_values() {
let config = TmuxConfig {
extended_keys: true,
extended_keys_format: "KrOCa1".to_string(),
version: "3.3a".to_string(),
};
assert!(config.extended_keys);
assert_eq!(config.extended_keys_format, "KrOCa1");
assert_eq!(config.version, "3.3a");
}
#[test]
fn test_tmux_error_display() {
let err = TmuxError::new("test error");
assert_eq!(err.to_string(), "test error");
}
#[test]
fn test_tmux_info_not_in_tmux() {
let info = TmuxInfo::gather();
assert!(!info.keyboard_configured);
}
#[test]
fn test_tmux_info_format_not_in_tmux() {
let info = TmuxInfo {
in_tmux: false,
version: None,
extended_keys_enabled: false,
extended_keys_format: None,
keyboard_configured: false,
};
assert_eq!(info.format(), "Not running in tmux");
}
#[test]
fn test_tmux_info_format_in_tmux() {
let info = TmuxInfo {
in_tmux: true,
version: Some("3.3a".to_string()),
extended_keys_enabled: true,
extended_keys_format: Some("KrOCa1".to_string()),
keyboard_configured: true,
};
let formatted = info.format();
assert!(formatted.contains("tmux 3.3a"));
assert!(formatted.contains("extended-keys: on"));
assert!(formatted.contains("KrOCa1"));
assert!(formatted.contains("keyboard: configured"));
}
#[test]
fn test_show_tmux_warning_not_in_tmux() {
let warning = show_tmux_warning();
assert!(warning.is_empty() || warning.contains("Warning"));
}
#[test]
fn test_tmux_error_debug() {
let err = TmuxError::new("debug test");
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("debug test"));
}
#[test]
fn test_extended_keys_format_includes_kr() {
assert!("KrOCa1".contains('K'));
assert!("Kr".contains('K'));
assert!(!"ABC".contains('K'));
}
#[test]
fn test_detect_tmux_returns_result() {
let result = detect_tmux();
assert!(result.is_ok());
}
}