rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_proto::{
    ResolveTargetRequest, ResolveTargetType, RmuxError, RunShellRequest, SessionName, Target,
};

#[cfg(windows)]
use super::super::pane_support::format_references_pane_pid;
use super::super::{RequestHandler, StableTargetIdentity};
use super::format_context::{format_context_for_target_with_server_values, global_format_context};
use super::shell_runtime::{hook_session_default_target, pane_id_for_target};
use crate::format_runtime::render_runtime_template;
use crate::hook_runtime::current_hook_formats;
use crate::terminal::TerminalProfile;

#[derive(Debug)]
pub(super) struct SynchronousRunShellDispatch {
    pub(super) expanded_command: String,
    pub(super) shell_profile: Option<TerminalProfile>,
    pub(super) target_identity: Option<StableTargetIdentity>,
}

impl RequestHandler {
    pub(super) async fn capture_synchronous_run_shell_dispatch(
        &self,
        requester_pid: u32,
        request: &mut RunShellRequest,
        client_name: Option<&str>,
        target_missing_canfail: bool,
    ) -> Result<SynchronousRunShellDispatch, RmuxError> {
        if request.target.is_none() && !target_missing_canfail {
            request.target = match self
                .resolve_target_for_requester(
                    requester_pid,
                    ResolveTargetRequest {
                        target: None,
                        target_type: ResolveTargetType::Pane,
                        window_index: false,
                        prefer_unattached: false,
                    },
                )
                .await
            {
                Ok(Target::Pane(target)) => Some(target),
                Ok(_) | Err(_) => None,
            };
        }
        #[cfg(windows)]
        if format_references_pane_pid(Some(&request.command)) {
            self.wait_for_windows_deferred_all_pane_pids().await;
        }
        let attached_count = match request.target.as_ref() {
            Some(target) => self.attached_count(target.session_name()).await,
            None => 0,
        };
        let hook_formats = current_hook_formats();
        let socket_path = self.socket_path();
        let mut state = self.state.lock().await;
        let target_identity = request.target.as_ref().and_then(|target| {
            StableTargetIdentity::capture(&mut state, Target::Pane(target.clone())).ok()
        });
        let expanded_command = render_run_shell_command_from_state(
            &state,
            request,
            client_name,
            target_missing_canfail,
            attached_count,
            hook_formats,
            &socket_path,
        )?;
        let shell_profile = if request.as_commands {
            None
        } else {
            Some(self.run_shell_profile_from_state(&state, request)?)
        };
        Ok(SynchronousRunShellDispatch {
            expanded_command,
            shell_profile,
            target_identity,
        })
    }

    pub(super) fn run_shell_profile_from_state(
        &self,
        state: &crate::pane_terminals::HandlerState,
        request: &RunShellRequest,
    ) -> Result<TerminalProfile, RmuxError> {
        let (session_name, session_id) = request
            .target
            .as_ref()
            .and_then(|target| {
                state
                    .sessions
                    .session(target.session_name())
                    .map(|session| (Some(target.session_name()), Some(session.id().as_u32())))
            })
            .unwrap_or((None, None));

        let base_environment = request
            .target
            .as_ref()
            .and_then(|target| state.session_base_environment_for_pane_target(target));
        let pane_id = request
            .target
            .as_ref()
            .and_then(|target| pane_id_for_target(state, target));

        TerminalProfile::for_run_shell_with_base_environment(
            &state.environment,
            &state.options,
            session_name,
            session_id,
            &self.socket_path(),
            base_environment.as_ref(),
            !self.config_loading_active(),
            pane_id,
            request.start_directory.as_deref(),
        )
        .map(|profile| match request.source_depth {
            Some(depth) => profile.with_source_depth(depth),
            None if self.config_loading_active() => profile.with_source_depth(1),
            None => profile,
        })
    }
}

#[allow(clippy::too_many_arguments)]
pub(super) fn render_run_shell_command_from_state(
    state: &crate::pane_terminals::HandlerState,
    request: &RunShellRequest,
    client_name: Option<&str>,
    target_missing_canfail: bool,
    attached_count: usize,
    hook_formats: Vec<(String, String)>,
    socket_path: &std::path::Path,
) -> Result<String, RmuxError> {
    let context = match request.target.as_ref() {
        Some(target) => format_context_for_target_with_server_values(
            state,
            &Target::Pane(target.clone()),
            attached_count,
            socket_path,
        )
        .unwrap_or_else(|_| global_format_context(state, socket_path)),
        None if !target_missing_canfail => match hook_formats
            .iter()
            .rev()
            .find(|(name, _)| name == "hook_session_name")
            .and_then(|(_, value)| SessionName::new(value.clone()).ok())
            .and_then(|session_name| hook_session_default_target(state, &session_name))
        {
            Some(target) => {
                format_context_for_target_with_server_values(state, &target, 0, socket_path)?
            }
            None => global_format_context(state, socket_path),
        },
        None => global_format_context(state, socket_path),
    };
    let context = match client_name {
        Some(client_name) => context.with_named_value("client_name", client_name.to_owned()),
        None => context,
    };
    let context = hook_formats
        .into_iter()
        .fold(context, |context, (name, value)| {
            context.with_named_value(name, value)
        });
    let context = if request.as_commands {
        context
    } else {
        request
            .arguments
            .iter()
            .enumerate()
            .fold(context, |context, (index, value)| {
                context.with_named_value((index + 1).to_string(), value.clone())
            })
    };
    Ok(render_runtime_template(&request.command, &context, false))
}