rmux-server 0.1.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::formats::{is_known_format_variable_name, FormatVariable, FormatVariables};
use rmux_core::input::mode;
use rmux_core::{Session, Window, WINLINK_ACTIVITY, WINLINK_BELL, WINLINK_SILENCE};

use crate::hook_runtime::current_hook_format_value;

use crate::host_name::local_hostname;

use super::{bool_string, server_start_time, RuntimeFormatContext};

impl FormatVariables for RuntimeFormatContext<'_> {
    fn format_value(&self, variable: FormatVariable) -> Option<String> {
        if variable == FormatVariable::WindowName {
            return self.window_name();
        }
        self.base.format_value(variable)
    }

    fn format_loop(&self, scope: char, body: &str, count_only: bool) -> Option<String> {
        match scope {
            'S' => self.render_session_loop(body, count_only),
            'W' => self.render_window_loop(body, count_only),
            'P' => self.render_pane_loop(body, count_only),
            _ => None,
        }
    }

    fn format_name_exists(&self, scope: Option<char>, name: &str) -> Option<bool> {
        match scope {
            Some('s') => Some(
                rmux_proto::SessionName::new(name)
                    .ok()
                    .and_then(|session_name| {
                        self.session_store
                            .map(|sessions| sessions.contains_session(&session_name))
                    })
                    .unwrap_or(false),
            ),
            None | Some('w') => Some(
                self.session
                    .map(|session| {
                        session.windows().iter().any(|(window_index, window)| {
                            self.rendered_window_name(*window_index, window).as_deref()
                                == Some(name)
                        })
                    })
                    .unwrap_or(false),
            ),
            Some(_) => None,
        }
    }

    fn format_value_by_name(&self, name: &str) -> Option<String> {
        let runtime_value = match name {
            "pane_at_bottom" => self.visible_pane_snapshot().map(|pane| {
                bool_string(self.visible_window_snapshot().is_some_and(|window| {
                    pane.geometry().y() + pane.geometry().rows() >= window.size().rows
                }))
            }),
            "pane_bottom" => self.visible_pane_snapshot().map(|pane| {
                (pane.geometry().y() + pane.geometry().rows())
                    .saturating_sub(1)
                    .to_string()
            }),
            "pane_height" => self
                .visible_pane_snapshot()
                .map(|pane| pane.geometry().rows().to_string()),
            "pane_width" => self
                .visible_pane_snapshot()
                .map(|pane| pane.geometry().cols().to_string()),
            "session_activity_flag" => self.session_flag(WINLINK_ACTIVITY),
            "session_alert" => self.session_alert(),
            "session_alerts" => self.session_alerts(),
            "session_bell_flag" => self.session_flag(WINLINK_BELL),
            "session_height" => (self.hide_session_size || self.session_attached_count() == 0)
                .then(String::new)
                .or_else(|| {
                    self.visible_session_snapshot()
                        .map(|session| session.window().size().rows.to_string())
                }),
            "session_silence_flag" => self.session_flag(WINLINK_SILENCE),
            "session_width" => (self.hide_session_size || self.session_attached_count() == 0)
                .then(String::new)
                .or_else(|| {
                    self.visible_session_snapshot()
                        .map(|session| session.window().size().cols.to_string())
                }),
            "window_activity_flag" => self.window_flag(WINLINK_ACTIVITY),
            "window_bell_flag" => self.window_flag(WINLINK_BELL),
            "window_flags" => self.window_flags(),
            "window_height" => self
                .visible_window_snapshot()
                .map(|window| window.size().rows.to_string()),
            "window_layout" | "window_visible_layout" => self
                .layout_window_snapshot()
                .map(|window| window.layout_dump()),
            "window_linked" => self.window_linked(),
            "window_linked_sessions" => self.window_linked_sessions(),
            "window_linked_sessions_list" => self.window_linked_sessions_list(),
            "window_name" => self.window_name(),
            "window_raw_flags" => Some(self.printable_window_flags(false)),
            "window_silence_flag" => self.window_flag(WINLINK_SILENCE),
            "window_width" => self
                .visible_window_snapshot()
                .map(|window| window.size().cols.to_string()),
            _ => None,
        };
        if let Some(value) = runtime_value {
            return Some(value);
        }
        if let Some(value) = self.base.format_value_by_name(name) {
            return Some(value);
        }
        if let Some(value) = current_hook_format_value(name) {
            return Some(value);
        }

        let value = match name {
            "active_window_index" => self
                .session
                .map(|session| session.active_window_index().to_string()),
            "buffer_full" => self
                .buffer_head()
                .map(|(_, content)| String::from_utf8_lossy(&content).into_owned()),
            "buffer_name" => self.buffer_head().map(|(name, _)| name),
            "buffer_sample" => self.buffer_head().map(|(_, content)| {
                let text = String::from_utf8_lossy(&content);
                text.chars().take(50).collect()
            }),
            "buffer_size" => self
                .buffer_head()
                .map(|(_, content)| content.len().to_string()),
            "client_height" => self.client_size.map(|size| size.rows.to_string()),
            "client_width" => self.client_size.map(|size| size.cols.to_string()),
            "config_files" => Some(String::new()),
            "cursor_x" => self
                .pane_cursor_position()
                .map(|(cursor_x, _)| cursor_x.to_string()),
            "cursor_y" => self
                .pane_cursor_position()
                .map(|(_, cursor_y)| cursor_y.to_string()),
            "history_bytes" => self.pane_history_bytes(),
            "history_limit" => self.pane_history_limit(),
            "history_size" => self.pane_history_size(),
            "host" => local_hostname(),
            "host_short" => {
                local_hostname().map(|host| host.split('.').next().unwrap_or_default().to_owned())
            }
            "last_window_index" => self
                .session
                .and_then(|session| session.windows().keys().next_back().copied())
                .map(|value| value.to_string()),
            "next_session_id" => self
                .session_store
                .map(|sessions| sessions.next_session_id().as_u32().to_string()),
            "pane_at_left" => self.pane.map(|pane| bool_string(pane.geometry().x() == 0)),
            "pane_at_right" => self.pane.map(|pane| {
                bool_string(self.window.is_some_and(|window| {
                    pane.geometry().x() + pane.geometry().cols() >= window.size().cols
                }))
            }),
            "pane_at_top" => self.pane.map(|pane| bool_string(pane.geometry().y() == 0)),
            "alternate_on" => self.pane_alternate_on(),
            "bracket_paste_flag" => self.pane_mode_flag(mode::MODE_BRACKETPASTE),
            "mouse_all_flag" => self.pane_mode_flag(mode::MODE_MOUSE_ALL),
            "mouse_any_flag" => self
                .pane_screen_mode()
                .map(|mode_value| bool_string(mode_value & mode::ALL_MOUSE_MODES != 0)),
            "mouse_button_flag" => self.pane_mode_flag(mode::MODE_MOUSE_BUTTON),
            "mouse_sgr_flag" => self.pane_mode_flag(mode::MODE_MOUSE_SGR),
            "mouse_standard_flag" => self.pane_mode_flag(mode::MODE_MOUSE_STANDARD),
            "mouse_utf8_flag" => self.pane_mode_flag(mode::MODE_MOUSE_UTF8),
            "pane_current_path" | "pane_path" | "session_path" => self
                .pane_current_path()
                .or_else(|| self.environment_value_by_name("PWD"))
                .or_else(|| self.environment_value_by_name("HOME")),
            "pane_current_command" => self.pane_current_command(),
            "pane_dead" => Some(bool_string(self.pane_dead())),
            "pane_dead_signal" => self.pane_dead_signal(),
            "pane_dead_status" => self.pane_dead_status(),
            "pane_dead_time" => self.pane_dead_time(),
            "pane_flags" => self.pane.map(|pane| {
                let mut flags = String::new();
                if self
                    .base
                    .format_value(FormatVariable::PaneActive)
                    .is_some_and(|value| value == "1")
                {
                    flags.push('*');
                }
                if self.window.is_some_and(Window::is_zoomed)
                    && self
                        .window
                        .is_some_and(|window| window.active_pane_index() == pane.index())
                {
                    flags.push('Z');
                }
                flags
            }),
            "pane_format" => Some(bool_string(self.pane.is_some())),
            "pane_in_mode" => Some(bool_string(self.pane_in_mode())),
            "pane_marked" => self.pane_marked(),
            "pane_marked_set" => Some(bool_string(self.marked_pane_set())),
            "pane_last" => self.pane.map(|pane| {
                bool_string(
                    self.window
                        .and_then(Window::last_pane_index)
                        .is_some_and(|last| last == pane.index()),
                )
            }),
            "pane_left" => self.pane.map(|pane| pane.geometry().x().to_string()),
            "pane_mode" => self.pane_mode_name(),
            "pane_lifecycle_generation" | "pane_generation" => self.pane_lifecycle_generation(),
            "pane_lifecycle_revision" | "pane_revision" => self.pane_lifecycle_revision(),
            "pane_output_sequence" => self.pane_output_sequence(),
            "pane_pid" => self.pane_pid(),
            "pane_right" => self.pane.map(|pane| {
                (pane.geometry().x() + pane.geometry().cols())
                    .saturating_sub(1)
                    .to_string()
            }),
            "pane_search_string" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.pane_search_string),
            "pane_start_command" => self.pane_start_command(),
            "pane_start_path" => self
                .pane_start_path()
                .or_else(|| self.environment_value_by_name("PWD"))
                .or_else(|| self.environment_value_by_name("HOME")),
            "pane_tty" => self.pane_tty(),
            "pane_title" => self.pane_title(),
            "pane_top" => self.pane.map(|pane| pane.geometry().y().to_string()),
            "pane_zoomed_flag" => Some(bool_string(self.window.is_some_and(Window::is_zoomed))),
            "pid" => Some(std::process::id().to_string()),
            "scroll_position" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.scroll_position.to_string()),
            "rectangle_toggle" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.rectangle_toggle)),
            "copy_cursor_x" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.cursor_x.to_string()),
            "copy_cursor_y" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.cursor_y.to_string()),
            "selection_start_x" => self.pane_copy_mode_summary().and_then(|summary| {
                summary
                    .selection_start
                    .map(|position| position.x.to_string())
            }),
            "selection_start_y" => self.pane_copy_mode_summary().and_then(|summary| {
                summary
                    .selection_start
                    .map(|position| position.y.to_string())
            }),
            "selection_end_x" => self
                .pane_copy_mode_summary()
                .and_then(|summary| summary.selection_end.map(|position| position.x.to_string())),
            "selection_end_y" => self
                .pane_copy_mode_summary()
                .and_then(|summary| summary.selection_end.map(|position| position.y.to_string())),
            "selection_active" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.selection_active)),
            "selection_present" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.selection_present)),
            "selection_mode" => self.pane_copy_mode_summary().and_then(|summary| {
                summary
                    .selection_mode
                    .map(|selection_mode| selection_mode.as_str().to_owned())
            }),
            "search_present" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.search_present)),
            "search_timed_out" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.search_timed_out)),
            "search_count" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.search_count.to_string()),
            "search_count_partial" => self
                .pane_copy_mode_summary()
                .map(|summary| bool_string(summary.search_count_partial)),
            "search_match" => self
                .pane_copy_mode_summary()
                .and_then(|summary| summary.search_match),
            "copy_cursor_word" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.copy_cursor_word),
            "copy_cursor_line" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.copy_cursor_line),
            "copy_cursor_hyperlink" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.copy_cursor_hyperlink),
            "top_line_time" => self
                .pane_copy_mode_summary()
                .map(|summary| summary.top_line_time.to_string()),
            "server_sessions" => self
                .session_store
                .map(|sessions| sessions.len().to_string()),
            "session_active" => Some(bool_string(self.session.is_some())),
            "session_created" => self.session.map(|session| session.created_at().to_string()),
            "session_format" => Some(bool_string(
                self.session.is_some() && self.window.is_none() && self.pane.is_none(),
            )),
            "session_group" => Some(
                self.session_group_name()
                    .map(|name| name.to_string())
                    .unwrap_or_default(),
            ),
            "session_group_attached" => Some(self.session_attached_count().to_string()),
            "session_group_attached_list" => Some(
                (self.session_attached_count() > 0)
                    .then(|| self.session_name().map(ToString::to_string))
                    .flatten()
                    .unwrap_or_default(),
            ),
            "session_group_list" => Some(
                self.session_group_members()
                    .into_iter()
                    .map(|session_name| session_name.to_string())
                    .collect::<Vec<_>>()
                    .join(","),
            ),
            "session_group_many_attached" => Some(bool_string(self.session_attached_count() > 1)),
            "session_group_size" => Some(self.session_group_members().len().to_string()),
            "session_grouped" => Some(bool_string(self.session_group_name().is_some())),
            "session_id" => self.session.map(|session| session.id().to_string()),
            "session_last_attached" => self
                .session
                .and_then(Session::last_attached_at)
                .map(|timestamp| timestamp.to_string()),
            "session_many_attached" => Some(bool_string(self.session_attached_count() > 1)),
            "session_marked" => Some(bool_string(self.session_marked())),
            "socket_path" => Some(String::new()),
            "start_time" => Some(server_start_time().to_string()),
            "uid" => std::env::var("UID").ok(),
            "user" => std::env::var("USER").ok(),
            "version" => Some(env!("CARGO_PKG_VERSION").to_owned()),
            "window_active_clients" => Some(
                if self
                    .base
                    .format_value(FormatVariable::WindowActive)
                    .is_some_and(|value| value == "1")
                {
                    self.session_attached_count().to_string()
                } else {
                    "0".to_owned()
                },
            ),
            "window_active_sessions" => Some(bool_string(
                self.base
                    .format_value(FormatVariable::WindowActive)
                    .is_some_and(|value| value == "1"),
            )),
            "window_bigger" => Some("0".to_owned()),
            "window_end_flag" => self.window_index.map(|window_index| {
                bool_string(
                    self.session
                        .and_then(|session| session.windows().keys().next_back().copied())
                        .is_some_and(|last| last == window_index),
                )
            }),
            "window_format" => Some(bool_string(self.window.is_some() && self.pane.is_none())),
            "window_marked_flag" => Some(bool_string(self.window_marked())),
            "window_offset_x" | "window_offset_y" => Some("0".to_owned()),
            "window_start_flag" => self.window_index.map(|window_index| {
                bool_string(
                    self.session
                        .and_then(|session| session.windows().keys().next().copied())
                        .is_some_and(|first| first == window_index),
                )
            }),
            "window_zoomed_flag" => Some(bool_string(self.window.is_some_and(Window::is_zoomed))),
            _ => None,
        };

        if let Some(value) = value {
            return Some(value);
        }

        if let Some(value) = self.option_value_by_name(name) {
            return Some(value);
        }

        if let Some(value) = self.environment_value_by_name(name) {
            return Some(value);
        }

        if is_known_format_variable_name(name) {
            return Some(String::new());
        }

        None
    }
}