rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{TargetFindContext, TargetFindFlags, TargetFindType, UnresolvedTarget, Window};
use rmux_proto::{PaneTarget, RmuxError, SessionName, Target};

use super::super::attach_support::ActiveAttachIdentity;
use super::super::RequestHandler;
use super::mode_tree_model::{ModeTreeActionIdentity, ModeTreeClientState, ModeTreePaneIdentity};
use crate::handler_support::attached_client_required;
use crate::pane_terminals::session_not_found;

impl RequestHandler {
    pub(super) async fn current_mode_tree_action_identity(
        &self,
        attach_pid: u32,
    ) -> Result<ModeTreeActionIdentity, RmuxError> {
        let active_attach = self.active_attach.lock().await;
        let active = active_attach
            .by_pid
            .get(&attach_pid)
            .filter(|active| {
                active.mode_tree.is_some()
                    && !active.closing.load(std::sync::atomic::Ordering::SeqCst)
            })
            .ok_or_else(|| RmuxError::Server("mode-tree is not active".to_owned()))?;
        Ok(ModeTreeActionIdentity::new(
            attach_pid,
            active.id,
            active.mode_tree_state_id,
        ))
    }

    pub(super) async fn capture_mode_tree_host_identity(
        &self,
        target: &PaneTarget,
    ) -> Result<ModeTreePaneIdentity, RmuxError> {
        let mut state = self.state.lock().await;
        ModeTreePaneIdentity::capture(&mut state, target)
    }

    pub(super) async fn capture_mode_tree_host(
        &self,
        target: &PaneTarget,
    ) -> Result<
        (
            ModeTreePaneIdentity,
            crate::pane_transcript::SharedPaneTranscript,
        ),
        RmuxError,
    > {
        let mut state = self.state.lock().await;
        let identity = ModeTreePaneIdentity::capture(&mut state, target)?;
        let transcript = state.transcript_handle(target)?;
        Ok((identity, transcript))
    }

    pub(super) async fn mode_tree_attach_identity(
        &self,
        requester_pid: u32,
        command_name: &str,
    ) -> Result<ActiveAttachIdentity, RmuxError> {
        let active_attach = self.active_attach.lock().await;
        if let Some(active) = active_attach.by_pid.get(&requester_pid) {
            return Ok(active.identity(requester_pid));
        }
        active_attach
            .by_pid
            .iter()
            .min_by_key(|(_, active)| active.id)
            .map(|(&pid, active)| active.identity(pid))
            .ok_or_else(|| attached_client_required(command_name))
    }

    pub(super) async fn detached_mode_tree_target(
        &self,
        target: Option<&str>,
    ) -> Result<PaneTarget, RmuxError> {
        let state = self.state.lock().await;
        let unresolved = target
            .map(|target| UnresolvedTarget::new(target.to_owned()))
            .unwrap_or_else(UnresolvedTarget::none);
        let resolved = state.sessions.resolve_unresolved_target(
            &unresolved,
            TargetFindType::Pane,
            TargetFindFlags::NONE,
            &TargetFindContext::new(None),
        )?;
        match resolved {
            Target::Pane(target) => Ok(target),
            Target::Session(_) | Target::Window(_) => Err(RmuxError::Server(
                "mode tree target did not resolve to a pane".to_owned(),
            )),
        }
    }

    pub(super) async fn mode_tree_for_action_identity(
        &self,
        identity: ModeTreeActionIdentity,
    ) -> Result<ModeTreeClientState, RmuxError> {
        let state = self.state.lock().await;
        let active_attach = self.active_attach.lock().await;
        let mode = active_attach
            .by_pid
            .get(&identity.attach_pid())
            .filter(|active| {
                active.id == identity.attach_id()
                    && active.mode_tree_state_id == identity.state_id()
                    && !active.closing.load(std::sync::atomic::Ordering::SeqCst)
            })
            .and_then(|active| active.mode_tree.as_ref())
            .filter(|mode| mode_tree_host_is_current(&state, mode))
            .cloned()
            .ok_or_else(|| RmuxError::Server("mode-tree is not active".to_owned()))?;
        Ok(mode)
    }

    pub(super) async fn activate_mode_tree_for_session(
        &self,
        requester: ActiveAttachIdentity,
        mode: &mut ModeTreeClientState,
        zoom_requested: bool,
    ) -> Result<(Vec<SessionName>, bool), RmuxError> {
        let mut state = self.state.lock().await;
        if !mode_tree_host_is_current(&state, mode) {
            return Err(RmuxError::Server(
                "mode-tree host identity changed before activation".to_owned(),
            ));
        }
        let host_transcript = mode
            .host_identity
            .as_ref()
            .zip(mode.host_transcript.as_ref())
            .map(|(identity, transcript)| (identity.target().clone(), transcript.clone()));

        let mut active_attach = self.active_attach.lock().await;
        let requester_is_current = active_attach
            .by_pid
            .get(&requester.attach_pid())
            .is_some_and(|active| {
                requester.matches_active_session(active, &mode.session_name, mode.session_id)
                    && !active.suspended
                    && !active.closing.load(std::sync::atomic::Ordering::SeqCst)
            });
        if !requester_is_current {
            return Err(attached_client_required(mode.kind.command_name()));
        }

        let pane_mode_changed = if let Some((target, transcript)) = host_transcript.as_ref() {
            let changed = transcript
                .lock()
                .expect("pane transcript mutex must not be poisoned")
                .enter_mode_tree(mode.kind.pane_mode_name());
            if changed {
                if let Err(error) = state.resize_terminals(target.session_name()) {
                    transcript
                        .lock()
                        .expect("pane transcript mutex must not be poisoned")
                        .clear_mode_tree();
                    let _ = state.resize_terminals(target.session_name());
                    return Err(error);
                }
            }
            changed
        } else {
            false
        };

        let refresh_sessions = if zoom_requested {
            let session = state
                .sessions
                .session(&mode.session_name)
                .filter(|session| session.id() == mode.session_id)
                .ok_or_else(|| session_not_found(&mode.session_name))?;
            let window_index = session.active_window_index();
            let pane_index = session.active_pane_index();
            if session
                .window_at(window_index)
                .is_some_and(Window::is_zoomed)
            {
                Vec::new()
            } else {
                let target =
                    PaneTarget::with_window(mode.session_name.clone(), window_index, pane_index);
                let zoom_restore = ModeTreePaneIdentity::capture(&mut state, &target)?;
                let refresh_sessions = match state
                    .mutate_session_and_resize_window_terminal_with_family(
                        &mode.session_name,
                        window_index,
                        |session| {
                            session.toggle_zoom_in_window(window_index, pane_index)?;
                            Ok(())
                        },
                    ) {
                    Ok(((), refresh_sessions)) => refresh_sessions,
                    Err(error) => {
                        if pane_mode_changed {
                            if let Some((target, transcript)) = host_transcript.as_ref() {
                                transcript
                                    .lock()
                                    .expect("pane transcript mutex must not be poisoned")
                                    .clear_mode_tree();
                                let _ = state.resize_terminals(target.session_name());
                            }
                        }
                        return Err(error);
                    }
                };
                mode.zoom_restore = Some(zoom_restore);
                refresh_sessions
            }
        } else {
            Vec::new()
        };

        for active in active_attach.by_pid.values_mut() {
            if active.session_name != mode.session_name
                || active.session_id != mode.session_id
                || active.suspended
                || active.closing.load(std::sync::atomic::Ordering::SeqCst)
            {
                continue;
            }
            active.mode_tree_state_id = active.mode_tree_state_id.saturating_add(1);
            active.persistent_overlay_epoch.store(
                active.mode_tree_state_id,
                std::sync::atomic::Ordering::SeqCst,
            );
            active.mode_tree = Some(mode.clone());
        }
        Ok((refresh_sessions, pane_mode_changed))
    }
}

pub(super) fn mode_tree_host_is_current(
    state: &crate::pane_terminals::HandlerState,
    mode: &ModeTreeClientState,
) -> bool {
    state
        .sessions
        .session(&mode.session_name)
        .is_some_and(|session| session.id() == mode.session_id)
        && mode
            .host_identity
            .as_ref()
            .is_none_or(|identity| identity.matches(state))
}