use std::path::PathBuf;
use clap::{ArgAction, ArgGroup, Args};
use rmux_proto::{SelectPaneDirection, SplitDirection};
use super::{parse_command_args, parse_target_spec, TargetSpec};
pub(super) fn parse_select_pane_args(
arguments: Vec<String>,
) -> Result<SelectPaneArgs, clap::Error> {
parse_command_args::<SelectPaneArgs>("select-pane", arguments)?.validate()
}
pub(super) fn parse_resize_pane_args(
arguments: Vec<String>,
) -> Result<ResizePaneArgs, clap::Error> {
validate_resize_pane_tmux_direction_delta_syntax(&arguments)?;
parse_command_args::<ResizePaneArgs>(
"resize-pane",
normalize_resize_pane_optional_delta(arguments),
)
}
fn validate_resize_pane_tmux_direction_delta_syntax(
arguments: &[String],
) -> Result<(), clap::Error> {
for (index, argument) in arguments.iter().enumerate() {
if matches!(argument.as_str(), "-D" | "-U" | "-L" | "-R") {
if arguments
.get(index + 1)
.is_some_and(|next| next.parse::<u16>().is_ok())
&& index + 2 < arguments.len()
{
return Err(clap::Error::raw(
clap::error::ErrorKind::UnknownArgument,
format!("unexpected argument '{}'", arguments[index + 1]),
));
}
} else if matches!(argument.get(..3), Some("-D=" | "-U=" | "-L=" | "-R=")) {
return Err(clap::Error::raw(
clap::error::ErrorKind::UnknownArgument,
format!("unexpected argument '{argument}'"),
));
}
}
Ok(())
}
fn normalize_resize_pane_optional_delta(arguments: Vec<String>) -> Vec<String> {
let Some(direction_index) = arguments
.iter()
.position(|argument| matches!(argument.as_str(), "-D" | "-U" | "-L" | "-R"))
else {
return arguments;
};
let direction = arguments[direction_index].clone();
if arguments
.iter()
.skip(direction_index + 1)
.any(|argument| matches!(argument.as_str(), "-D" | "-U" | "-L" | "-R"))
{
return arguments;
}
if arguments
.get(direction_index + 1)
.is_some_and(|next| next.parse::<u16>().is_ok())
&& !arguments
.iter()
.skip(direction_index + 2)
.any(|argument| argument.starts_with('-'))
{
let mut normalized = arguments;
let value = normalized.remove(direction_index + 1);
normalized[direction_index] = format!("{direction}={value}");
return normalized;
}
if arguments
.last()
.is_some_and(|last| last.parse::<u16>().is_ok())
&& arguments.len() > direction_index + 1
{
let mut normalized = arguments;
let value = normalized.pop().expect("last resize-pane delta must exist");
normalized[direction_index] = format!("{direction}={value}");
return normalized;
}
arguments
}
#[derive(Debug, Clone, Args)]
#[command(disable_help_flag = true, group(
ArgGroup::new("direction")
.required(false)
.multiple(false)
.args(["horizontal", "vertical"])
))]
pub(crate) struct SplitWindowArgs {
#[arg(short = 'd', action = ArgAction::SetTrue)]
pub(crate) detached: bool,
#[arg(short = 'e')]
pub(crate) environment: Vec<String>,
#[arg(short = 'h', action = ArgAction::SetTrue, group = "direction")]
pub(crate) horizontal: bool,
#[arg(short = 'v', action = ArgAction::SetTrue, group = "direction")]
pub(crate) vertical: bool,
#[arg(short = 'l', allow_hyphen_values = true)]
pub(crate) size: Option<String>,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(crate) command: Vec<String>,
}
#[derive(Debug, Clone, Args)]
#[command(group(
ArgGroup::new("relative")
.required(false)
.multiple(false)
.args(["down", "up"])
))]
pub(crate) struct SwapPaneArgs {
#[arg(short = 'd', action = ArgAction::SetTrue)]
pub(crate) detached: bool,
#[arg(short = 'D', action = ArgAction::SetTrue, group = "relative")]
pub(crate) down: bool,
#[arg(short = 'U', action = ArgAction::SetTrue, group = "relative")]
pub(crate) up: bool,
#[arg(short = 's', value_parser = parse_target_spec)]
pub(crate) source: Option<TargetSpec>,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(short = 'Z', action = ArgAction::SetTrue)]
pub(crate) preserve_zoom: bool,
}
#[derive(Debug, Clone, Args)]
#[command(disable_help_flag = true, group(
ArgGroup::new("direction")
.required(false)
.multiple(false)
.args(["horizontal", "vertical"])
), group(
ArgGroup::new("size_spec")
.required(false)
.multiple(false)
.args(["size", "percentage"])
))]
pub(crate) struct JoinPaneArgs {
#[arg(short = 'b', action = ArgAction::SetTrue)]
pub(crate) before: bool,
#[arg(short = 'd', action = ArgAction::SetTrue)]
pub(crate) detached: bool,
#[arg(short = 'f', action = ArgAction::SetTrue)]
pub(crate) full_size: bool,
#[arg(short = 'h', action = ArgAction::SetTrue, group = "direction")]
pub(crate) horizontal: bool,
#[arg(short = 'l', allow_hyphen_values = true)]
pub(crate) size: Option<String>,
#[arg(short = 'p')]
pub(crate) percentage: Option<u8>,
#[arg(short = 'v', action = ArgAction::SetTrue, group = "direction")]
pub(crate) vertical: bool,
#[arg(short = 's', value_parser = parse_target_spec)]
pub(crate) source: TargetSpec,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
}
#[derive(Debug, Clone, Args)]
#[command(group(
ArgGroup::new("placement")
.required(false)
.multiple(false)
.args(["after", "before"])
))]
pub(crate) struct BreakPaneArgs {
#[arg(short = 'a', action = ArgAction::SetTrue)]
pub(crate) after: bool,
#[arg(short = 'b', action = ArgAction::SetTrue)]
pub(crate) before: bool,
#[arg(short = 'd', action = ArgAction::SetTrue)]
pub(crate) detached: bool,
#[arg(short = 'F')]
pub(crate) format: Option<String>,
#[arg(short = 'P', action = ArgAction::SetTrue)]
pub(crate) print_target: bool,
#[arg(short = 's', value_parser = parse_target_spec)]
pub(crate) source: Option<TargetSpec>,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(short = 'n')]
pub(crate) name: Option<String>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct PipePaneArgs {
#[arg(short = 'I', action = ArgAction::SetTrue)]
pub(crate) stdin: bool,
#[arg(short = 'O', action = ArgAction::SetTrue)]
pub(crate) stdout: bool,
#[arg(short = 'o', action = ArgAction::SetTrue)]
pub(crate) once: bool,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(crate) command: Vec<String>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct RespawnPaneArgs {
#[arg(short = 'c', allow_hyphen_values = true)]
pub(crate) start_directory: Option<PathBuf>,
#[arg(short = 'e')]
pub(crate) environment: Vec<String>,
#[arg(short = 'k', action = ArgAction::SetTrue)]
pub(crate) kill: bool,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(crate) command: Vec<String>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct SelectLayoutArgs {
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
pub(crate) layout: Option<String>,
}
#[derive(Debug, Clone, Args)]
#[command(group(
ArgGroup::new("adjustment")
.required(false)
.multiple(false)
.args(["down", "up", "left", "right", "columns", "rows", "zoom"])
))]
pub(crate) struct ResizePaneArgs {
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(short = 'D', num_args = 0..=1, default_missing_value = "1")]
pub(crate) down: Option<u16>,
#[arg(short = 'U', num_args = 0..=1, default_missing_value = "1")]
pub(crate) up: Option<u16>,
#[arg(short = 'L', num_args = 0..=1, default_missing_value = "1")]
pub(crate) left: Option<u16>,
#[arg(short = 'R', num_args = 0..=1, default_missing_value = "1")]
pub(crate) right: Option<u16>,
#[arg(short = 'x', group = "adjustment")]
pub(crate) columns: Option<u16>,
#[arg(short = 'y', group = "adjustment")]
pub(crate) rows: Option<u16>,
#[arg(short = 'Z', action = ArgAction::SetTrue, group = "adjustment")]
pub(crate) zoom: bool,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct PaneTargetArgs {
#[arg(short = 'a', action = ArgAction::SetTrue)]
pub(crate) kill_all_except: bool,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
}
#[derive(Debug, Clone, Args)]
#[command(group(
ArgGroup::new("marking")
.required(false)
.multiple(false)
.args(["mark", "clear_marked"])
), group(
ArgGroup::new("direction")
.required(false)
.multiple(false)
.args(["up", "down", "left", "right"])
))]
pub(crate) struct SelectPaneArgs {
#[arg(short = 'm', action = ArgAction::SetTrue, group = "marking")]
pub(crate) mark: bool,
#[arg(short = 'M', action = ArgAction::SetTrue, group = "marking")]
pub(crate) clear_marked: bool,
#[arg(short = 'U', action = ArgAction::SetTrue, group = "direction")]
pub(crate) up: bool,
#[arg(short = 'D', action = ArgAction::SetTrue, group = "direction")]
pub(crate) down: bool,
#[arg(short = 'L', action = ArgAction::SetTrue, group = "direction")]
pub(crate) left: bool,
#[arg(short = 'R', action = ArgAction::SetTrue, group = "direction")]
pub(crate) right: bool,
#[arg(short = 'T')]
pub(crate) title: Option<String>,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct CopyModeArgs {
#[arg(short = 'd', action = ArgAction::SetTrue)]
pub(crate) page_down: bool,
#[arg(short = 'e', action = ArgAction::SetTrue)]
pub(crate) exit_on_scroll: bool,
#[arg(short = 'H', action = ArgAction::SetTrue)]
pub(crate) hide_position: bool,
#[arg(short = 'M', action = ArgAction::SetTrue)]
pub(crate) mouse_drag_start: bool,
#[arg(short = 'q', action = ArgAction::SetTrue)]
pub(crate) cancel_mode: bool,
#[arg(short = 'S', action = ArgAction::SetTrue)]
pub(crate) scrollbar_scroll: bool,
#[arg(short = 's', value_parser = parse_target_spec)]
pub(crate) source: Option<TargetSpec>,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(short = 'u', action = ArgAction::SetTrue)]
pub(crate) page_up: bool,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct ClockModeArgs {
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct DisplayPanesArgs {
#[arg(short = 'b', action = ArgAction::SetTrue)]
pub(crate) non_blocking: bool,
#[arg(short = 'd')]
pub(crate) duration_ms: Option<u64>,
#[arg(short = 'N', action = ArgAction::SetTrue)]
pub(crate) no_command: bool,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(crate) template: Vec<String>,
}
#[derive(Debug, Clone, Args)]
pub(crate) struct ListPanesArgs {
#[arg(short = 'a', action = ArgAction::SetTrue)]
pub(crate) all_sessions: bool,
#[arg(short = 's', action = ArgAction::SetTrue)]
pub(crate) short_format: bool,
#[arg(short = 't', value_parser = parse_target_spec)]
pub(crate) target: Option<TargetSpec>,
#[arg(short = 'F')]
pub(crate) format: Option<String>,
}
impl SplitWindowArgs {
pub(crate) fn direction(&self) -> SplitDirection {
if self.horizontal {
SplitDirection::Horizontal
} else {
SplitDirection::Vertical
}
}
}
impl SwapPaneArgs {
pub(crate) fn uses_relative_target(&self) -> bool {
self.down || self.up
}
}
impl JoinPaneArgs {
pub(crate) fn direction(&self) -> SplitDirection {
if self.horizontal {
SplitDirection::Horizontal
} else {
SplitDirection::Vertical
}
}
pub(crate) fn size_spec(&self) -> Option<String> {
self.percentage
.map(|value| format!("{value}%"))
.or_else(|| self.size.clone())
}
}
impl SelectPaneArgs {
fn validate(self) -> Result<Self, clap::Error> {
if self.direction().is_some() && (self.mark || self.clear_marked || self.title.is_some()) {
return Err(clap::Error::raw(
clap::error::ErrorKind::ArgumentConflict,
"select-pane -U/-D/-L/-R cannot be combined with -m, -M, or -T",
));
}
Ok(self)
}
pub(crate) fn direction(&self) -> Option<SelectPaneDirection> {
if self.up {
Some(SelectPaneDirection::Up)
} else if self.down {
Some(SelectPaneDirection::Down)
} else if self.left {
Some(SelectPaneDirection::Left)
} else if self.right {
Some(SelectPaneDirection::Right)
} else {
None
}
}
}
impl DisplayPanesArgs {
pub(crate) fn template_command(&self) -> Option<String> {
if self.template.is_empty() {
None
} else {
Some(self.template.join(" "))
}
}
}