rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use std::collections::{BTreeMap, HashMap};

use rmux_core::{PaneId, Session};
use rmux_proto::{OptionScopeSelector, PaneTarget, RmuxError, SessionName};

use super::{session_not_found, HandlerState};

pub(crate) type PaneSlotSnapshot = HashMap<PaneId, Vec<PaneTarget>>;

impl HandlerState {
    pub(crate) fn pane_alias_targets(&self, pane_id: PaneId) -> Vec<PaneTarget> {
        let mut aliases = self
            .sessions
            .iter()
            .flat_map(|(session_name, session)| {
                session
                    .windows()
                    .iter()
                    .flat_map(move |(window_index, window)| {
                        window
                            .panes()
                            .iter()
                            .filter(move |pane| pane.id() == pane_id)
                            .map(move |pane| {
                                PaneTarget::with_window(
                                    session_name.clone(),
                                    *window_index,
                                    pane.index(),
                                )
                            })
                    })
            })
            .collect::<Vec<_>>();
        aliases.sort_by(|left, right| {
            left.session_name()
                .as_str()
                .cmp(right.session_name().as_str())
                .then_with(|| left.window_index().cmp(&right.window_index()))
                .then_with(|| left.pane_index().cmp(&right.pane_index()))
        });
        aliases
    }

    pub(crate) fn synchronize_pane_alias_options_from_target(
        &mut self,
        source: &PaneTarget,
    ) -> Result<PaneId, RmuxError> {
        let pane_id = pane_id_for_target(&self.sessions, source)?;
        for alias in self.pane_alias_targets(pane_id) {
            if alias != *source {
                self.options.copy_pane_overrides(source, &alias);
            }
        }
        Ok(pane_id)
    }

    pub(crate) fn synchronize_pane_alias_options_from_session(
        &mut self,
        source: &Session,
    ) -> Result<(), RmuxError> {
        let sources = pane_option_slots(source)
            .into_values()
            .filter_map(|targets| targets.into_iter().next())
            .collect::<Vec<_>>();
        for source in sources {
            self.synchronize_pane_alias_options_from_target(&source)?;
        }
        Ok(())
    }

    pub(crate) fn pane_explicit_option_value_by_name(
        &self,
        target: &PaneTarget,
        name: &str,
    ) -> Result<(String, Option<String>), RmuxError> {
        let pane_id = pane_id_for_target(&self.sessions, target)?;
        let scope = OptionScopeSelector::Pane(target.clone());
        let (canonical_name, direct) = self.options.explicit_value_by_name(&scope, name)?;
        if direct.is_some() {
            return Ok((canonical_name, direct));
        }
        for alias in self.pane_alias_targets(pane_id) {
            if alias == *target {
                continue;
            }
            let (_, value) = self
                .options
                .explicit_value_by_name(&OptionScopeSelector::Pane(alias), name)?;
            if value.is_some() {
                return Ok((canonical_name, value));
            }
        }
        Ok((canonical_name, None))
    }

    pub(crate) fn pane_explicit_option_entries(
        &self,
        target: &PaneTarget,
    ) -> Result<Vec<(String, String)>, RmuxError> {
        let pane_id = pane_id_for_target(&self.sessions, target)?;
        let mut entries = BTreeMap::new();
        for alias in self.pane_alias_targets(pane_id) {
            for (name, value) in self
                .options
                .explicit_entries_for_scope(&OptionScopeSelector::Pane(alias))
            {
                entries.entry(name).or_insert(value);
            }
        }
        for (name, value) in self
            .options
            .explicit_entries_for_scope(&OptionScopeSelector::Pane(target.clone()))
        {
            entries.insert(name, value);
        }
        Ok(entries.into_iter().collect())
    }

    pub(crate) fn pane_option_slots_for_session(
        &self,
        session_name: &SessionName,
    ) -> Result<PaneSlotSnapshot, RmuxError> {
        let session = self
            .sessions
            .session(session_name)
            .ok_or_else(|| session_not_found(session_name))?;
        Ok(pane_option_slots(session))
    }

    pub(crate) fn rekey_pane_options_after_session_change(
        &mut self,
        before: &PaneSlotSnapshot,
        session_name: &SessionName,
    ) -> Result<(), RmuxError> {
        let after = self
            .sessions
            .session(session_name)
            .map(pane_option_slots)
            .unwrap_or_default();
        self.rekey_pane_options_between_snapshots(before, &after)
    }

    pub(crate) fn rekey_and_synchronize_pane_options_after_session_change(
        &mut self,
        before: &PaneSlotSnapshot,
        session_name: &SessionName,
    ) -> Result<(), RmuxError> {
        self.rekey_pane_options_after_session_change(before, session_name)?;
        let session = self
            .sessions
            .session(session_name)
            .cloned()
            .ok_or_else(|| session_not_found(session_name))?;
        self.synchronize_pane_alias_options_from_session(&session)
    }

    pub(crate) fn rekey_pane_options_between_snapshots(
        &mut self,
        before: &PaneSlotSnapshot,
        after: &PaneSlotSnapshot,
    ) -> Result<(), RmuxError> {
        let mut mappings = Vec::new();
        for (pane_id, sources) in before {
            let targets = after.get(pane_id).map(Vec::as_slice).unwrap_or_default();
            for (source, target) in sources.iter().zip(targets) {
                if source != target {
                    mappings.push((source.clone(), Some(target.clone())));
                }
            }
            mappings.extend(
                sources
                    .iter()
                    .skip(targets.len())
                    .cloned()
                    .map(|source| (source, None)),
            );
        }
        self.options.rekey_pane_overrides(&mappings)
    }
}

fn pane_id_for_target(
    sessions: &rmux_core::SessionStore,
    target: &PaneTarget,
) -> Result<PaneId, RmuxError> {
    sessions
        .session(target.session_name())
        .and_then(|session| session.window_at(target.window_index()))
        .and_then(|window| window.pane(target.pane_index()))
        .map(|pane| pane.id())
        .ok_or_else(|| {
            RmuxError::invalid_target(target.to_string(), "pane index does not exist in session")
        })
}

fn pane_option_slots(session: &Session) -> PaneSlotSnapshot {
    let mut slots = PaneSlotSnapshot::new();
    for (window_index, window) in session.windows() {
        for pane in window.panes() {
            slots
                .entry(pane.id())
                .or_default()
                .push(PaneTarget::with_window(
                    session.name().clone(),
                    *window_index,
                    pane.index(),
                ));
        }
    }
    for targets in slots.values_mut() {
        targets.sort_by(|left, right| {
            left.window_index()
                .cmp(&right.window_index())
                .then_with(|| left.pane_index().cmp(&right.pane_index()))
        });
    }
    slots
}