rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{SessionStore, TargetFindContext};
use rmux_proto::{
    JoinPaneRequest, MovePaneRequest, PaneSplitSize, Request, RmuxError, SplitDirection,
};

use super::super::tokens::{parse_compact_flag_cluster, CommandTokens, CompactFlag};
use super::super::values::{parse_percentage, parse_u32, unsupported_flag};
use super::super::{implicit_pane_target, marked_pane_target_or_current, parse_pane_target};

pub(in crate::handler::scripting_support) fn parse_join_pane(
    mut args: CommandTokens,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    parse_join_or_move_pane(&mut args, "join-pane", false, sessions, find_context)
}

pub(in crate::handler::scripting_support) fn parse_move_pane(
    mut args: CommandTokens,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    parse_join_or_move_pane(&mut args, "move-pane", true, sessions, find_context)
}

fn parse_join_or_move_pane(
    args: &mut CommandTokens,
    command: &str,
    as_move: bool,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    let mut detached = false;
    let mut before = false;
    let mut full_size = false;
    let mut direction = SplitDirection::Vertical;
    let mut size = None;
    let mut percentage_size = None;
    let mut source = None;
    let mut target = None;

    while let Some(token) = args.peek() {
        match token {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-b" => {
                let _ = args.optional();
                before = true;
            }
            "-d" => {
                let _ = args.optional();
                detached = true;
            }
            "-f" => {
                let _ = args.optional();
                full_size = true;
            }
            "-h" => {
                let _ = args.optional();
                direction = SplitDirection::Horizontal;
            }
            "-l" => {
                let _ = args.optional();
                if size.is_some() {
                    return Err(RmuxError::Server(format!("{command} accepts only one -l")));
                }
                size = Some(parse_pane_split_size(
                    command,
                    "-l",
                    &args.required("-l size")?,
                )?);
            }
            "-p" => {
                let _ = args.optional();
                let percentage = args.required("-p size")?;
                if size.is_none() {
                    percentage_size = Some(percentage);
                }
            }
            token if legacy_percentage_attached_value(token).is_some() => {
                let percentage = legacy_percentage_attached_value(token)
                    .expect("checked above")
                    .to_owned();
                let _ = args.optional();
                if size.is_none() {
                    percentage_size = Some(percentage);
                }
            }
            "-v" => {
                let _ = args.optional();
                if direction != SplitDirection::Horizontal {
                    direction = SplitDirection::Vertical;
                }
            }
            "-s" => {
                let _ = args.optional();
                source = Some(parse_pane_target(command, args.required("-s target")?)?);
            }
            "-t" => {
                let _ = args.optional();
                target = Some(parse_pane_target(command, args.required("-t target")?)?);
            }
            token => {
                let Some(cluster) = parse_compact_flag_cluster(command, token, "bdfhv", "lpst")?
                else {
                    break;
                };
                let _ = args.optional();
                for flag in cluster {
                    match flag {
                        CompactFlag::Bare('b') => before = true,
                        CompactFlag::Bare('d') => detached = true,
                        CompactFlag::Bare('f') => full_size = true,
                        CompactFlag::Bare('h') => {
                            direction = SplitDirection::Horizontal;
                        }
                        CompactFlag::Bare('v') => {
                            if direction != SplitDirection::Horizontal {
                                direction = SplitDirection::Vertical;
                            }
                        }
                        compact_flag @ CompactFlag::Value { flag: 'l', .. } => {
                            if size.is_some() {
                                return Err(RmuxError::Server(format!(
                                    "{command} accepts only one -l"
                                )));
                            }
                            size = Some(parse_pane_split_size(
                                command,
                                "-l",
                                &compact_flag.value_or_next(args, "-l size")?,
                            )?);
                        }
                        compact_flag @ CompactFlag::Value { flag: 'p', .. } => {
                            let percentage = compact_flag.value_or_next(args, "-p size")?;
                            if size.is_none() {
                                percentage_size = Some(percentage);
                            }
                        }
                        compact_flag @ CompactFlag::Value { flag: 's', .. } => {
                            source = Some(parse_pane_target(
                                command,
                                compact_flag.value_or_next(args, "-s target")?,
                            )?);
                        }
                        compact_flag @ CompactFlag::Value { flag: 't', .. } => {
                            target = Some(parse_pane_target(
                                command,
                                compact_flag.value_or_next(args, "-t target")?,
                            )?);
                        }
                        CompactFlag::Bare(flag) | CompactFlag::Value { flag, .. } => {
                            return Err(unsupported_flag(command, &format!("-{flag}")));
                        }
                    }
                }
            }
        }
    }
    args.no_extra(command)?;
    if size.is_none() {
        if let Some(percentage) = percentage_size {
            size = Some(PaneSplitSize::Percentage(parse_percentage(
                command,
                "-p",
                &percentage,
            )?));
        }
    }

    let source = match source {
        Some(source) => source,
        None => marked_pane_target_or_current(sessions, find_context, command)?,
    };

    let request = JoinPaneRequest {
        source,
        target: target.unwrap_or(implicit_pane_target(sessions, find_context, command)?),
        direction,
        detached,
        before,
        full_size,
        size,
    };

    if as_move {
        Ok(Request::MovePane(MovePaneRequest {
            source: request.source,
            target: request.target,
            direction: request.direction,
            detached: request.detached,
            before: request.before,
            full_size: request.full_size,
            size: request.size,
        }))
    } else {
        Ok(Request::JoinPane(request))
    }
}

fn legacy_percentage_attached_value(token: &str) -> Option<&str> {
    token
        .strip_prefix("-p")
        .filter(|value| !value.is_empty() && !token.starts_with("--"))
}

fn parse_pane_split_size(
    command: &str,
    flag: &str,
    value: &str,
) -> Result<PaneSplitSize, RmuxError> {
    if let Some(percentage) = value.strip_suffix('%') {
        return Ok(PaneSplitSize::Percentage(parse_percentage(
            command, flag, percentage,
        )?));
    }

    Ok(PaneSplitSize::Absolute(parse_u32(command, flag, value)?))
}