rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::command_parser::{CommandArgument, ParsedCommands};
use rmux_core::key_code_lookup_bits;
use rmux_proto::{PaneTarget, RmuxError, Target};

use super::super::{scripting_support::QueueExecutionContext, RequestHandler};
use super::target_is_in_copy_mode;
use crate::key_table::{
    default_key_table_name, lookup_key_table_binding, COPY_MODE_TABLE, COPY_MODE_VI_TABLE,
};
use crate::limits::clamp_repeat_count;

pub(in crate::handler) struct DirectCopyModeCommand {
    pub(in crate::handler) command: String,
    pub(in crate::handler) args: Vec<String>,
    pub(in crate::handler) repeat_count: usize,
}

pub(in crate::handler) fn direct_copy_mode_command(
    commands: &ParsedCommands,
) -> Option<DirectCopyModeCommand> {
    if !commands.assignments().is_empty() {
        return None;
    }
    let [command] = commands.commands() else {
        return None;
    };
    if !matches!(command.name(), "send" | "send-keys") {
        return None;
    }

    let mut args = command.arguments().iter();
    let mut copy_mode_command = false;
    let mut repeat_count = 1;
    let mut command_args = Vec::new();
    while let Some(argument) = args.next() {
        let value = argument.as_string()?;
        match value {
            "--" => {
                command_args.extend(copy_mode_argument_strings(args)?);
                break;
            }
            "-X" => copy_mode_command = true,
            "-N" => {
                repeat_count = clamp_repeat_count(args.next()?.as_string()?.parse::<usize>().ok()?);
            }
            value if value.starts_with("-N") && value.len() > 2 => {
                repeat_count = clamp_repeat_count(value[2..].parse::<usize>().ok()?);
            }
            value if value.starts_with('-') => return None,
            value => {
                command_args.push(value.to_owned());
                command_args.extend(copy_mode_argument_strings(args)?);
                break;
            }
        }
    }

    if !copy_mode_command {
        return None;
    }
    let command = command_args.first()?.clone();
    Some(DirectCopyModeCommand {
        command,
        args: command_args.into_iter().skip(1).collect(),
        repeat_count,
    })
}

fn copy_mode_argument_strings<'a>(
    args: impl Iterator<Item = &'a CommandArgument>,
) -> Option<Vec<String>> {
    args.map(|argument| argument.as_string().map(str::to_owned))
        .collect()
}

impl RequestHandler {
    pub(in crate::handler) async fn handle_detached_copy_mode_key_code(
        &self,
        requester_pid: u32,
        target: PaneTarget,
        key: rmux_core::KeyCode,
    ) -> Result<bool, RmuxError> {
        let binding = {
            let state = self.state.lock().await;
            if !target_is_in_copy_mode(&state, &target) {
                return Ok(false);
            }
            let table_name = default_key_table_name(&state, &target);
            if !matches!(table_name.as_str(), COPY_MODE_TABLE | COPY_MODE_VI_TABLE) {
                return Ok(false);
            }
            lookup_key_table_binding(&state, &table_name, key_code_lookup_bits(key))
        };

        let Some(binding) = binding else {
            return Ok(true);
        };

        if let Some(command) = direct_copy_mode_command(binding.commands()) {
            Box::pin(self.execute_copy_mode_command(
                requester_pid,
                target,
                &command.command,
                &command.args,
                command.repeat_count,
            ))
            .await?;
            return Ok(true);
        }

        let context = QueueExecutionContext::without_caller_cwd()
            .with_current_target(Some(Target::Pane(target)));
        self.execute_parsed_commands(requester_pid, binding.commands().clone(), context)
            .await?;
        Ok(true)
    }
}

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

    #[test]
    fn direct_copy_mode_command_accepts_send_alias() {
        use rmux_core::command_parser::CommandParser;

        let parsed = CommandParser::new()
            .parse_one_group("send -N3 -X cancel")
            .unwrap();
        let command = direct_copy_mode_command(&parsed).unwrap();

        assert_eq!(command.command, "cancel");
        assert_eq!(command.args, Vec::<String>::new());
        assert_eq!(command.repeat_count, 3);
    }

    #[test]
    fn direct_copy_mode_command_clamps_repeat_count() {
        use rmux_core::command_parser::CommandParser;

        let parsed = CommandParser::new()
            .parse_one_group("send -N999999 -X cancel")
            .unwrap();
        let command = direct_copy_mode_command(&parsed).unwrap();

        assert_eq!(
            command.repeat_count,
            crate::limits::MAX_COMMAND_REPEAT_COUNT
        );
    }
}