rmux 0.1.2

A local terminal multiplexer with a tmux-style CLI, daemon runtime, Rust SDK, and ratatui integration.
use rmux_core::command_parser::lookup_command;

use super::{write_lines_output, ExitFailure};
use crate::cli_args::{documented_cli_aliases, implemented_command_surface, ListCommandsArgs};

const LIST_COMMAND_SIGNATURES: &[(&str, &str)] = &[
    (
        "attach-session",
        "(attach) [-dErx] [-c working-directory] [-f flags] [-t target-session]",
    ),
    (
        "bind-key",
        "(bind) [-nr] [-T key-table] [-N note] key [command [argument ...]]",
    ),
    (
        "break-pane",
        "(breakp) [-abdP] [-F format] [-n window-name] [-s src-pane] [-t dst-window]",
    ),
    (
        "capture-pane",
        "(capturep) [-aCeJMNpPqT] [-b buffer-name] [-E end-line] [-S start-line] [-t target-pane]",
    ),
    (
        "choose-buffer",
        "[-NrZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]",
    ),
    (
        "choose-client",
        "[-NrZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]",
    ),
    (
        "choose-tree",
        "[-GNrswZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]",
    ),
    ("clear-history", "(clearhist) [-H] [-t target-pane]"),
    ("clear-prompt-history", "(clearphist) [-T prompt-type]"),
    ("clock-mode", "[-t target-pane]"),
    (
        "command-prompt",
        "[-1beFiklN] [-I inputs] [-p prompts] [-t target-client] [-T prompt-type] [template]",
    ),
    (
        "confirm-before",
        "(confirm) [-by] [-c confirm-key] [-p prompt] [-t target-client] command",
    ),
    ("copy-mode", "[-deHMqSu] [-s src-pane] [-t target-pane]"),
    ("customize-mode", "[-NZ] [-F format] [-f filter] [-t target-pane]"),
    ("delete-buffer", "(deleteb) [-b buffer-name]"),
    (
        "detach-client",
        "(detach) [-aP] [-E shell-command] [-s target-session] [-t target-client]",
    ),
    (
        "display-menu",
        "(menu) [-MO] [-b border-lines] [-c target-client] [-C starting-choice] [-H selected-style] [-s style] [-S border-style] [-t target-pane] [-T title] [-x position] [-y position] name [key] [command] ...",
    ),
    (
        "display-message",
        "(display) [-aCIlNpv] [-c target-client] [-d delay] [-F format] [-t target-pane] [message]",
    ),
    (
        "display-popup",
        "(popup) [-BCEkN] [-b border-lines] [-c target-client] [-d start-directory] [-e environment] [-h height] [-s style] [-S border-style] [-t target-pane] [-T title] [-w width] [-x position] [-y position] [shell-command [argument ...]]",
    ),
    (
        "display-panes",
        "(displayp) [-bN] [-d duration] [-t target-client] [template]",
    ),
    (
        "find-window",
        "(findw) [-CiNrTZ] [-t target-pane] match-string",
    ),
    ("has-session", "(has) [-t target-session]"),
    (
        "if-shell",
        "(if) [-bF] [-t target-pane] shell-command command [command]",
    ),
    (
        "join-pane",
        "(joinp) [-bdfhv] [-l size] [-s src-pane] [-t dst-pane]",
    ),
    ("kill-pane", "(killp) [-a] [-t target-pane]"),
    ("kill-server", ""),
    ("kill-session", "[-aC] [-t target-session]"),
    ("kill-window", "(killw) [-a] [-t target-window]"),
    ("last-pane", "(lastp) [-deZ] [-t target-window]"),
    ("last-window", "(last) [-t target-session]"),
    (
        "link-window",
        "(linkw) [-abdk] [-s src-window] [-t dst-window]",
    ),
    ("list-buffers", "(lsb) [-F format] [-f filter] [-O order]"),
    (
        "list-clients",
        "(lsc) [-F format] [-f filter] [-O order][-t target-session]",
    ),
    ("list-commands", "(lscm) [-F format] [command]"),
    (
        "list-keys",
        "(lsk) [-1aNr] [-F format] [-O order] [-P prefix-string][-T key-table] [key]",
    ),
    (
        "list-panes",
        "(lsp) [-asr] [-F format] [-f filter] [-O order][-t target-window]",
    ),
    ("list-sessions", "(ls) [-r] [-F format] [-f filter] [-O order]"),
    (
        "list-windows",
        "(lsw) [-ar] [-F format] [-f filter] [-O order][-t target-session]",
    ),
    (
        "load-buffer",
        "(loadb) [-b buffer-name] [-t target-client] path",
    ),
    ("lock-client", "(lockc) [-t target-client]"),
    ("lock-server", "(lock) "),
    ("lock-session", "(locks) [-t target-session]"),
    (
        "move-pane",
        "(movep) [-bdfhv] [-l size] [-s src-pane] [-t dst-pane]",
    ),
    (
        "move-window",
        "(movew) [-abdkr] [-s src-window] [-t dst-window]",
    ),
    (
        "new-session",
        "(new) [-AdDEPX] [-c start-directory] [-e environment] [-F format] [-f flags] [-n window-name] [-s session-name] [-t target-session] [-x width] [-y height] [shell-command]",
    ),
    (
        "new-window",
        "(neww) [-abdkPS] [-c start-directory] [-e environment] [-F format] [-n window-name] [-t target-window] [shell-command [argument ...]]",
    ),
    ("next-layout", "(nextl) [-t target-window]"),
    ("next-window", "(next) [-a] [-t target-session]"),
    (
        "paste-buffer",
        "(pasteb) [-dprS] [-s separator] [-b buffer-name] [-t target-pane]",
    ),
    ("pipe-pane", "(pipep) [-IOo] [-t target-pane] [shell-command]"),
    ("previous-layout", "(prevl) [-t target-window]"),
    ("previous-window", "(prev) [-a] [-t target-session]"),
    (
        "refresh-client",
        "(refresh) [-cDlLRSU] [-A pane:state] [-B name:what:format] [-C XxY] [-f flags] [-r pane:report] [-t target-client] [adjustment]",
    ),
    ("rename-session", "(rename) [-t target-session] new-name"),
    ("rename-window", "(renamew) [-t target-window] new-name"),
    (
        "resize-pane",
        "(resizep) [-DLMRTUZ] [-x width] [-y height] [-t target-pane] [adjustment]",
    ),
    (
        "resize-window",
        "(resizew) [-aADLRU] [-x width] [-y height] [-t target-window] [adjustment]",
    ),
    (
        "respawn-pane",
        "(respawnp) [-k] [-c start-directory] [-e environment] [-t target-pane] [shell-command [argument ...]]",
    ),
    (
        "respawn-window",
        "(respawnw) [-k] [-c start-directory] [-e environment] [-t target-window] [shell-command [argument ...]]",
    ),
    ("rotate-window", "(rotatew) [-DUZ] [-t target-window]"),
    (
        "run-shell",
        "(run) [-bCE] [-c start-directory] [-d delay] [-t target-pane] [shell-command]",
    ),
    ("save-buffer", "(saveb) [-a] [-b buffer-name] path"),
    ("select-layout", "(selectl) [-Enop] [-t target-pane] [layout-name]"),
    (
        "select-pane",
        "(selectp) [-DdeLlMmRUZ] [-T title] [-t target-pane]",
    ),
    ("select-window", "(selectw) [-lnpT] [-t target-window]"),
    (
        "send-keys",
        "(send) [-FHKlMRX] [-c target-client] [-N repeat-count] [-t target-pane] [key ...]",
    ),
    ("send-prefix", "[-2] [-t target-pane]"),
    ("server-access", "[-adlrw] [-t target-pane] [user]"),
    (
        "set-buffer",
        "(setb) [-aw] [-b buffer-name] [-n new-buffer-name] [-t target-client] [data]",
    ),
    (
        "set-environment",
        "(setenv) [-Fhgru] [-t target-session] variable [value]",
    ),
    ("set-hook", "[-agpRuw] [-t target-pane] hook [command]"),
    (
        "set-option",
        "(set) [-aFgopqsuUw] [-t target-pane] option [value]",
    ),
    (
        "set-window-option",
        "(setw) [-aFgoqu] [-t target-window] option [value]",
    ),
    ("show-buffer", "(showb) [-b buffer-name]"),
    (
        "show-environment",
        "(showenv) [-hgs] [-t target-session] [variable]",
    ),
    ("show-hooks", "[-gpw] [-t target-pane] [hook]"),
    ("show-messages", "(showmsgs) [-JT] [-t target-client]"),
    (
        "show-options",
        "(show) [-AgHpqsvw] [-t target-pane] [option]",
    ),
    ("show-prompt-history", "(showphist) [-T prompt-type]"),
    (
        "show-window-options",
        "(showw) [-gv] [-t target-window] [option]",
    ),
    ("source-file", "(source) [-Fnqv] [-t target-pane] path ..."),
    (
        "split-window",
        "(splitw) [-bdefhIPvZ] [-c start-directory] [-e environment] [-F format] [-l size] [-t target-pane] [shell-command [argument ...]]",
    ),
    ("start-server", "(start) "),
    ("suspend-client", "(suspendc) [-t target-client]"),
    ("swap-pane", "(swapp) [-dDUZ] [-s src-pane] [-t dst-pane]"),
    ("swap-window", "(swapw) [-d] [-s src-window] [-t dst-window]"),
    (
        "switch-client",
        "(switchc) [-ElnprZ] [-c target-client] [-t target-session] [-T key-table] [-O order]",
    ),
    ("unbind-key", "(unbind) [-anq] [-T key-table] key"),
    ("unlink-window", "(unlinkw) [-k] [-t target-window]"),
    ("wait-for", "(wait) [-L|-S|-U] channel"),
];

pub(super) fn run_list_commands(args: ListCommandsArgs) -> Result<i32, ExitFailure> {
    let entries = implemented_command_surface();
    let requested = args
        .command
        .as_deref()
        .map(resolve_list_commands_target)
        .transpose()?;
    let format = args.format.as_deref();
    let lines = entries
        .iter()
        .copied()
        .filter(|entry| requested.map(|name| entry.name == name).unwrap_or(true))
        .map(|entry| render_list_commands_line(format, entry.name, entry.alias))
        .collect::<Vec<_>>();

    write_lines_output(&lines)
}

fn resolve_list_commands_target(name: &str) -> Result<&'static str, ExitFailure> {
    if let Some(alias) = documented_cli_aliases()
        .iter()
        .find(|alias| alias.alias == name)
    {
        return Ok(alias.expansion.split(' ').next().unwrap_or("choose-tree"));
    }

    let resolved = lookup_command(name).map_err(|error| ExitFailure::new(1, error.to_string()))?;
    if implemented_command_surface()
        .iter()
        .any(|entry| entry.name == resolved.name)
    {
        Ok(resolved.name)
    } else {
        Err(ExitFailure::new(
            1,
            format!("command not implemented: {}", resolved.name),
        ))
    }
}

pub(super) fn render_list_commands_line(
    format: Option<&str>,
    name: &str,
    alias: Option<&str>,
) -> String {
    let alias = alias.unwrap_or("");
    let usage = list_command_usage(name);
    match format {
        Some(template) => template
            .replace("#{command_name}", name)
            .replace("#{command_alias}", alias)
            .replace("#{command_list_name}", name)
            .replace("#{command_list_alias}", alias)
            .replace("#{command_list_usage}", usage),
        None => format!("{name} {usage}"),
    }
}

fn list_command_usage(name: &str) -> &'static str {
    LIST_COMMAND_SIGNATURES
        .iter()
        .find_map(|(command_name, usage)| (*command_name == name).then_some(*usage))
        .unwrap_or("")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn list_command_signatures_match_implemented_inventory_order() {
        let expected = implemented_command_surface()
            .iter()
            .map(|entry| entry.name)
            .collect::<Vec<_>>();
        let actual = LIST_COMMAND_SIGNATURES
            .iter()
            .map(|(name, _usage)| *name)
            .collect::<Vec<_>>();

        assert_eq!(actual, expected);
    }

    #[test]
    fn list_command_signature_aliases_match_inventory_aliases() {
        for entry in implemented_command_surface() {
            let usage = list_command_usage(entry.name);
            match entry.alias {
                Some(alias) => assert!(
                    usage.starts_with(&format!("({alias})")),
                    "{} list-commands usage should start with alias ({alias}), got {usage:?}",
                    entry.name
                ),
                None => assert!(
                    !usage.starts_with('('),
                    "{} list-commands usage should not advertise an alias, got {usage:?}",
                    entry.name
                ),
            }
        }
    }
}