rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use std::sync::{Arc, Mutex as StdMutex};

use rmux_core::{BoxLines, Style};
use rmux_proto::{Target, TerminalSize};

use super::super::scripting_support::rename_target_session;
use crate::renderer::{render_popup_overlay, OverlayRect, PopupContent, PopupRenderSpec};

use super::identity::OverlayIdentity;
use super::menu::MenuOverlayState;
use super::popup_job::{PopupDragMode, PopupJob, PopupSurface};
use super::scrollable::ScrollablePopupText;

#[derive(Debug, Clone)]
pub(in crate::handler) enum ClientOverlayState {
    Menu(Box<MenuOverlayState>),
    Popup(Box<PopupOverlayState>),
}

impl ClientOverlayState {
    pub(in crate::handler) fn id(&self) -> u64 {
        match self {
            Self::Menu(menu) => menu.id,
            Self::Popup(popup) => popup.id,
        }
    }

    pub(in crate::handler) fn render(&self) -> Vec<u8> {
        match self {
            Self::Menu(menu) => menu.render(),
            Self::Popup(popup) => popup.render(),
        }
    }

    pub(super) fn identity(&self) -> &OverlayIdentity {
        match self {
            Self::Menu(menu) => &menu.identity,
            Self::Popup(popup) => &popup.identity,
        }
    }

    pub(super) fn current_target(&self) -> &Target {
        match self {
            Self::Menu(menu) => &menu.current_target,
            Self::Popup(popup) => &popup.current_target,
        }
    }

    pub(in crate::handler) fn rename_session_targets(
        &mut self,
        old_name: &rmux_proto::SessionName,
        new_name: &rmux_proto::SessionName,
    ) {
        match self {
            Self::Menu(menu) => {
                menu.identity.rename_session(old_name, new_name);
                rename_target_session(&mut menu.current_target, old_name, new_name);
                menu.command_context
                    .rename_session_targets(old_name, new_name);
            }
            Self::Popup(popup) => {
                popup.identity.rename_session(old_name, new_name);
                rename_target_session(&mut popup.current_target, old_name, new_name);
                if let Some(menu) = popup.nested_menu.as_mut() {
                    menu.identity.rename_session(old_name, new_name);
                    rename_target_session(&mut menu.current_target, old_name, new_name);
                    menu.command_context
                        .rename_session_targets(old_name, new_name);
                }
            }
        }
    }
}

#[derive(Debug, Clone)]
pub(in crate::handler) struct PopupOverlayState {
    pub(in crate::handler) id: u64,
    pub(in crate::handler) identity: OverlayIdentity,
    pub(in crate::handler) current_target: Target,
    pub(in crate::handler) rect: OverlayRect,
    pub(in crate::handler) preferred_width: u16,
    pub(in crate::handler) preferred_height: u16,
    pub(in crate::handler) title: String,
    pub(in crate::handler) style: Style,
    pub(in crate::handler) border_style: Style,
    pub(in crate::handler) border_lines: BoxLines,
    pub(in crate::handler) close_on_exit: bool,
    pub(in crate::handler) close_on_zero_exit: bool,
    pub(in crate::handler) close_any_key: bool,
    pub(in crate::handler) no_job: bool,
    pub(in crate::handler) surface: Arc<StdMutex<PopupSurface>>,
    pub(in crate::handler) scrollable_text: Option<ScrollablePopupText>,
    pub(in crate::handler) job: Option<PopupJob>,
    pub(in crate::handler) nested_menu: Option<MenuOverlayState>,
    pub(in crate::handler) dragging: PopupDragMode,
}

impl PopupOverlayState {
    #[cfg(test)]
    pub(in crate::handler) fn begin_resize_for_test(&mut self) {
        self.dragging = PopupDragMode::Resize;
    }

    fn render(&self) -> Vec<u8> {
        let content = self.scrollable_text.as_ref().map_or_else(
            || {
                PopupContent::Surface(
                    self.surface
                        .lock()
                        .expect("popup surface")
                        .rows(&self.style),
                )
            },
            |text| PopupContent::Text(text.visible_lines(self.content_size().rows)),
        );
        let popup_frame = render_popup_overlay(&PopupRenderSpec {
            rect: self.rect,
            title: self.title.clone(),
            style: self.style.clone(),
            border_style: self.border_style.clone(),
            border_lines: self.border_lines,
            content,
        });
        if let Some(menu) = &self.nested_menu {
            let mut frame = popup_frame;
            frame.extend_from_slice(&menu.render());
            frame
        } else {
            popup_frame
        }
    }

    pub(super) fn content_origin(&self) -> (u16, u16) {
        if self.border_lines.visible() {
            (self.rect.x.saturating_add(1), self.rect.y.saturating_add(1))
        } else {
            (self.rect.x, self.rect.y)
        }
    }

    pub(super) fn content_size(&self) -> TerminalSize {
        let rect = if self.border_lines.visible() {
            OverlayRect {
                x: self.rect.x.saturating_add(1),
                y: self.rect.y.saturating_add(1),
                width: self.rect.width.saturating_sub(2),
                height: self.rect.height.saturating_sub(2),
            }
        } else {
            self.rect
        };
        TerminalSize {
            cols: rect.width,
            rows: rect.height,
        }
    }
}