rmux-server 0.1.1

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

use super::super::RequestHandler;
use super::queue::QueueExecutionContext;
use crate::hook_runtime::current_hook_formats;
use crate::terminal::{spawn_hook_command_with_profile, TerminalProfile};

impl RequestHandler {
    #[cfg_attr(not(test), allow(dead_code))]
    pub(in crate::handler) async fn execute_hook_command(
        &self,
        requester_pid: u32,
        command: &str,
    ) -> Result<(), RmuxError> {
        self.execute_hook_command_with_context(requester_pid, command, None)
            .await
    }

    pub(in crate::handler) async fn execute_hook_command_with_context(
        &self,
        requester_pid: u32,
        command: &str,
        current_target: Option<Target>,
    ) -> Result<(), RmuxError> {
        let parsed = match self
            .parse_hook_command(command, current_target.as_ref())
            .await
        {
            Ok(parsed) => parsed,
            Err(error) => return Err(error),
        };

        self.execute_parsed_commands(
            requester_pid,
            parsed,
            QueueExecutionContext::without_caller_cwd().with_current_target(current_target),
        )
        .await
        .map(|_| ())
    }

    #[allow(dead_code)]
    async fn parse_hook_command(
        &self,
        command: &str,
        current_target: Option<&Target>,
    ) -> Result<ParsedCommands, RmuxError> {
        let mut parser = {
            let state = self.state.lock().await;
            CommandParser::new().with_environment_store(&state.environment)
        };
        for (name, value) in current_hook_formats() {
            parser = parser.with_format_value(name, value);
        }
        match parser.parse_one_group(command) {
            Ok(parsed) => Ok(parsed),
            Err(error) if error.message().starts_with("unknown command: ") => {
                let profile = self.hook_shell_profile(current_target).await?;
                spawn_hook_command_with_profile(command.to_owned(), &profile).map_err(|error| {
                    RmuxError::Server(format!(
                        "failed to spawn legacy shell hook command: {error}"
                    ))
                })?;
                Ok(ParsedCommands::default())
            }
            Err(error) => Err(super::command_parse_error_to_rmux(error)),
        }
    }

    async fn hook_shell_profile(
        &self,
        current_target: Option<&Target>,
    ) -> Result<TerminalProfile, RmuxError> {
        let state = self.state.lock().await;
        let session_name = target_session_name(current_target);
        let session_id = session_name
            .and_then(|name| state.sessions.session(name))
            .map(|session| session.id().as_u32());

        TerminalProfile::for_run_shell(
            &state.environment,
            &state.options,
            session_name,
            session_id,
            &self.socket_path(),
            !self.config_loading_active(),
            None,
        )
    }
}

fn target_session_name(target: Option<&Target>) -> Option<&SessionName> {
    match target {
        Some(Target::Session(session_name)) => Some(session_name),
        Some(Target::Window(target)) => Some(target.session_name()),
        Some(Target::Pane(target)) => Some(target.session_name()),
        None => None,
    }
}