rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{SessionStore, TargetFindContext};
use rmux_proto::request::{AttachSessionExt2Request, NewSessionExtRequest};
use rmux_proto::{
    ClientTerminalContext, DisplayPanesRequest, HasSessionRequest, KillSessionRequest,
    LastWindowRequest, LockSessionRequest, NextWindowRequest, PreviousWindowRequest,
    RenameSessionRequest, Request, RmuxError, SwitchClientRequest, TerminalSize,
};

use super::super::DEFAULT_SESSION_SIZE;
use super::tokens::CommandTokens;
use super::values::{missing_argument, parse_u16, unsupported_flag};
use super::{implicit_session_name, parse_session_name};

pub(super) fn parse_new_session(mut args: CommandTokens) -> Result<Request, RmuxError> {
    let mut attach_if_exists = false;
    let mut detach_other_clients = false;
    let mut detached = false;
    let mut environment = Vec::new();
    let mut flags = Vec::new();
    let mut group_target = None;
    let mut kill_other_clients = false;
    let mut print_format = None;
    let mut print_session_info = false;
    let mut session_name = None;
    let mut working_directory = None;
    let mut window_name = None;
    let mut cols = None;
    let mut rows = None;
    let mut command = Vec::new();

    while let Some(token) = args.optional() {
        match token.as_str() {
            "-A" => attach_if_exists = true,
            "-c" => working_directory = Some(args.required("-c start-directory")?),
            "-D" => detach_other_clients = true,
            "-d" => detached = true,
            "-e" => environment.push(args.required("-e name=value")?),
            "-f" => flags.push(args.required("-f flags")?),
            "-F" => print_format = Some(args.required("-F format")?),
            "-n" => window_name = Some(args.required("-n window-name")?),
            "-P" => print_session_info = true,
            "-s" => session_name = Some(parse_session_name(args.required("-s session")?)?),
            "-t" => group_target = Some(parse_session_name(args.required("-t group")?)?),
            "-X" => kill_other_clients = true,
            "-x" => cols = Some(parse_u16("new-session", "-x", &args.required("-x value")?)?),
            "-y" => rows = Some(parse_u16("new-session", "-y", &args.required("-y value")?)?),
            "--" => {
                command.extend(args.remaining());
                break;
            }
            flag if flag.starts_with('-') => return Err(unsupported_flag("new-session", flag)),
            _ => {
                command.push(token);
                command.extend(args.remaining());
                break;
            }
        }
    }

    let size = match (cols, rows) {
        (None, None) => None,
        (cols, rows) => Some(TerminalSize {
            cols: cols.unwrap_or(DEFAULT_SESSION_SIZE.cols),
            rows: rows.unwrap_or(DEFAULT_SESSION_SIZE.rows),
        }),
    };

    if group_target.is_some() && (window_name.is_some() || !command.is_empty()) {
        return Err(RmuxError::Server(
            "command or window name given with target".to_owned(),
        ));
    }

    Ok(Request::NewSessionExt(NewSessionExtRequest {
        detached,
        size,
        environment: (!environment.is_empty()).then_some(environment),
        session_name,
        working_directory,
        group_target,
        attach_if_exists,
        detach_other_clients: detach_other_clients || kill_other_clients,
        kill_other_clients,
        flags: (!flags.is_empty()).then_some(flags),
        window_name,
        print_session_info,
        print_format,
        command: (!command.is_empty()).then_some(command),
    }))
}

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

    while let Some(token) = args.optional() {
        match token.as_str() {
            "-a" if matches!(command, "next-window" | "previous-window") => alerts_only = true,
            "-t" => target = Some(parse_session_name(args.required("-t target")?)?),
            flag if flag.starts_with('-') => return Err(unsupported_flag(command, flag)),
            _ => {
                return Err(RmuxError::Server(format!(
                    "unexpected argument '{token}' for {command}"
                )));
            }
        }
    }

    let target = target.unwrap_or(implicit_session_name(sessions, find_context, command)?);
    match command {
        "has-session" => Ok(Request::HasSession(HasSessionRequest { target })),
        "next-window" => Ok(Request::NextWindow(NextWindowRequest {
            target,
            alerts_only,
        })),
        "previous-window" => Ok(Request::PreviousWindow(PreviousWindowRequest {
            target,
            alerts_only,
        })),
        "last-window" => Ok(Request::LastWindow(LastWindowRequest { target })),
        "display-panes" => Ok(Request::DisplayPanes(DisplayPanesRequest {
            target,
            duration_ms: None,
            non_blocking: false,
            no_command: false,
            template: None,
        })),
        "switch-client" => Ok(Request::SwitchClient(SwitchClientRequest { target })),
        "lock-session" => Ok(Request::LockSession(LockSessionRequest { target })),
        _ => Err(RmuxError::Server(format!(
            "unsupported session request parser command: {command}"
        ))),
    }
}

pub(super) fn parse_kill_session(mut args: CommandTokens) -> Result<Request, RmuxError> {
    let mut target = None;
    let mut kill_all_except_target = false;
    let mut clear_alerts = false;

    while let Some(token) = args.optional() {
        match token.as_str() {
            "-a" => kill_all_except_target = true,
            "-C" => clear_alerts = true,
            "-t" => target = Some(parse_session_name(args.required("-t target")?)?),
            flag if flag.starts_with('-') => return Err(unsupported_flag("kill-session", flag)),
            _ => {
                return Err(RmuxError::Server(format!(
                    "unexpected argument '{token}' for kill-session"
                )));
            }
        }
    }

    Ok(Request::KillSession(KillSessionRequest {
        target: target.ok_or_else(|| missing_argument("kill-session", "-t target"))?,
        kill_all_except_target,
        clear_alerts,
    }))
}

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

    while let Some(token) = args.peek() {
        match token {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-t" => {
                let _ = args.optional();
                target = Some(parse_session_name(args.required("-t target")?)?);
            }
            _ => break,
        }
    }

    let new_name = parse_session_name(args.required("rename-session new-name")?)?;
    args.no_extra("rename-session")?;

    Ok(Request::RenameSession(RenameSessionRequest {
        target: target.unwrap_or(implicit_session_name(
            sessions,
            find_context,
            "rename-session",
        )?),
        new_name,
    }))
}

pub(super) fn parse_attach_session(mut args: CommandTokens) -> Result<Request, RmuxError> {
    let mut target = None;
    let mut target_spec = None;
    let mut working_directory = None;
    let mut detach_other_clients = false;
    let mut kill_other_clients = false;
    let mut read_only = false;
    let mut skip_environment_update = false;
    let mut flags = Vec::new();

    while let Some(token) = args.optional() {
        match token.as_str() {
            "-c" => working_directory = Some(args.required("-c working-directory")?),
            "-d" => detach_other_clients = true,
            "-E" => skip_environment_update = true,
            "-f" => flags.push(args.required("-f flags")?),
            "-r" => read_only = true,
            "-t" => {
                let value = args.required("-t target")?;
                target = Some(parse_session_name(value.clone())?);
                target_spec = Some(value);
            }
            "-x" => kill_other_clients = true,
            flag if flag.starts_with('-') => return Err(unsupported_flag("attach-session", flag)),
            _ => {
                return Err(RmuxError::Server(format!(
                    "unexpected argument '{token}' for attach-session"
                )));
            }
        }
    }

    Ok(Request::AttachSessionExt2(AttachSessionExt2Request {
        target,
        target_spec,
        detach_other_clients: detach_other_clients || kill_other_clients,
        kill_other_clients,
        read_only,
        skip_environment_update,
        flags: (!flags.is_empty()).then_some(flags),
        working_directory,
        client_terminal: ClientTerminalContext::default(),
        client_size: None,
    }))
}