rmux-core 0.10.0

Core session, pane, layout, format, hook, and buffer model for the RMUX terminal multiplexer.
Documentation
use super::Screen;
use crate::grid::GridLineFlags;
use crate::input::InputParser;
use crate::{GridRenderOptions, ScreenCaptureRange};
use rmux_proto::TerminalSize;

const SIZE: TerminalSize = TerminalSize { cols: 8, rows: 6 };

fn screen_with(bytes: &[u8]) -> Screen {
    screen_with_size(bytes, SIZE)
}

fn screen_with_size(bytes: &[u8], size: TerminalSize) -> Screen {
    let mut screen = Screen::new(size, 100);
    let mut parser = InputParser::new();
    parser.parse(bytes, &mut screen);
    screen
}

fn wrapped(screen: &Screen, row: u32) -> bool {
    screen
        .grid()
        .visible_line(row)
        .expect("visible row")
        .flags()
        .contains(GridLineFlags::WRAPPED)
}

fn transcript(screen: &Screen, join_wrapped: bool) -> Vec<u8> {
    screen.capture_transcript(
        ScreenCaptureRange {
            start_is_absolute: true,
            end_is_absolute: true,
            ..ScreenCaptureRange::default()
        },
        GridRenderOptions {
            join_wrapped,
            ..GridRenderOptions::default()
        },
    )
}

#[test]
fn partial_erase_preserves_both_soft_wrap_boundaries() {
    let screen = screen_with(b"abcdefghijklmnopqrst\x1b[2;2H\x1b[X\x1b[4;1HNEXT");

    assert!(wrapped(&screen, 0));
    assert!(wrapped(&screen, 1));
    assert!(!wrapped(&screen, 2));
    assert_eq!(
        transcript(&screen, false),
        b"abcdefgh\ni klmnop\nqrst\nNEXT\n\n\n"
    );
    assert_eq!(
        transcript(&screen, true),
        b"abcdefghi klmnopqrst\nNEXT\n\n\n"
    );
}

#[test]
fn complete_erase_breaks_both_soft_wrap_boundaries() {
    let screen = screen_with(b"abcdefghijklmnopqrst\x1b[2;1H\x1b[2K\x1b[4;1HNEXT");

    assert!(!wrapped(&screen, 0));
    assert!(!wrapped(&screen, 1));
    assert!(!wrapped(&screen, 2));
    let expected = b"abcdefgh\n\nqrst\nNEXT\n\n\n";
    assert_eq!(transcript(&screen, false), expected);
    assert_eq!(transcript(&screen, true), expected);
}

#[test]
fn erase_entry_paths_update_only_the_boundaries_they_clear() {
    for erase in [
        b"\x1b[2;1H\x1b[8X".as_slice(),
        b"\x1b[2;1H\x1b[K".as_slice(),
        b"\x1b[2;8H\x1b[1K".as_slice(),
        b"\x1b[2;4H\x1b[2K".as_slice(),
    ] {
        let mut input = b"abcdefghijklmnopqrst".to_vec();
        input.extend_from_slice(erase);
        let screen = screen_with(&input);
        assert_eq!(
            [wrapped(&screen, 0), wrapped(&screen, 1)],
            [false, false],
            "full-width erase sequence {erase:?}"
        );
    }

    let erase_to_display_end = screen_with(b"abcdefghijklmnopqrst\x1b[2;3H\x1b[J");
    assert_eq!(
        [
            wrapped(&erase_to_display_end, 0),
            wrapped(&erase_to_display_end, 1),
        ],
        [true, false]
    );

    let erase_from_display_start = screen_with(b"abcdefghijklmnopqrst\x1b[2;5H\x1b[1J");
    assert_eq!(
        [
            wrapped(&erase_from_display_start, 0),
            wrapped(&erase_from_display_start, 1),
        ],
        [false, true]
    );
}

#[test]
fn complete_erase_breaks_wrap_across_history_viewport_boundary() {
    let screen = screen_with_size(
        b"abcdefghijkl\r\n\x1b[1;1H\x1b[2K\x1b[2;1HNEXT",
        TerminalSize { cols: 8, rows: 2 },
    );

    assert_eq!(screen.history_size(), 1);
    assert!(!screen
        .grid()
        .absolute_line_wrapped(0)
        .expect("history row wrap flag"));
    let expected = b"abcdefgh\n\nNEXT\n";
    assert_eq!(transcript(&screen, false), expected);
    assert_eq!(transcript(&screen, true), expected);
}

#[test]
fn complete_erase_boundary_survives_reflow_and_resize() {
    let mut screen = screen_with(b"abcdefghijklmnopqrst\x1b[2;1H\x1b[2K\x1b[4;1HNEXT");

    let initial = b"abcdefgh\n\nqrst\nNEXT\n\n\n";
    assert_eq!(transcript(&screen, false), initial);
    assert_eq!(transcript(&screen, true), initial);

    screen.resize(TerminalSize { cols: 5, rows: 6 });
    assert_eq!(transcript(&screen, true), initial);

    screen.resize(SIZE);
    assert_eq!(transcript(&screen, false), initial);
    assert_eq!(transcript(&screen, true), initial);
}

#[test]
fn complete_erase_boundary_is_preserved_on_alternate_screen() {
    let screen = screen_with(b"MAIN\x1b[?1049h\x1b[Habcdefghijkl\x1b[2;1H\x1b[2K\x1b[3;1HNEXT");

    let expected = b"abcdefgh\n\nNEXT\n\n\n\n";
    assert!(screen.is_alternate());
    assert_eq!(transcript(&screen, false), expected);
    assert_eq!(transcript(&screen, true), expected);
    assert_eq!(
        screen
            .capture_saved_transcript(
                ScreenCaptureRange {
                    start_is_absolute: true,
                    end_is_absolute: true,
                    ..ScreenCaptureRange::default()
                },
                GridRenderOptions::default(),
            )
            .expect("saved main screen"),
        b"MAIN\n\n\n\n\n\n"
    );
}