use super::*; use crate::page::{ContentTag, SemanticContent, SemanticPrompt as RowPrompt}; use ghostty_vt::{ ansi::ProtectedMode, color::Rgb, csi::{EraseDisplay, EraseLine, TabClear}, osc::semantic_prompt::{Action as PA, Command}, sgr::{Attribute as A, Underline}, }; fn pr(t: &mut Terminal, s: &str) { for c in s.chars() { t.print(u32::from(c)).unwrap(); } } fn red() -> Rgb { Rgb { r: 0xFF, g: 0, b: 0, } } fn assert_bg_red(t: &Terminal, xs: std::ops::Range, y: u32) { for x in xs { let c = cell_at(t, pt_active(x, y)); assert_eq!(c.content_tag(), ContentTag::BgColorRgb); assert_eq!(c.color_rgb(), red()); } } fn abc_def_ghi(t: &mut Terminal) { pr(t, "ABC"); t.carriage_return(); t.linefeed().unwrap(); pr(t, "DEF"); t.carriage_return(); t.linefeed().unwrap(); pr(t, "GHI"); } #[test] fn terminal_erase_line_resets_pending_wrap() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); assert!(t.screen().cursor.pending_wrap); t.erase_line(EraseLine::Right, false); assert!(!t.screen().cursor.pending_wrap); t.print(u32::from('B')).unwrap(); assert_eq!("ABCDB", plain(&t)); } #[test] fn terminal_erase_line_resets_wrap() { let mut t = init(5, 5); pr(&mut t, "ABCDE123"); assert!(row_at(&t, pt_active(0, 0)).wrap()); t.set_cursor_pos(1, 1); t.erase_line(EraseLine::Right, false); assert!(!row_at(&t, pt_active(0, 0)).wrap()); t.print(u32::from('X')).unwrap(); assert_eq!("X\n123", plain(&t)); } #[test] fn terminal_erase_line_right_preserves_background_sgr() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); t.set_cursor_pos(1, 2); t.set_attribute(A::DirectColorBg(red())).unwrap(); t.erase_line(EraseLine::Right, false); assert_eq!("A", plain(&t)); assert_bg_red(&t, 1..5, 0); } #[test] fn terminal_erase_line_right_wide_character() { let mut t = init(10, 5); pr(&mut t, "AB橋DE"); t.set_cursor_pos(1, 4); t.clear_dirty(); t.erase_line(EraseLine::Right, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("AB", plain(&t)); } #[test] fn terminal_erase_line_right_protected_attributes_respected_with_iso() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_cursor_pos(1, 1); t.clear_dirty(); t.erase_line(EraseLine::Right, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("ABC", plain(&t)); } #[test] fn terminal_erase_line_right_protected_attributes_ignored_with_dec_most_recent() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_protected_mode(ProtectedMode::Dec); t.set_protected_mode(ProtectedMode::Off); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Right, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("A", plain(&t)); } #[test] fn terminal_erase_line_right_protected_attributes_ignored_with_dec_set() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); pr(&mut t, "ABC"); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Right, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("A", plain(&t)); } #[test] fn terminal_erase_line_right_protected_requested() { let mut t = init(10, 5); pr(&mut t, "12345678"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 4); t.clear_dirty(); t.erase_line(EraseLine::Right, true); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("123 X", plain(&t)); } #[test] fn terminal_erase_line_simple_erase_left() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); t.set_cursor_pos(1, 3); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" DE", plain(&t)); } #[test] fn terminal_erase_line_left_resets_wrap() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); assert!(t.screen().cursor.pending_wrap); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert!(!t.screen().cursor.pending_wrap); t.print(u32::from('B')).unwrap(); assert_eq!(" B", plain(&t)); } #[test] fn terminal_erase_line_left_preserves_background_sgr() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); t.set_cursor_pos(1, 2); t.set_attribute(A::DirectColorBg(red())).unwrap(); t.erase_line(EraseLine::Left, false); assert_eq!(" CDE", plain(&t)); assert_bg_red(&t, 0..2, 0); } #[test] fn terminal_erase_line_left_wide_character() { let mut t = init(10, 5); pr(&mut t, "AB橋DE"); t.set_cursor_pos(1, 3); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" DE", plain(&t)); } #[test] fn terminal_erase_line_left_protected_attributes_respected_with_iso() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_cursor_pos(1, 1); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("ABC", plain(&t)); } #[test] fn terminal_erase_line_left_protected_attributes_ignored_with_dec_most_recent() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_protected_mode(ProtectedMode::Dec); t.set_protected_mode(ProtectedMode::Off); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" C", plain(&t)); } #[test] fn terminal_erase_line_left_protected_attributes_ignored_with_dec_set() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); pr(&mut t, "ABC"); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Left, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" C", plain(&t)); } #[test] fn terminal_erase_line_left_protected_requested() { let mut t = init(10, 5); pr(&mut t, "123456789"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 8); t.clear_dirty(); t.erase_line(EraseLine::Left, true); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" X 9", plain(&t)); } #[test] fn terminal_erase_line_complete_preserves_background_sgr() { let mut t = init(5, 5); pr(&mut t, "ABCDE"); t.set_cursor_pos(1, 2); t.set_attribute(A::DirectColorBg(red())).unwrap(); t.erase_line(EraseLine::Complete, false); assert_eq!("", plain(&t)); assert_bg_red(&t, 0..5, 0); } #[test] fn terminal_erase_line_complete_protected_attributes_respected_with_iso() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_cursor_pos(1, 1); t.clear_dirty(); t.erase_line(EraseLine::Complete, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("ABC", plain(&t)); } #[test] fn terminal_erase_line_complete_protected_attributes_ignored_with_dec_most_recent() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); pr(&mut t, "ABC"); t.set_protected_mode(ProtectedMode::Dec); t.set_protected_mode(ProtectedMode::Off); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Complete, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("", plain(&t)); } #[test] fn terminal_erase_line_complete_protected_attributes_ignored_with_dec_set() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); pr(&mut t, "ABC"); t.set_cursor_pos(1, 2); t.clear_dirty(); t.erase_line(EraseLine::Complete, false); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("", plain(&t)); } #[test] fn terminal_erase_line_complete_protected_requested() { let mut t = init(10, 5); pr(&mut t, "123456789"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 8); t.clear_dirty(); t.erase_line(EraseLine::Complete, true); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" X", plain(&t)); } #[test] fn terminal_tab_clear_single() { let mut t = init(30, 5); t.horizontal_tab(); t.tab_clear(TabClear::Current); assert!(!t.is_dirty(pt_active(0, 0))); t.set_cursor_pos(1, 1); t.horizontal_tab(); assert_eq!(16, t.screen().cursor.x); } #[test] fn terminal_tab_clear_all() { let mut t = init(30, 5); t.tab_clear(TabClear::All); assert!(!t.is_dirty(pt_active(0, 0))); t.set_cursor_pos(1, 1); t.horizontal_tab(); assert_eq!(29, t.screen().cursor.x); } #[test] fn terminal_print_repeat_simple() { let mut t = init(5, 5); t.print_string("A").unwrap(); t.print_repeat(1).unwrap(); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!("AA", plain(&t)); } #[test] fn terminal_print_repeat_wrap() { let mut t = init(5, 5); t.print_string(" A").unwrap(); t.print_repeat(1).unwrap(); assert!(t.is_dirty(pt_active(0, 0))); assert_eq!(" A\nA", plain(&t)); } #[test] fn terminal_print_repeat_no_previous_character() { let mut t = init(5, 5); t.print_repeat(1).unwrap(); assert!(!t.is_dirty(pt_active(0, 0))); assert_eq!("", plain(&t)); } #[test] fn terminal_print_attributes() { let mut t = init(5, 5); let rgb = |r, g, b| Rgb { r, g, b }; { t.set_attribute(A::DirectColorFg(rgb(1, 2, 3))).unwrap(); assert_eq!("0;38:2::1:2:3", t.print_attributes()); t.set_attribute(A::Unset).unwrap(); } { t.set_attribute(A::Bold).unwrap(); t.set_attribute(A::DirectColorBg(rgb(1, 2, 3))).unwrap(); assert_eq!("0;1;48:2::1:2:3", t.print_attributes()); t.set_attribute(A::Unset).unwrap(); } { t.set_attribute(A::Bold).unwrap(); t.set_attribute(A::Faint).unwrap(); t.set_attribute(A::Italic).unwrap(); t.set_attribute(A::Underline(Underline::Single)).unwrap(); t.set_attribute(A::Blink).unwrap(); t.set_attribute(A::Inverse).unwrap(); t.set_attribute(A::Invisible).unwrap(); t.set_attribute(A::Strikethrough).unwrap(); t.set_attribute(A::DirectColorFg(rgb(100, 200, 255))) .unwrap(); t.set_attribute(A::DirectColorBg(rgb(101, 102, 103))) .unwrap(); assert_eq!( "0;1;2;3;4;5;7;8;9;38:2::100:200:255;48:2::101:102:103", t.print_attributes() ); t.set_attribute(A::Unset).unwrap(); } { t.set_attribute(A::Underline(Underline::Single)).unwrap(); assert_eq!("0;4", t.print_attributes()); t.set_attribute(A::Unset).unwrap(); } assert_eq!("0", t.print_attributes()); } #[test] fn terminal_erase_display_simple_erase_below() { let mut t = init(5, 5); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.clear_dirty(); t.erase_display(EraseDisplay::Below, false); assert!(!t.is_dirty(pt_active(0, 0))); assert!(t.is_dirty(pt_active(0, 1))); assert!(t.is_dirty(pt_active(0, 2))); assert_eq!("ABC\nD", plain(&t)); } #[test] fn terminal_erase_display_erase_below_preserves_sgr_bg() { let mut t = init(5, 5); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.set_attribute(A::DirectColorBg(red())).unwrap(); t.erase_display(EraseDisplay::Below, false); assert_eq!("ABC\nD", plain(&t)); assert_bg_red(&t, 1..5, 1); } #[test] fn terminal_erase_display_below_split_multi_cell() { let mut t = init(5, 5); t.print_string("AB橋C").unwrap(); t.carriage_return(); t.linefeed().unwrap(); t.print_string("DE橋F").unwrap(); t.carriage_return(); t.linefeed().unwrap(); t.print_string("GH橋I").unwrap(); t.set_cursor_pos(2, 4); t.erase_display(EraseDisplay::Below, false); assert_eq!("AB橋C\nDE", plain(&t)); } #[test] fn terminal_erase_display_below_protected_attributes_respected_with_iso() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Below, false); assert_eq!("ABC\nDEF\nGHI", plain(&t)); } #[test] fn terminal_erase_display_below_protected_attributes_ignored_with_dec_most_recent() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); abc_def_ghi(&mut t); t.set_protected_mode(ProtectedMode::Dec); t.set_protected_mode(ProtectedMode::Off); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Below, false); assert_eq!("ABC\nD", plain(&t)); } #[test] fn terminal_erase_display_below_protected_attributes_ignored_with_dec_set() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Below, false); assert_eq!("ABC\nD", plain(&t)); } #[test] fn terminal_erase_display_below_protected_attributes_respected_with_force() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Below, true); assert_eq!("ABC\nDEF\nGHI", plain(&t)); } #[test] fn terminal_erase_display_simple_erase_above() { let mut t = init(5, 5); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.clear_dirty(); t.erase_display(EraseDisplay::Above, false); assert!(t.is_dirty(pt_active(0, 0))); assert!(t.is_dirty(pt_active(0, 1))); assert!(!t.is_dirty(pt_active(0, 2))); assert_eq!("\n F\nGHI", plain(&t)); } #[test] fn terminal_erase_display_erase_above_preserves_sgr_bg() { let mut t = init(5, 5); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.set_attribute(A::DirectColorBg(red())).unwrap(); t.erase_display(EraseDisplay::Above, false); assert_eq!("\n F\nGHI", plain(&t)); assert_bg_red(&t, 0..2, 1); } #[test] fn terminal_erase_display_above_split_multi_cell() { let mut t = init(5, 5); t.print_string("AB橋C").unwrap(); t.carriage_return(); t.linefeed().unwrap(); t.print_string("DE橋F").unwrap(); t.carriage_return(); t.linefeed().unwrap(); t.print_string("GH橋I").unwrap(); t.set_cursor_pos(2, 3); t.erase_display(EraseDisplay::Above, false); assert_eq!("\n F\nGH橋I", plain(&t)); } #[test] fn terminal_erase_display_above_protected_attributes_respected_with_iso() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Above, false); assert_eq!("ABC\nDEF\nGHI", plain(&t)); } #[test] fn terminal_erase_display_above_protected_attributes_ignored_with_dec_most_recent() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Iso); abc_def_ghi(&mut t); t.set_protected_mode(ProtectedMode::Dec); t.set_protected_mode(ProtectedMode::Off); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Above, false); assert_eq!("\n F\nGHI", plain(&t)); } #[test] fn terminal_erase_display_above_protected_attributes_ignored_with_dec_set() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Above, false); assert_eq!("\n F\nGHI", plain(&t)); } #[test] fn terminal_erase_display_above_protected_attributes_respected_with_force() { let mut t = init(5, 5); t.set_protected_mode(ProtectedMode::Dec); abc_def_ghi(&mut t); t.set_cursor_pos(2, 2); t.erase_display(EraseDisplay::Above, true); assert_eq!("ABC\nDEF\nGHI", plain(&t)); } #[test] fn terminal_erase_display_protected_complete() { let mut t = init(10, 5); t.print(u32::from('A')).unwrap(); t.carriage_return(); t.linefeed().unwrap(); pr(&mut t, "123456789"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 4); t.clear_dirty(); t.erase_display(EraseDisplay::Complete, true); for y in 0..u32::from(t.rows) { assert!(t.is_dirty(pt_active(0, y))); } assert_eq!("\n X", plain(&t)); } #[test] fn terminal_erase_display_protected_below() { let mut t = init(10, 5); t.print(u32::from('A')).unwrap(); t.carriage_return(); t.linefeed().unwrap(); pr(&mut t, "123456789"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 4); t.erase_display(EraseDisplay::Below, true); assert_eq!("A\n123 X", plain(&t)); } #[test] fn terminal_erase_display_scroll_complete() { let mut t = init(10, 5); t.print(u32::from('A')).unwrap(); t.carriage_return(); t.linefeed().unwrap(); t.erase_display(EraseDisplay::ScrollComplete, false); assert_eq!("", plain(&t)); } #[test] fn terminal_erase_display_protected_above() { let mut t = init(10, 3); t.print(u32::from('A')).unwrap(); t.carriage_return(); t.linefeed().unwrap(); pr(&mut t, "123456789"); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 6); t.set_protected_mode(ProtectedMode::Dec); t.print(u32::from('X')).unwrap(); let y = usize::from(t.screen().cursor.y); t.set_cursor_pos(y + 1, 8); t.erase_display(EraseDisplay::Above, true); assert_eq!("\n X 9", plain(&t)); } #[test] fn terminal_erase_display_complete_preserves_cursor() { let mut t = init(5, 5); // Set our cursor t.set_attribute(A::Bold).unwrap(); t.print_string("AAAA").unwrap(); assert_ne!(style::DEFAULT_ID, t.screen().cursor.style_id); // Erasing the display may detect that our style is no longer in use // and prune our style, which we don't want because its still our // active cursor. t.erase_display(EraseDisplay::Complete, false); assert_ne!(style::DEFAULT_ID, t.screen().cursor.style_id); } #[test] fn terminal_semantic_prompt() { let mut t = init(10, 5); // Prompt t.semantic_prompt(&Command::init(PA::FreshLineNewPrompt)) .unwrap(); pr(&mut t, "hello"); assert_eq!(0, t.screen().cursor.y); assert_eq!(5, t.screen().cursor.x); { let pt = pt_active(t.screen().cursor.x - 1, u32::from(t.screen().cursor.y)); assert_eq!(SemanticContent::Prompt, cell_at(&t, pt).semantic_content()); assert_eq!(RowPrompt::Prompt, row_at(&t, pt).semantic_prompt()); } // Start input but end it on EOL t.semantic_prompt(&Command::init(PA::EndPromptStartInputTerminateEol)) .unwrap(); t.carriage_return(); t.linefeed().unwrap(); // Write some output assert_eq!(1, t.screen().cursor.y); assert_eq!(0, t.screen().cursor.x); pr(&mut t, "world"); { let pt = pt_active(t.screen().cursor.x - 1, u32::from(t.screen().cursor.y)); assert_eq!(SemanticContent::Output, cell_at(&t, pt).semantic_content()); assert_eq!(RowPrompt::None, row_at(&t, pt).semantic_prompt()); } }