use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use regex::Regex;
use crate::config::extended::CaptureRegion;
use crate::{Error, Result};
#[derive(Debug, PartialEq, Eq)]
pub struct Pane {
pub id: PaneId,
pub is_copy_mode: bool,
pub height: i32,
pub scroll_position: i32,
pub is_active: bool,
}
impl FromStr for Pane {
type Err = Error;
fn from_str(src: &str) -> std::result::Result<Self, Self::Err> {
let items: Vec<&str> = src.split(':').collect();
assert_eq!(items.len(), 5, "tmux should have returned 5 items per line");
let mut iter = items.iter();
let id_str = iter.next().unwrap();
let id = PaneId::from_str(id_str)?;
let is_copy_mode = iter.next().unwrap().parse::<bool>()?;
let height = iter.next().unwrap().parse::<i32>()?;
let scroll_position = iter.next().unwrap();
let scroll_position = if scroll_position.is_empty() {
"0"
} else {
scroll_position
};
let scroll_position = scroll_position.parse::<i32>()?;
let is_active = iter.next().unwrap().parse::<bool>()?;
Ok(Pane {
id,
is_copy_mode,
height,
scroll_position,
is_active,
})
}
}
impl Pane {
pub fn capture(&self, region: &CaptureRegion) -> Result<String> {
let mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = self.id);
let region_str = match region {
CaptureRegion::VisibleArea => {
if self.is_copy_mode && self.scroll_position > 0 {
format!(
" -S {start} -E {end}",
start = -self.scroll_position,
end = self.height - self.scroll_position - 1
)
} else {
String::new()
}
}
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
};
args_str.push_str(®ion_str);
let args: Vec<&str> = args_str.split(' ').collect();
let output = duct::cmd("tmux", &args).read()?;
Ok(output)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct PaneId(String);
impl FromStr for PaneId {
type Err = Error;
fn from_str(src: &str) -> std::result::Result<Self, Self::Err> {
if !src.starts_with('%') {
return Err(Error::ExpectedPaneIdMarker);
}
let id = src[1..].parse::<u16>()?;
let id = format!("%{id}");
Ok(PaneId(id))
}
}
impl PaneId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for PaneId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub fn available_panes() -> Result<Vec<Pane>> {
let args = vec![
"list-panes",
"-F",
"#{pane_id}:#{?pane_in_mode,true,false}:#{pane_height}:#{scroll_position}:#{?pane_active,true,false}",
];
let output = duct::cmd("tmux", &args).read()?;
let result: Result<Vec<Pane>> = output
.trim_end() .split('\n')
.map(Pane::from_str) .collect();
result
}
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>> {
let output = duct::cmd!("tmux", "show-options", "-g").read()?;
let lines: Vec<&str> = output.split('\n').collect();
let pattern = format!(r#"({prefix}[\w\-0-9]+) "?([\w-]+)"?"#);
let re = Regex::new(&pattern).unwrap();
let args: HashMap<String, String> = lines
.iter()
.flat_map(|line| match re.captures(line) {
None => None,
Some(captures) => {
let key = captures[1].to_string();
let value = captures[2].to_string();
Some((key, value))
}
})
.collect();
Ok(args)
}
pub fn swap_pane_with(target_pane: &str) -> Result<()> {
duct::cmd!("tmux", "swap-pane", "-Z", "-s", target_pane).run()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_parse_pass() {
let output = ["%52:false:62:3:false", "%53:false:23::true"];
let panes: Result<Vec<Pane>> = output.iter().map(|&line| Pane::from_str(line)).collect();
let panes = panes.expect("Could not parse tmux panes");
let expected = vec![
Pane {
id: PaneId::from_str("%52").unwrap(),
is_copy_mode: false,
height: 62,
scroll_position: 3,
is_active: false,
},
Pane {
id: PaneId(String::from("%53")),
is_copy_mode: false,
height: 23,
scroll_position: 0,
is_active: true,
},
];
assert_eq!(panes, expected);
}
}