rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{SessionStore, TargetFindContext};
use rmux_proto::{
    DisplayPanesRequest, Request, ResizePaneAdjustment, ResizePaneRequest, RmuxError,
    SelectCustomLayoutRequest, SelectLayoutRequest, SelectLayoutTarget, SelectOldLayoutRequest,
    SpreadLayoutRequest,
};

use super::tokens::CommandTokens;
use super::values::{parse_u16, parse_u64};
use super::{
    implicit_pane_target, implicit_session_name, implicit_window_target,
    is_unsupported_named_layout, parse_layout_name, parse_pane_target, parse_select_layout_target,
    parse_session_name,
};

pub(super) fn parse_display_panes(
    mut args: CommandTokens,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    let mut target = None;
    let mut duration_ms = None;
    let mut non_blocking = false;
    let mut no_command = false;

    while let Some(token) = args.peek() {
        match token {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-b" => {
                let _ = args.optional();
                non_blocking = true;
            }
            "-d" => {
                let _ = args.optional();
                duration_ms = Some(parse_u64(
                    "display-panes",
                    "-d",
                    &args.required("-d duration")?,
                )?);
            }
            "-N" => {
                let _ = args.optional();
                no_command = true;
            }
            "-t" => {
                let _ = args.optional();
                target = Some(parse_session_name(args.required("-t target")?)?);
            }
            _ => break,
        }
    }

    let template = (!args.is_empty()).then(|| args.remaining_joined());

    Ok(Request::DisplayPanes(DisplayPanesRequest {
        target: target.unwrap_or(implicit_session_name(
            sessions,
            find_context,
            "display-panes",
        )?),
        duration_ms,
        non_blocking,
        no_command,
        template,
    }))
}

pub(super) fn parse_select_layout(
    mut args: CommandTokens,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    let mut target = None;
    let mut spread = false;
    let mut old_layout = false;

    while let Some(token) = args.peek() {
        match token {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-E" => {
                let _ = args.optional();
                spread = true;
            }
            "-o" => {
                let _ = args.optional();
                old_layout = true;
            }
            "-t" => {
                let _ = args.optional();
                target = Some(parse_select_layout_target(args.required("-t target")?)?);
            }
            _ => break,
        }
    }

    let target = target.unwrap_or(SelectLayoutTarget::Window(implicit_window_target(
        sessions,
        find_context,
        "select-layout",
    )?));
    if spread && old_layout {
        return Err(RmuxError::Server(
            "select-layout accepts only one of -E or -o".to_owned(),
        ));
    }
    if spread {
        args.no_extra("select-layout")?;
        return Ok(Request::SpreadLayout(SpreadLayoutRequest { target }));
    }
    if old_layout {
        args.no_extra("select-layout")?;
        return Ok(Request::SelectOldLayout(SelectOldLayoutRequest { target }));
    }

    let layout = args.required("select-layout layout")?;
    args.no_extra("select-layout")?;

    match parse_layout_name(&layout) {
        Ok(layout) if is_unsupported_named_layout(layout) => {
            Err(RmuxError::Server(format!("invalid layout: {layout}")))
        }
        Ok(layout) => Ok(Request::SelectLayout(SelectLayoutRequest {
            target,
            layout,
        })),
        Err(_) => Ok(Request::SelectCustomLayout(SelectCustomLayoutRequest {
            target,
            layout,
        })),
    }
}

pub(super) fn parse_resize_pane(
    mut args: CommandTokens,
    sessions: &SessionStore,
    find_context: &TargetFindContext,
) -> Result<Request, RmuxError> {
    let mut target = None;
    let mut adjustment = None;

    while let Some(token) = args.peek() {
        match token {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-t" => {
                let _ = args.optional();
                target = Some(parse_pane_target(
                    "resize-pane",
                    args.required("-t target")?,
                )?);
            }
            "-x" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::AbsoluteWidth {
                    columns: parse_u16("resize-pane", "-x", &args.required("-x value")?)?,
                });
            }
            "-y" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::AbsoluteHeight {
                    rows: parse_u16("resize-pane", "-y", &args.required("-y value")?)?,
                });
            }
            "-U" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::Up {
                    cells: parse_resize_pane_delta(&mut args, "-U")?,
                });
            }
            "-D" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::Down {
                    cells: parse_resize_pane_delta(&mut args, "-D")?,
                });
            }
            "-L" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::Left {
                    cells: parse_resize_pane_delta(&mut args, "-L")?,
                });
            }
            "-R" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::Right {
                    cells: parse_resize_pane_delta(&mut args, "-R")?,
                });
            }
            "-Z" => {
                let _ = args.optional();
                if adjustment.is_some() {
                    return Err(RmuxError::Server(
                        "resize-pane accepts only one adjustment flag".to_owned(),
                    ));
                }
                adjustment = Some(ResizePaneAdjustment::Zoom);
            }
            _ => break,
        }
    }
    args.no_extra("resize-pane")?;

    Ok(Request::ResizePane(ResizePaneRequest {
        target: target.unwrap_or(implicit_pane_target(sessions, find_context, "resize-pane")?),
        adjustment: adjustment.unwrap_or(ResizePaneAdjustment::NoOp),
    }))
}

fn parse_resize_pane_delta(args: &mut CommandTokens, flag: &str) -> Result<u16, RmuxError> {
    match args.peek() {
        Some(next) if !next.starts_with('-') || next == "-" => parse_u16(
            "resize-pane",
            flag,
            &args.required(&format!("{flag} value"))?,
        ),
        _ => Ok(1),
    }
}