tastty-core 0.1.0

Sans-IO core of the tastty terminal session library: VT parser, screen buffer, and byte encoders.
use super::*;

#[test]
fn osc_133_a_marks_prompt_start_on_cursor_row() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]133;A\x07");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ShellIntegration {
        mark: SemanticPrompt::PromptStart,
    }));
    assert_eq!(
        parser
            .screen()
            .grid()
            .drawing_row(0)
            .unwrap()
            .semantic_prompt(),
        Some(SemanticPrompt::PromptStart),
    );
}

#[test]
fn osc_133_d_marks_output_end_with_exit_code() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]133;D;42\x07");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ShellIntegration {
        mark: SemanticPrompt::OutputEnd {
            exit_code: Some(42)
        },
    }));
    assert_eq!(
        parser
            .screen()
            .grid()
            .drawing_row(0)
            .unwrap()
            .semantic_prompt(),
        Some(SemanticPrompt::OutputEnd {
            exit_code: Some(42)
        }),
    );
}

#[test]
fn osc_133_d_without_exit_code_yields_none() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]133;D\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ShellIntegration {
        mark: SemanticPrompt::OutputEnd { exit_code: None },
    }));
}

#[test]
fn osc_133_d_negative_exit_code_round_trips() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]133;D;-1\x07");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ShellIntegration {
        mark: SemanticPrompt::OutputEnd {
            exit_code: Some(-1)
        },
    }));
}

#[test]
fn osc_133_malformed_drops_silently() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    // Missing kind letter.
    process(&mut parser, b"\x1b]133\x07");
    // Unknown kind letter.
    process(&mut parser, b"\x1b]133;Z\x07");
    // Non-numeric exit code on D.
    process(&mut parser, b"\x1b]133;D;abc\x07");
    let events = parser.screen_mut().drain_events();
    assert!(
        events
            .iter()
            .all(|e| !matches!(e, ScreenEvent::ShellIntegration { .. })),
        "no ShellIntegration event expected, got {events:?}",
    );
    assert_eq!(
        parser
            .screen()
            .grid()
            .drawing_row(0)
            .unwrap()
            .semantic_prompt(),
        None,
    );
}

#[test]
fn osc_133_most_recent_wins_on_same_row() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    // Single-line prompt: A then B then C all land on row 0.
    process(&mut parser, b"\x1b]133;A\x07");
    process(&mut parser, b"\x1b]133;B\x07");
    process(&mut parser, b"\x1b]133;C\x07");
    assert_eq!(
        parser
            .screen()
            .grid()
            .drawing_row(0)
            .unwrap()
            .semantic_prompt(),
        Some(SemanticPrompt::OutputStart),
    );
    // Each emission still surfaces an event (consumers tracking
    // transitions need every mark, not just the row-final one).
    let events = parser.screen_mut().drain_events();
    let marks: Vec<_> = events
        .iter()
        .filter_map(|e| match e {
            ScreenEvent::ShellIntegration { mark } => Some(*mark),
            _ => None,
        })
        .collect();
    assert_eq!(
        marks,
        vec![
            SemanticPrompt::PromptStart,
            SemanticPrompt::PromptEnd,
            SemanticPrompt::OutputStart,
        ],
    );
}

#[test]
fn osc_133_marks_separate_rows_after_lf() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]133;A\x07");
    process(&mut parser, b"\r\n");
    process(&mut parser, b"\x1b]133;C\x07");
    let screen = parser.screen();
    assert_eq!(
        screen.grid().drawing_row(0).unwrap().semantic_prompt(),
        Some(SemanticPrompt::PromptStart),
    );
    assert_eq!(
        screen.grid().drawing_row(1).unwrap().semantic_prompt(),
        Some(SemanticPrompt::OutputStart),
    );
}