rmux-server 0.1.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{
    input::{mode, GridAttr},
    style_tostring, GridRenderOptions, ScreenCaptureRange, Style, StyleCell, Utf8Config,
};
use rmux_proto::PaneTarget;

use crate::pane_terminals::{HandlerState, PaneCaptureRequest};

use super::super::mode_tree_render::{pad_visible_width, sanitize_overlay_text};

pub(in crate::handler::mode_tree_support) fn preview_lines_for_target(
    state: &HandlerState,
    target: &PaneTarget,
    limit: usize,
    width: usize,
    utf8: &Utf8Config,
) -> Vec<String> {
    if limit == 0 || width == 0 {
        return Vec::new();
    }

    let mut lines = state
        .transcript_handle(target)
        .ok()
        .and_then(|transcript| transcript.lock().ok().map(|guard| guard.clone_screen()))
        .map(|screen| preview_lines_for_screen(&screen, limit, width, utf8))
        .or_else(|| {
            state
                .capture_transcript(
                    target,
                    PaneCaptureRequest {
                        range: ScreenCaptureRange::default(),
                        options: GridRenderOptions {
                            include_empty_cells: true,
                            trim_spaces: false,
                            ..GridRenderOptions::default()
                        },
                        alternate: false,
                        use_mode_screen: false,
                        pending_input: false,
                        quiet: true,
                        escape_pending: false,
                    },
                )
                .ok()
                .map(|bytes| {
                    String::from_utf8_lossy(&bytes)
                        .lines()
                        .map(|line| pad_visible_width(&sanitize_overlay_text(line), width, utf8))
                        .collect::<Vec<_>>()
                })
        })
        .unwrap_or_default();
    if lines.len() > limit {
        lines.truncate(limit);
    }
    while lines.len() < limit {
        lines.push(" ".repeat(width));
    }
    lines
}

pub(in crate::handler::mode_tree_support) fn preview_lines_for_screen(
    screen: &rmux_core::Screen,
    limit: usize,
    width: usize,
    utf8: &Utf8Config,
) -> Vec<String> {
    if limit == 0 || width == 0 {
        return Vec::new();
    }

    let rows = usize::from(screen.size().rows.max(1));
    let left = preview_horizontal_offset(screen, width);
    let top = preview_vertical_offset(screen, limit);
    let visible = rows.saturating_sub(top).min(limit);
    if visible == 0 {
        return vec![" ".repeat(width); limit];
    }
    let (cursor_x, cursor_y) = screen.cursor_position();
    let mut lines = (0..visible)
        .filter_map(|offset| screen.absolute_line_view(screen.history_size() + top + offset))
        .enumerate()
        .map(|(offset, line)| {
            preview_line_for_screen_view(
                &line,
                left,
                width,
                top + offset,
                (cursor_x as usize, cursor_y as usize),
                utf8,
            )
        })
        .collect::<Vec<_>>();
    while lines.len() < limit {
        lines.push(" ".repeat(width));
    }
    lines
}

fn preview_line_for_screen_view(
    line: &rmux_core::ScreenLineView,
    start_x: usize,
    width: usize,
    row: usize,
    cursor: (usize, usize),
    _utf8: &Utf8Config,
) -> String {
    let mut rendered = String::new();
    let mut remaining = width;
    let mut active_style = None::<Style>;

    for (x, cell) in line.cells().iter().enumerate().skip(start_x) {
        if remaining == 0 {
            break;
        }
        if cell.is_padding() {
            continue;
        }

        let cell_width = usize::from(cell.width().max(1));
        if cell_width > remaining {
            break;
        }

        let text = sanitize_preview_text(cell.text());
        let mut style = preview_cell_style(cell);
        if row == cursor.1 && x == cursor.0 {
            style.cell.attr |= GridAttr::REVERSE;
        }
        if active_style.as_ref() != Some(&style) {
            rendered.push_str("#[");
            rendered.push_str(&style_tostring(&style));
            rendered.push(']');
            active_style = Some(style);
        }
        rendered.push_str(&super::escape_format_draw_text(&text));
        remaining = remaining.saturating_sub(cell_width);
    }

    if active_style.is_some() {
        rendered.push_str("#[default]");
    }
    rendered.push_str(&" ".repeat(remaining));
    rendered
}

fn sanitize_preview_text(value: &str) -> String {
    sanitize_overlay_text(value)
}

fn preview_cell_style(cell: &rmux_core::ScreenCellView) -> Style {
    Style {
        cell: StyleCell {
            fg: cell.fg(),
            bg: cell.bg(),
            us: cell.us(),
            attr: cell.attr(),
        },
        ..Style::default()
    }
}

pub(in crate::handler::mode_tree_support) fn preview_vertical_offset(
    screen: &rmux_core::Screen,
    height: usize,
) -> usize {
    let rows = usize::from(screen.size().rows.max(1));
    if rows <= height || (screen.mode() & mode::MODE_CURSOR) == 0 {
        return 0;
    }

    let (_, cursor_y) = screen.cursor_position();
    let mut top = cursor_y as usize;
    if top < height / 3 {
        top = 0;
    } else {
        top = top.saturating_sub(height / 3);
    }
    if top + height > rows {
        top = rows.saturating_sub(height);
    }
    top
}

pub(in crate::handler::mode_tree_support) fn preview_horizontal_offset(
    screen: &rmux_core::Screen,
    width: usize,
) -> usize {
    let cols = usize::from(screen.size().cols.max(1));
    if cols <= width || (screen.mode() & mode::MODE_CURSOR) == 0 {
        return 0;
    }

    let (cursor_x, _) = screen.cursor_position();
    let mut left = cursor_x as usize;
    if left < width / 3 {
        left = 0;
    } else {
        left = left.saturating_sub(width / 3);
    }
    if left + width > cols {
        left = cols.saturating_sub(width);
    }
    left
}