mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
* shared instance plugins * reduce needless allocations and share memory * fix: read buffer bug * idle plugin fixes and optimizations * trim allocator for glibc on pane close * fix flaky integration tests * refactor: naming * rustfmt * add pr
9566 lines
321 KiB
Rust
9566 lines
321 KiB
Rust
use super::super::Grid;
|
||
use crate::panes::grid::SixelImageStore;
|
||
use crate::panes::kitty_graphics::KittyImageStore;
|
||
use crate::panes::link_handler::LinkHandler;
|
||
use base64::engine::general_purpose::STANDARD as BASE64_ENCODER;
|
||
use base64::engine::Engine as _;
|
||
use insta::assert_snapshot;
|
||
use std::cell::RefCell;
|
||
use std::collections::HashMap;
|
||
use std::rc::Rc;
|
||
use vte;
|
||
use zellij_utils::consts::SCROLL_BUFFER_SIZE;
|
||
use zellij_utils::{
|
||
data::{Palette, Style},
|
||
input::options::DEFAULT_WORD_SEPARATORS,
|
||
pane_size::SizeInPixels,
|
||
position::Position,
|
||
};
|
||
|
||
use std::fmt::Write;
|
||
|
||
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||
let mut path_to_file = std::path::PathBuf::new();
|
||
path_to_file.push("../src");
|
||
path_to_file.push("tests");
|
||
path_to_file.push("fixtures");
|
||
path_to_file.push(fixture_name);
|
||
std::fs::read(path_to_file)
|
||
.unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_0() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-0";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_1() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-1";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_2() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-2";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_3() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-3";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_4() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-4";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest1_5() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest1-5";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_0() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-0";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_1() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-1";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_2() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-2";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_3() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-3";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_4() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-4";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_5() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-5";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_6() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-6";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_7() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-7";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_8() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-8";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_9() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-9";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_10() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-10";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_11() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-11";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_12() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-12";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_13() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-13";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest2_14() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest2-14";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest3_0() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest3-0";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_0() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-0";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_1() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-1";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_2() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-2";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_3() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-3";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_4() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-4";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn vttest8_5() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vttest8-5";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn csi_b() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "csi-b";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn csi_capital_i() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "csi-capital-i";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn csi_capital_z() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "csi-capital-z";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn terminal_reports() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "terminal_reports";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid.pending_messages_to_pty));
|
||
}
|
||
|
||
#[test]
|
||
fn wide_characters() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn wide_characters_line_wrap() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_line_wrap";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn insert_character_in_line_with_wide_character() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_middle_line_insert";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn delete_char_in_middle_of_line_with_widechar() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide-chars-delete-middle";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn delete_char_in_middle_of_line_with_multiple_widechars() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide-chars-delete-middle-after-multi";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn fish_wide_characters_override_clock() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fish_wide_characters_override_clock";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn bash_delete_wide_characters() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "bash_delete_wide_characters";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn delete_wide_characters_before_cursor() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "delete_wide_characters_before_cursor";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "delete_wide_characters_before_cursor_when_cursor_is_on_wide_character";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn delete_wide_character_under_cursor() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "delete_wide_character_under_cursor";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn replace_wide_character_under_cursor() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
104,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "replace_wide_character_under_cursor";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_wide_characters() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
90,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_full";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_wide_characters_on_size_change() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
93,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_full";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.change_size(21, 90);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn unwrap_wide_characters_on_size_change() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
93,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_full";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.change_size(21, 90);
|
||
grid.change_size(21, 93);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_wide_characters_in_the_middle_of_the_line() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
91,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_line_middle";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_wide_characters_at_the_end_of_the_line() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
90,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "wide_characters_line_end";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn copy_selected_text_from_viewport() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
27,
|
||
125,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "grid_copy";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
|
||
grid.start_selection(&Position::new(23, 6));
|
||
// check for widechar, 📦 occupies columns 34, 35, and gets selected even if only the first column is selected
|
||
grid.end_selection(&Position::new(25, 35));
|
||
let text = grid.get_selected_text();
|
||
assert_eq!(
|
||
text.unwrap(),
|
||
"mauris in aliquam sem fringilla.\n\nzellij on mouse-support [?] is 📦"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn copy_wrapped_selected_text_from_viewport() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
22,
|
||
73,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "grid_copy_wrapped";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
|
||
grid.start_selection(&Position::new(5, 0));
|
||
grid.end_selection(&Position::new(8, 42));
|
||
let text = grid.get_selected_text();
|
||
assert_eq!(
|
||
text.unwrap(),
|
||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn copy_selected_text_from_lines_above() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
27,
|
||
125,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "grid_copy";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
|
||
grid.start_selection(&Position::new(-2, 10));
|
||
// check for widechar, 📦 occupies columns 34, 35, and gets selected even if only the first column is selected
|
||
grid.end_selection(&Position::new(2, 8));
|
||
let text = grid.get_selected_text();
|
||
assert_eq!(
|
||
text.unwrap(),
|
||
"eu scelerisque felis imperdiet proin fermentum leo.\nCursus risus at ultrices mi tempus.\nLaoreet id donec ultrices tincidunt arcu non sodales.\nAmet dictum sit amet justo donec enim.\nHac habi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn copy_selected_text_from_lines_below() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
27,
|
||
125,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "grid_copy";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
|
||
grid.move_viewport_up(40);
|
||
|
||
grid.start_selection(&Position::new(63, 6));
|
||
// check for widechar, 📦 occupies columns 34, 35, and gets selected even if only the first column is selected
|
||
grid.end_selection(&Position::new(65, 35));
|
||
let text = grid.get_selected_text();
|
||
assert_eq!(
|
||
text.unwrap(),
|
||
"mauris in aliquam sem fringilla.\n\nzellij on mouse-support [?] is 📦"
|
||
);
|
||
}
|
||
|
||
/*
|
||
* These tests below are general compatibility tests for non-trivial scenarios running in the terminal.
|
||
* They use fake TTY input replicated from these scenarios.
|
||
*
|
||
*/
|
||
|
||
#[test]
|
||
fn run_bandwhich_from_fish_shell() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fish_and_bandwhich";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn fish_tab_completion_options() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fish_tab_completion_options";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn fish_select_tab_completion_options() {
|
||
// the difference between this and the previous test is that here we press <TAB>
|
||
// twice, meaning the selection moves between the options and the command line
|
||
// changes.
|
||
// this is not clearly seen in the snapshot because it does not include styles,
|
||
// but we can see the command line change and the cursor staying in place
|
||
// terminal_emulator_color_codes,
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fish_select_tab_completion_options";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn vim_scroll_region_down() {
|
||
// here we test a case where vim defines the scroll region as lesser than the screen row count
|
||
// and then scrolls down
|
||
// the region is defined here by vim as 1-26 (there are 28 rows)
|
||
// then the cursor is moved to line 26 and a new line is added
|
||
// what should happen is that the first line in the scroll region (1) is deleted
|
||
// terminal_emulator_color_codes,
|
||
// and an empty line is inserted in the last scroll region line (26)
|
||
// this tests also has other steps afterwards that fills the line with the next line in the
|
||
// sixel_image_store,
|
||
// file
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vim_scroll_region_down";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn vim_ctrl_d() {
|
||
// in vim ctrl-d moves down half a page
|
||
// in this case, it sends the terminal the csi 'M' directive, which tells it to delete X (13 in
|
||
// this case) lines inside the scroll region and push the other lines up
|
||
// what happens here is that 13 lines are deleted and instead 13 empty lines are added at the
|
||
// end of the scroll region
|
||
// terminal_emulator_color_codes,
|
||
// vim makes sure to fill these empty lines with the rest of the file
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vim_ctrl_d";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn vim_ctrl_u() {
|
||
// in vim ctrl-u moves up half a page
|
||
// in this case, it sends the terminal the csi 'L' directive, which tells it to insert X (13 in
|
||
// this case) lines at the cursor, pushing away (deleting) the last line in the scroll region
|
||
// this causes the effect of scrolling up X lines (vim replaces the lines with the ones in the
|
||
// file above the current content)
|
||
// terminal_emulator_color_codes,
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vim_ctrl_u";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn htop() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "htop";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn htop_scrolling() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "htop_scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn htop_right_scrolling() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "htop_right_scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn vim_overwrite() {
|
||
// this tests the vim overwrite message
|
||
// to recreate:
|
||
// * open a file in vim
|
||
// * open the same file in another window
|
||
// * change the file in the other window and save
|
||
// terminal_emulator_color_codes,
|
||
// * change the file in the original vim window and save
|
||
// * confirm you would like to change the file by pressing 'y' and then ENTER
|
||
// sixel_image_store,
|
||
// * if everything looks fine, this test passed :)
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "vim_overwrite";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn clear_scroll_region() {
|
||
// this is actually a test of 1049h/l (alternative buffer)
|
||
// @imsnif - the name is a monument to the time I didn't fully understand this mechanism :)
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "clear_scroll_region";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn display_tab_characters_properly() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "tab_characters";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn neovim_insert_mode() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "nvim_insert";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn bash_cursor_linewrap() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
116,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "bash_cursor_linewrap";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn fish_paste_multiline() {
|
||
// here we paste a multiline command in fish shell, making sure we support it
|
||
// going up and changing the colors of our line-wrapped pasted text
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
149,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fish_paste_multiline";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn git_log() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
149,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "git_log";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn git_diff_scrollup() {
|
||
// this tests makes sure that when we have a git diff that exceeds the screen size
|
||
// we are able to scroll up
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
28,
|
||
149,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "git_diff_scrollup";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn emacs_longbuf() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
60,
|
||
284,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "emacs_longbuf_tutorial";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn top_and_quit() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
56,
|
||
235,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "top_and_quit";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn exa_plus_omf_theme() {
|
||
// this tests that we handle a tab delimited table properly
|
||
// without overriding the previous content
|
||
// this is a potential bug because the \t character is a goto
|
||
// if we forwarded it as is to the terminal, we would be skipping
|
||
// over existing on-screen content without deleting it, so we must
|
||
// terminal_emulator_color_codes,
|
||
// convert it to spaces
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
56,
|
||
235,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "exa_plus_omf_theme";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_up() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
50,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.scroll_up_one_line();
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_down() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
50,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.scroll_up_one_line();
|
||
grid.scroll_down_one_line();
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_up_with_line_wraps() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
25,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.scroll_up_one_line();
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_down_with_line_wraps() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
25,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.scroll_up_one_line();
|
||
grid.scroll_down_one_line();
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_up_decrease_width_and_scroll_down() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
50,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
for _ in 0..10 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
grid.change_size(10, 25);
|
||
for _ in 0..10 {
|
||
grid.scroll_down_one_line();
|
||
}
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_up_increase_width_and_scroll_down() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
25,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scrolling";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
for _ in 0..10 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
grid.change_size(10, 50);
|
||
for _ in 0..10 {
|
||
grid.scroll_down_one_line();
|
||
}
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn saved_cursor_across_resize() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
4,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let mut parse = |s, grid: &mut Grid| {
|
||
vte_parser.advance(&mut *grid, &Vec::from(s));
|
||
};
|
||
let content = "
|
||
\rLine 1 >fill to 20_<
|
||
\rLine 2 >fill to 20_<
|
||
\rLine 3 >fill to 20_<
|
||
\rL\u{1b}[sine 4 >fill to 20_<";
|
||
parse(content, &mut grid);
|
||
// Move real cursor position up three lines
|
||
let content = "\u{1b}[3A";
|
||
parse(content, &mut grid);
|
||
// Truncate top of terminal, resetting cursor (but not saved cursor)
|
||
grid.change_size(3, 20);
|
||
// Wrap, resetting cursor again (but not saved cursor)
|
||
grid.change_size(3, 10);
|
||
// Restore saved cursor position and write ZZZ
|
||
let content = "\u{1b}[uZZZ";
|
||
parse(content, &mut grid);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn saved_cursor_across_resize_longline() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
4,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let mut parse = |s, grid: &mut Grid| {
|
||
vte_parser.advance(&mut *grid, &Vec::from(s));
|
||
};
|
||
let content = "
|
||
\rLine 1 >fill \u{1b}[sto 20_<";
|
||
parse(content, &mut grid);
|
||
// Wrap each line precisely halfway
|
||
grid.change_size(4, 10);
|
||
// Write 'YY' at the end (ends up on a new wrapped line), restore to the saved cursor
|
||
// and overwrite 'to' with 'ZZ'
|
||
let content = "YY\u{1b}[uZZ";
|
||
parse(content, &mut grid);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn saved_cursor_across_resize_rewrap() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
4,
|
||
4 * 8,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let mut parse = |s, grid: &mut Grid| {
|
||
vte_parser.advance(&mut *grid, &Vec::from(s));
|
||
};
|
||
let content = "
|
||
\r12345678123456781234567\u{1b}[s812345678"; // 4*8 chars
|
||
parse(content, &mut grid);
|
||
// Wrap each line precisely halfway, then rewrap to halve them again
|
||
grid.change_size(4, 16);
|
||
grid.change_size(4, 8);
|
||
// Write 'Z' at the end of line 3
|
||
let content = "\u{1b}[uZ";
|
||
parse(content, &mut grid);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn move_cursor_below_scroll_region() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
34,
|
||
114,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "move_cursor_below_scroll_region";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn insert_wide_characters_in_existing_line() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
21,
|
||
86,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "chinese_characters_line_middle";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn full_screen_scroll_region_and_scroll_up() {
|
||
// this test is a regression test for a bug
|
||
// where the scroll region would be set to the
|
||
// full viewport and then scrolling up would cause
|
||
// lines to get deleted from the viewport rather
|
||
// than moving to "lines_above"
|
||
// terminal_emulator_color_codes,
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
54,
|
||
80,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scroll_region_full_screen";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid.scroll_up_one_line();
|
||
grid.scroll_up_one_line();
|
||
grid.scroll_up_one_line();
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn ring_bell() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
134,
|
||
64,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "ring_bell";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert!(grid.ring_bell);
|
||
}
|
||
|
||
#[test]
|
||
pub fn alternate_screen_change_size() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
20,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "alternate_screen_change_size";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
// no scrollback in alternate screen
|
||
assert_eq!(grid.scrollback_position_and_length(), (0, 0));
|
||
grid.change_size(10, 10);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
assert_eq!(grid.scrollback_position_and_length(), (0, 0))
|
||
}
|
||
|
||
#[test]
|
||
pub fn fzf_fullscreen() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "fzf_fullscreen";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn replace_multiple_wide_characters_under_cursor() {
|
||
// this test makes sure that if we replace a wide character with a non-wide character, it
|
||
// properly pads the excess width in the proper place (either before the replaced non-wide
|
||
// character if the cursor was "in the middle" of the wide character, or after the character if
|
||
// it was "in the beginning" of the wide character)
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "replace_multiple_wide_characters";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn replace_non_wide_characters_with_wide_characters() {
|
||
// this test makes sure that if we replace a wide character with a non-wide character, it
|
||
// properly pads the excess width in the proper place (either before the replaced non-wide
|
||
// character if the cursor was "in the middle" of the wide character, or after the character if
|
||
// it was "in the beginning" of the wide character)
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "replace_non_wide_characters_with_wide_characters";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn scroll_down_ansi() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "scroll_down";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn ansi_capital_t() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "foo\u{1b}[14Tbar".as_bytes();
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn ansi_capital_s() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfoo\u{1b}[14Sbar".as_bytes();
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn terminal_pixel_size_reports() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(Some(SizeInPixels {
|
||
height: 21,
|
||
width: 8,
|
||
}))),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
osc8_hyperlinks,
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "terminal_pixel_size_reports";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
// CSI 14t and CSI 16t are forwarded to the host; Zellij no longer
|
||
// synthesises local replies from character_cell_size for these.
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
use crate::host_query::HostQuery;
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries,
|
||
vec![
|
||
HostQuery::TextAreaPixelSize,
|
||
HostQuery::CharacterCellPixelSize
|
||
],
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn terminal_pixel_size_reports_in_unsupported_terminals() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)), // in an unsupported terminal, we don't have this info
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "terminal_pixel_size_reports";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
// Forwarding is independent of character_cell_size availability —
|
||
// the host terminal is authoritative for these queries regardless
|
||
// of what Zellij knows locally.
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
use crate::host_query::HostQuery;
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries,
|
||
vec![
|
||
HostQuery::TextAreaPixelSize,
|
||
HostQuery::CharacterCellPixelSize
|
||
],
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
pub fn ansi_csi_at_sign() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "foo\u{1b}[2D\u{1b}[2@".as_bytes();
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn sixel_images_are_reaped_when_scrolled_off() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store.clone(),
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let pane_content = read_fixture("sixel-image-500px.six");
|
||
vte_parser.advance(&mut grid, &pane_content);
|
||
for _ in 0..10_051 {
|
||
// scrollbuffer limit + viewport height
|
||
grid.add_canonical_line();
|
||
}
|
||
let _ = grid.read_changes(0, 0); // we do this because this is where the images are reaped
|
||
assert_eq!(
|
||
sixel_image_store.borrow().image_count(),
|
||
0,
|
||
"all images were deleted from the store"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn sixel_replacement_reaps_covered_images_without_reusing_live_ids() {
|
||
let (mut grid, _) = new_kitty_grid(10, 25);
|
||
let mut parser = vte::Parser::new();
|
||
for step in 0..18u8 {
|
||
let width = if step < 12 { 20 } else { 10 + (step % 3) * 10 };
|
||
if step >= 12 {
|
||
parser.advance(&mut grid, b"\x1b[3;4H ");
|
||
}
|
||
let bytes = format!(
|
||
"\u{1b}[3;4H\u{1b}P9;1q\"1;1;{};12#1;2;{};0;0#1!{}~-!{}~\u{1b}\\",
|
||
width,
|
||
step * 5,
|
||
width,
|
||
width
|
||
);
|
||
parser.advance(&mut grid, bytes.as_bytes());
|
||
grid.read_changes(0, 0);
|
||
let coordinates: Vec<_> = grid.sixel_grid.image_coordinates().collect();
|
||
assert_eq!(coordinates.len(), 1, "obsolete image at step {}", step);
|
||
let (id, rect) = coordinates[0];
|
||
let mut store = grid.sixel_grid.sixel_image_store.borrow_mut();
|
||
assert_eq!(store.image_count(), 1, "storage after step {}", step);
|
||
let serialized = store
|
||
.serialize_image(id, 0, 0, rect.width, rect.height)
|
||
.expect("replacement image survives reaping");
|
||
let decoded = sixel_image::SixelImage::new(serialized.as_bytes()).unwrap();
|
||
assert!(decoded.pixels.iter().flatten().all(|pixel| {
|
||
pixel.on
|
||
&& decoded.color_registers[&pixel.color]
|
||
== sixel_image::SixelColor::Rgb(step * 5, 0, 0)
|
||
}));
|
||
}
|
||
parser.advance(&mut grid, b"\x1b[3;4H ");
|
||
let (_, sixel_chunks, _) = grid.read_changes(0, 0);
|
||
assert!(
|
||
sixel_chunks.is_empty(),
|
||
"erased rasters must not be replayed"
|
||
);
|
||
assert_eq!(grid.sixel_grid.sixel_image_store.borrow().image_count(), 0);
|
||
parser.advance(&mut grid, b"\x1b[3;4H\x1bP0;0q\"1;1;20;12?\x1b\\");
|
||
let (_, sixel_chunks, _) = grid.read_changes(0, 0);
|
||
assert_eq!(sixel_chunks.len(), 1);
|
||
}
|
||
|
||
#[test]
|
||
pub fn sixel_images_are_reaped_when_resetting() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store.clone(),
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let pane_content = read_fixture("sixel-image-500px.six");
|
||
vte_parser.advance(&mut grid, &pane_content);
|
||
grid.reset_terminal_state();
|
||
let _ = grid.read_changes(0, 0); // we do this because this is where the images are reaped
|
||
assert_eq!(
|
||
sixel_image_store.borrow().image_count(),
|
||
0,
|
||
"all images were deleted from the store"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
pub fn sixel_image_in_alternate_buffer() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
30,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store.clone(),
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
|
||
let move_to_alternate_screen = "\u{1b}[?1049h";
|
||
vte_parser.advance(&mut grid, move_to_alternate_screen.as_bytes());
|
||
|
||
let pane_content = read_fixture("sixel-image-500px.six");
|
||
vte_parser.advance(&mut grid, &pane_content);
|
||
assert_snapshot!(format!("{:?}", grid)); // should include the image
|
||
//
|
||
let move_away_from_alternate_screen = "\u{1b}[?1049l";
|
||
vte_parser.advance(&mut grid, move_away_from_alternate_screen.as_bytes());
|
||
assert_snapshot!(format!("{:?}", grid)); // should note include the image
|
||
assert_eq!(
|
||
sixel_image_store.borrow().image_count(),
|
||
0,
|
||
"all images were deleted from the store when we moved back from alternate screen"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
pub fn sixel_with_image_scrolling_decsdm() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
30,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
|
||
// enter DECSDM
|
||
let move_to_decsdm = "\u{1b}[?80h";
|
||
vte_parser.advance(&mut grid, move_to_decsdm.as_bytes());
|
||
|
||
// write some text
|
||
let mut text_to_fill_pane = String::new();
|
||
for i in 0..10 {
|
||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||
}
|
||
vte_parser.advance(&mut grid, text_to_fill_pane.as_bytes());
|
||
|
||
// render a sixel image (will appear on the top left and partially cover the text)
|
||
let pane_content = read_fixture("sixel-image-100px.six");
|
||
vte_parser.advance(&mut grid, &pane_content);
|
||
// image should be on the top left corner of the grid
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
|
||
// leave DECSDM
|
||
let move_away_from_decsdm = "\u{1b}[?80l";
|
||
vte_parser.advance(&mut grid, move_away_from_decsdm.as_bytes());
|
||
|
||
// Go down to the beginning of the next line
|
||
let mut go_down_once = String::new();
|
||
writeln!(&mut go_down_once, "\n\r").unwrap();
|
||
vte_parser.advance(&mut grid, go_down_once.as_bytes());
|
||
|
||
// render another sixel image, should appear under the cursor
|
||
let pane_content = read_fixture("sixel-image-100px.six");
|
||
vte_parser.advance(&mut grid, &pane_content);
|
||
|
||
// image should appear in cursor position
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
pub fn osc_4_background_query() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}]10;?\u{1b}\\";
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
// Post-refactor: OSC 10;? is forwarded to the host, not answered
|
||
// from Zellij's cached palette. pending_messages_to_pty must stay
|
||
// empty.
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
let forwarded_string: String = grid
|
||
.pending_forwarded_queries
|
||
.iter()
|
||
.map(|q| String::from_utf8(q.to_query_bytes()).unwrap())
|
||
.collect();
|
||
assert_eq!(forwarded_string, "\u{1b}]10;?\u{1b}\\");
|
||
}
|
||
|
||
#[test]
|
||
pub fn osc_4_foreground_query() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}]11;?\u{1b}\\";
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
let forwarded_string: String = grid
|
||
.pending_forwarded_queries
|
||
.iter()
|
||
.map(|q| String::from_utf8(q.to_query_bytes()).unwrap())
|
||
.collect();
|
||
assert_eq!(forwarded_string, "\u{1b}]11;?\u{1b}\\");
|
||
}
|
||
|
||
#[test]
|
||
pub fn osc_4_color_query() {
|
||
let mut color_codes = HashMap::new();
|
||
color_codes.insert(222, String::from("rgb:ffff/d7d7/8787"));
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(color_codes));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}]4;222;?\u{1b}\\";
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
// OSC 4;N;? is forwarded to the host for the real palette value.
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
let forwarded_string: String = grid
|
||
.pending_forwarded_queries
|
||
.iter()
|
||
.map(|q| String::from_utf8(q.to_query_bytes()).unwrap())
|
||
.collect();
|
||
assert_eq!(forwarded_string, "\u{1b}]4;222;?\u{1b}\\");
|
||
}
|
||
|
||
#[test]
|
||
pub fn xtsmgraphics_color_register_count() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}[?1;1;S\u{1b}\\";
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
let message_string = grid
|
||
.pending_messages_to_pty
|
||
.iter()
|
||
.map(|m| String::from_utf8_lossy(m))
|
||
.fold(String::new(), |mut acc, s| {
|
||
acc.push_str(&s);
|
||
acc
|
||
});
|
||
assert_eq!(message_string, "\u{1b}[?1;0;65536S");
|
||
}
|
||
|
||
#[test]
|
||
pub fn xtsmgraphics_pixel_graphics_geometry() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
51,
|
||
97,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}[?2;1;S\u{1b}\\";
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
let message_string = grid
|
||
.pending_messages_to_pty
|
||
.iter()
|
||
.map(|m| String::from_utf8_lossy(m))
|
||
.fold(String::new(), |mut acc, s| {
|
||
acc.push_str(&s);
|
||
acc
|
||
});
|
||
assert_eq!(message_string, "\u{1b}[?2;0;776;1071S");
|
||
}
|
||
|
||
#[test]
|
||
pub fn cursor_hide_persists_through_alternate_screen() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 21,
|
||
})));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
30,
|
||
112,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
character_cell_size,
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
|
||
let hide_cursor = "\u{1b}[?25l";
|
||
vte_parser.advance(&mut grid, hide_cursor.as_bytes());
|
||
assert!(
|
||
matches!(grid.cursor_coordinates(), Some((_, _, false))),
|
||
"Cursor hidden properly"
|
||
);
|
||
|
||
let move_to_alternate_screen = "\u{1b}[?1049h";
|
||
vte_parser.advance(&mut grid, move_to_alternate_screen.as_bytes());
|
||
assert!(
|
||
matches!(grid.cursor_coordinates(), Some((_, _, false))),
|
||
"Cursor still hidden in alternate screen"
|
||
);
|
||
|
||
let show_cursor = "\u{1b}[?25h";
|
||
vte_parser.advance(&mut grid, show_cursor.as_bytes());
|
||
assert!(
|
||
matches!(grid.cursor_coordinates(), Some((_, _, true))),
|
||
"Cursor shown"
|
||
);
|
||
|
||
let move_away_from_alternate_screen = "\u{1b}[?1049l";
|
||
vte_parser.advance(&mut grid, move_away_from_alternate_screen.as_bytes());
|
||
assert!(
|
||
matches!(grid.cursor_coordinates(), Some((_, _, true))),
|
||
"Cursor still shown away from alternate screen"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn table_ui_component() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "table-ui-component";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn table_ui_component_with_coordinates() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "table-ui-component-with-coordinates";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn ribbon_ui_component() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "ribbon-ui-component";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn ribbon_ui_component_with_coordinates() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "ribbon-ui-component-with-coordinates";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn nested_list_ui_component() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
120,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "nested-list-ui-component";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn nested_list_ui_component_with_coordinates() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
120,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "nested-list-ui-component-with-coordinates";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn text_ui_component() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
120,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "text-ui-component";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn text_ui_component_with_coordinates() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
120,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let fixture_name = "text-ui-component-with-coordinates";
|
||
let content = read_fixture(fixture_name);
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn cannot_escape_scroll_region() {
|
||
// this tests a fix for a bug where it would be possible to set the scroll region bounds beyond
|
||
// the pane height, which would then allow a goto instruction beyond the scroll region to scape
|
||
// the pane bounds and render content on other panes
|
||
//
|
||
// what we do here is set the scroll region beyond the terminal bounds (`<ESC>[1;42r` - whereas
|
||
// the terminal is just 41 lines high), and then issue a goto instruction to line 42, one line
|
||
// beyond the pane and scroll region bounds (`<ESC>[42;1H`) and then print text `Hi there!`.
|
||
// This should be printed on the last line (zero indexed 40) of the terminal and not beyond it.
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
41,
|
||
120,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
let content = "\u{1b}[1;42r\u{1b}[42;1HHi there!".as_bytes();
|
||
vte_parser.advance(&mut grid, &content);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn preserve_background_color_on_resize() {
|
||
use crate::panes::terminal_character::{AnsiCode, EMPTY_TERMINAL_CHARACTER};
|
||
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let debug = false;
|
||
let arrow_fonts = true;
|
||
let styled_underlines = true;
|
||
let osc8_hyperlinks = true;
|
||
let explicitly_disable_kitty_keyboard_protocol = false;
|
||
let mut grid = Grid::new(
|
||
10,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
debug,
|
||
arrow_fonts,
|
||
styled_underlines,
|
||
osc8_hyperlinks,
|
||
explicitly_disable_kitty_keyboard_protocol,
|
||
);
|
||
|
||
let mut parse = |s, grid: &mut Grid| {
|
||
vte_parser.advance(&mut *grid, &Vec::from(s));
|
||
};
|
||
|
||
// Write text with red background that extends to end of line
|
||
// ESC[41m = red background
|
||
// ESC[K = clear to end of line (fills with current background)
|
||
// ESC[0m = reset
|
||
let content = "test\x1b[41m\x1b[K\x1b[0m";
|
||
parse(content, &mut grid);
|
||
|
||
// Check that characters after "test" have red background before resize
|
||
let first_row = &grid.viewport[0];
|
||
let background_char_count_before = first_row
|
||
.columns
|
||
.iter()
|
||
.enumerate()
|
||
.filter(|(i, c)| *i >= 4 && c.styles.background != Some(AnsiCode::Reset))
|
||
.count();
|
||
assert!(
|
||
background_char_count_before > 0,
|
||
"Should have characters with background color before resize"
|
||
);
|
||
|
||
// Also check that plain trailing spaces are properly trimmed (regression test)
|
||
let content2 = "\r\n\rplain text with spaces ";
|
||
parse(content2, &mut grid);
|
||
|
||
// Resize the grid
|
||
grid.change_size(10, 30);
|
||
|
||
// Check that the background color is preserved after resize
|
||
let first_row = &grid.viewport[0];
|
||
let background_char_count_after = first_row
|
||
.columns
|
||
.iter()
|
||
.enumerate()
|
||
.filter(|(i, c)| *i >= 4 && c.styles.background != Some(AnsiCode::Reset))
|
||
.count();
|
||
assert_eq!(
|
||
background_char_count_before, background_char_count_after,
|
||
"Background colored characters should be preserved after resize"
|
||
);
|
||
|
||
// Verify that the second line doesn't have excessive trailing spaces
|
||
// (it should be trimmed since they're plain spaces without background color)
|
||
let second_row = &grid.viewport[1];
|
||
let trailing_spaces = second_row
|
||
.columns
|
||
.iter()
|
||
.rev()
|
||
.take_while(|c| c.character == EMPTY_TERMINAL_CHARACTER.character)
|
||
.count();
|
||
// All trailing plain spaces should be completely removed
|
||
assert_eq!(
|
||
trailing_spaces, 0,
|
||
"Plain trailing spaces should be completely trimmed, but found {} trailing spaces",
|
||
trailing_spaces
|
||
);
|
||
}
|
||
|
||
fn create_grid_with_content(content: &str) -> Grid {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
20,
|
||
80,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
grid
|
||
}
|
||
|
||
fn select_with_click_count(grid: &mut Grid, position: Position, click_count: usize) -> String {
|
||
for _ in 0..click_count {
|
||
grid.start_selection(&position);
|
||
}
|
||
grid.end_selection(&position);
|
||
grid.get_selected_text().unwrap()
|
||
}
|
||
|
||
fn viewport_position_of(grid: &Grid, needle: &str) -> Position {
|
||
grid.viewport
|
||
.iter()
|
||
.enumerate()
|
||
.find_map(|(line, row)| {
|
||
let text: String = row.columns.iter().map(|c| c.character).collect();
|
||
text.find(needle)
|
||
.map(|column| Position::new(line as i32, column as u16))
|
||
})
|
||
.unwrap_or_else(|| panic!("{needle:?} not found in viewport"))
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_a_b_c_d_selects_command_and_output_without_prompt() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07echo hi\x1b]133;C\x07\r\nhi\x1b]133;D;0\x07 suffix",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(1, 0), 3),
|
||
"echo hi\nhi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_p_i_aliases_select_command_and_output_without_prompt() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;P\x07> \x1b]133;I\x07pwd\x1b]133;C\x07\r\n/tmp\x1b]133;D\x07",
|
||
);
|
||
|
||
let position = viewport_position_of(&grid, "/tmp");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), "pwd\n/tmp");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_input_marker_survives_ghostty_prompt_clear() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A;cl=line\x07$ \x1b]133;B\x07\x1b[Kecho hi\r\n\x1b]133;C\x07hi\r\n\x1b]133;D;0\x07\r\x1b[J\x1b]133;A;cl=line\x07$ \x1b]133;B\x07\x1b[Knext",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(1, 0), 3),
|
||
"echo hi\nhi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_triple_click_outside_output_keeps_logical_line_selection() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07echo hi\x1b]133;C\x07 output\x1b]133;D\x07",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 0), 3),
|
||
"$ echo hi output"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_double_click_keeps_word_selection() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07echo\x1b]133;C\x07 result word\x1b]133;D\x07",
|
||
);
|
||
|
||
let position = viewport_position_of(&grid, "word");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 2), "word");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_wrapped_command_and_output_are_selected_together() {
|
||
let mut grid = create_grid_with_size_and_raw(
|
||
4,
|
||
8,
|
||
b"\x1b]133;A\x07$ \x1b]133;B\x07longcmd\x1b]133;C\x07-output\x1b]133;D\x07",
|
||
);
|
||
|
||
let position = viewport_position_of(&grid, "output");
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, position, 3),
|
||
"longcmd-output"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_running_command_without_end_boundary_falls_back_to_logical_line_selection() {
|
||
let mut grid =
|
||
create_grid_with_content("\x1b]133;A\x07$ \x1b]133;B\x07sleep\x1b]133;C\x07\r\npartial");
|
||
|
||
let position = viewport_position_of(&grid, "partial");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), "partial");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_command_becomes_selectable_once_its_end_boundary_arrives() {
|
||
let mut grid =
|
||
create_grid_with_content("\x1b]133;A\x07$ \x1b]133;B\x07sleep\x1b]133;C\x07\r\npartial");
|
||
let position = viewport_position_of(&grid, "partial");
|
||
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]133;D;0\x07");
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, position, 3),
|
||
"sleep\npartial"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_missing_input_starts_selection_at_output_boundary() {
|
||
let mut grid =
|
||
create_grid_with_content("\x1b]133;A\x07$ hidden\x1b]133;C\x07visible\x1b]133;D\x07");
|
||
|
||
let position = viewport_position_of(&grid, "visible");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), "visible");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_missing_end_stops_at_later_prompt() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07one\x1b]133;C\x07:1\x1b]133;A\x07$ \x1b]133;B\x07two\x1b]133;C\x07:2",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 5), 3),
|
||
"one:1"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_multiple_commands_on_one_row_remain_distinct() {
|
||
let content = "\x1b]133;A\x07$ \x1b]133;B\x07one\x1b]133;C\x07:1\x1b]133;D\x07\x1b]133;P\x07$ \x1b]133;I\x07two\x1b]133;C\x07:2\x1b]133;D\x07";
|
||
let mut first = create_grid_with_content(content);
|
||
let mut second = create_grid_with_content(content);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut first, Position::new(0, 5), 3),
|
||
"one:1"
|
||
);
|
||
assert_eq!(
|
||
select_with_click_count(&mut second, Position::new(0, 12), 3),
|
||
"two:2"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_markers_survive_scrollback() {
|
||
let mut grid = create_grid_with_size_and_raw(
|
||
3,
|
||
20,
|
||
b"\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07\r\nRESULT\x1b]133;D\x07\r\ntail1\r\ntail2\r\ntail3\r\ntail4",
|
||
);
|
||
for _ in 0..3 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
|
||
let position = viewport_position_of(&grid, "RESULT");
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, position, 3),
|
||
"cmd\nRESULT"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_markers_survive_resize_reflow() {
|
||
let mut grid = create_grid_with_size_and_raw(
|
||
5,
|
||
20,
|
||
b"\x1b]133;A\x07$ \x1b]133;B\x07echo hi\x1b]133;C\x07RESULT\x1b]133;D\x07",
|
||
);
|
||
grid.change_size(5, 6);
|
||
|
||
let position = viewport_position_of(&grid, "RES");
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, position, 3),
|
||
"echo hiRESULT"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_cleared_markers_fall_back_to_logical_line_selection() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07old\r\x1b[2Kunmarked",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 6), 3),
|
||
"unmarked"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_truncated_markers_fall_back_to_logical_line_selection() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07old\r\x1b[2C\x1b[Jplain",
|
||
);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 3), 3),
|
||
"$ plain"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_overwritten_markers_fall_back_to_logical_line_selection() {
|
||
let mut grid =
|
||
create_grid_with_content("\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07old\runmarked");
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 6), 3),
|
||
"unmarked"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_unknown_subcommand_is_ignored() {
|
||
let mut grid = create_grid_with_content("plain\x1b]133;Z\x07 text");
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(0, 7), 3),
|
||
"plain text"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_command_selection_disabled_falls_back_to_logical_line_selection() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07echo hi\x1b]133;C\x07\r\nhi\x1b]133;D;0\x07",
|
||
);
|
||
grid.set_selection_options(false, DEFAULT_WORD_SEPARATORS);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(1, 0), 3),
|
||
"hi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_command_selection_can_be_re_enabled_for_existing_markers() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07echo hi\x1b]133;C\x07\r\nhi\x1b]133;D;0\x07",
|
||
);
|
||
grid.set_selection_options(false, DEFAULT_WORD_SEPARATORS);
|
||
grid.set_selection_options(true, DEFAULT_WORD_SEPARATORS);
|
||
|
||
assert_eq!(
|
||
select_with_click_count(&mut grid, Position::new(1, 0), 3),
|
||
"echo hi\nhi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn configured_word_separators_split_double_click_selection() {
|
||
let mut grid = create_grid_with_content("foo:bar baz");
|
||
grid.set_selection_options(true, ":");
|
||
|
||
let position = viewport_position_of(&grid, "bar");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 2), "bar");
|
||
}
|
||
|
||
#[test]
|
||
fn characters_absent_from_word_separators_do_not_split_double_click_selection() {
|
||
let mut grid = create_grid_with_content("foo:bar baz");
|
||
grid.set_selection_options(true, DEFAULT_WORD_SEPARATORS);
|
||
|
||
let position = viewport_position_of(&grid, "bar");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 2), "foo:bar");
|
||
}
|
||
|
||
#[test]
|
||
fn word_separators_not_configured_as_separators_are_part_of_the_word() {
|
||
let mut grid = create_grid_with_content("foo(bar) baz");
|
||
grid.set_selection_options(true, "");
|
||
|
||
let position = viewport_position_of(&grid, "bar");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 2), "foo(bar)");
|
||
}
|
||
|
||
#[test]
|
||
fn default_word_separators_keep_bracket_boundaries() {
|
||
let mut grid = create_grid_with_content("foo(bar) baz");
|
||
grid.set_selection_options(true, DEFAULT_WORD_SEPARATORS);
|
||
|
||
let position = viewport_position_of(&grid, "bar");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 2), "bar");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_repeated_output_markers_without_input_start_at_latest_output() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ first\x1b]133;C\x07skip\x1b]133;C\x07keep\x1b]133;D\x07",
|
||
);
|
||
|
||
let position = viewport_position_of(&grid, "keep");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), "keep");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_click_exactly_on_output_marker_column_selects_command_and_output() {
|
||
let mut grid = create_grid_with_content(
|
||
"\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07OUT\x1b]133;D\x07",
|
||
);
|
||
|
||
let position = viewport_position_of(&grid, "OUT");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), "cmdOUT");
|
||
}
|
||
|
||
#[test]
|
||
fn osc133_backward_scan_finds_command_start_in_deep_scrollback() {
|
||
let mut content = b"\x1b]133;A\x07$ \x1b]133;B\x07cmd\x1b]133;C\x07\r\nline00".to_vec();
|
||
for i in 1..40 {
|
||
content.extend_from_slice(format!("\r\nline{:02}", i).as_bytes());
|
||
}
|
||
content.extend_from_slice(b"\x1b]133;D\x07");
|
||
let mut grid = create_grid_with_size_and_raw(10, 20, &content);
|
||
|
||
let position = viewport_position_of(&grid, "line39");
|
||
let expected = std::iter::once("cmd".to_string())
|
||
.chain((0..40).map(|i| format!("line{:02}", i)))
|
||
.collect::<Vec<String>>()
|
||
.join("\n");
|
||
assert_eq!(select_with_click_count(&mut grid, position, 3), expected);
|
||
}
|
||
|
||
#[test]
|
||
fn cursor_forward_over_unwritten_line_positions_character() {
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
|
||
let content = "\u{1b}[1BABC \u{1b}[1B\r\u{1b}[3C\u{1b}[42mDEF\u{1b}[0m";
|
||
let grid = create_grid_with_content(content);
|
||
|
||
let row = &grid.viewport[2];
|
||
|
||
assert_eq!(row.columns[0].character, ' ');
|
||
assert_eq!(row.columns[1].character, ' ');
|
||
assert_eq!(row.columns[2].character, ' ');
|
||
assert_eq!(row.columns[3].character, 'D');
|
||
assert_eq!(row.columns[4].character, 'E');
|
||
assert_eq!(row.columns[5].character, 'F');
|
||
|
||
let has_background = |c: &crate::panes::terminal_character::TerminalCharacter| {
|
||
!matches!(c.styles.background, Some(AnsiCode::Reset) | None)
|
||
};
|
||
assert!(!has_background(&row.columns[0]));
|
||
assert!(!has_background(&row.columns[1]));
|
||
assert!(!has_background(&row.columns[2]));
|
||
assert!(has_background(&row.columns[3]));
|
||
assert!(has_background(&row.columns[4]));
|
||
assert!(has_background(&row.columns[5]));
|
||
}
|
||
|
||
#[test]
|
||
fn double_click_selection_preserved_after_scroll() {
|
||
let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is a word test\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n";
|
||
let mut grid = create_grid_with_content(content);
|
||
|
||
for _ in 0..20 {
|
||
grid.add_canonical_line();
|
||
}
|
||
|
||
let word_position = Position::new(5, 10);
|
||
grid.start_selection(&word_position);
|
||
|
||
let selection_before = grid.get_selected_text();
|
||
let word_start = grid.selection.start;
|
||
let word_end = grid.selection.end;
|
||
|
||
grid.end_selection(&word_position);
|
||
|
||
grid.scroll_up_one_line();
|
||
|
||
let selection_after_start = grid.selection.start;
|
||
let selection_after_end = grid.selection.end;
|
||
|
||
assert_eq!(selection_after_start.line.0, word_start.line.0 + 1);
|
||
assert_eq!(selection_after_end.line.0, word_end.line.0 + 1);
|
||
assert_eq!(selection_after_start.column, word_start.column);
|
||
assert_eq!(selection_after_end.column, word_end.column);
|
||
|
||
let text_after = grid.get_selected_text();
|
||
assert_eq!(selection_before, text_after);
|
||
}
|
||
|
||
#[test]
|
||
fn triple_click_selection_preserved_after_scroll() {
|
||
let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is line five with some text\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n";
|
||
let mut grid = create_grid_with_content(content);
|
||
|
||
for _ in 0..20 {
|
||
grid.add_canonical_line();
|
||
}
|
||
|
||
let line_position = Position::new(5, 15);
|
||
grid.start_selection(&line_position);
|
||
grid.start_selection(&line_position);
|
||
grid.start_selection(&line_position);
|
||
|
||
let selection_before = grid.get_selected_text();
|
||
let line_start = grid.selection.start;
|
||
let line_end = grid.selection.end;
|
||
|
||
grid.end_selection(&line_position);
|
||
|
||
grid.scroll_up_one_line();
|
||
|
||
let selection_after_start = grid.selection.start;
|
||
let selection_after_end = grid.selection.end;
|
||
|
||
assert_eq!(selection_after_start.line.0, line_start.line.0 + 1);
|
||
assert_eq!(selection_after_end.line.0, line_end.line.0 + 1);
|
||
assert_eq!(selection_after_start.column, line_start.column);
|
||
assert_eq!(selection_after_end.column, line_end.column);
|
||
|
||
let text_after = grid.get_selected_text();
|
||
assert_eq!(selection_before, text_after);
|
||
}
|
||
|
||
#[test]
|
||
fn double_click_selection_moves_with_multiple_scrolls() {
|
||
let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is a word test\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n";
|
||
let mut grid = create_grid_with_content(content);
|
||
|
||
for _ in 0..20 {
|
||
grid.add_canonical_line();
|
||
}
|
||
|
||
let word_position = Position::new(5, 10);
|
||
grid.start_selection(&word_position);
|
||
|
||
let initial_start = grid.selection.start;
|
||
let initial_end = grid.selection.end;
|
||
|
||
grid.end_selection(&word_position);
|
||
|
||
for _ in 0..5 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
|
||
assert_eq!(grid.selection.start.line.0, initial_start.line.0 + 5);
|
||
assert_eq!(grid.selection.end.line.0, initial_end.line.0 + 5);
|
||
assert_eq!(grid.selection.start.column, initial_start.column);
|
||
assert_eq!(grid.selection.end.column, initial_end.column);
|
||
}
|
||
|
||
#[test]
|
||
fn single_click_drag_selection_preserved_after_scroll() {
|
||
let content = "line 0\nline 1\nline 2\nline 3\nline 4\nsome text here\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n";
|
||
let mut grid = create_grid_with_content(content);
|
||
|
||
for _ in 0..20 {
|
||
grid.add_canonical_line();
|
||
}
|
||
|
||
grid.start_selection(&Position::new(5, 5));
|
||
grid.update_selection(&Position::new(5, 10));
|
||
|
||
let start_before = grid.selection.start;
|
||
let end_before = grid.selection.end;
|
||
|
||
grid.end_selection(&Position::new(5, 10));
|
||
|
||
grid.scroll_up_one_line();
|
||
|
||
assert_eq!(grid.selection.start.line.0, start_before.line.0 + 1);
|
||
assert_eq!(grid.selection.end.line.0, end_before.line.0 + 1);
|
||
assert_eq!(grid.selection.start.column, start_before.column);
|
||
assert_eq!(grid.selection.end.column, end_before.column);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_11_set_and_query_pane_default_bg() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
10,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
|
||
// Set background via OSC 11
|
||
let set_bg = b"\x1b]11;#001a3a\x07";
|
||
vte_parser.advance(&mut grid, set_bg);
|
||
|
||
assert_eq!(grid.pane_default_bg, Some((0, 26, 58)));
|
||
|
||
// Query background via OSC 11 — because a pane-scoped override is
|
||
// in place, the query is short-circuited: apps inside the pane
|
||
// must see what Zellij is actually rendering, not the host
|
||
// terminal's bg. The reply uses xterm's canonical
|
||
// `rgb:RRRR/GGGG/BBBB` form with each 8-bit channel widened by
|
||
// repetition (0x00 → 0x0000, 0x1a → 0x1a1a, 0x3a → 0x3a3a).
|
||
let query_bg = b"\x1b]11;?\x07";
|
||
vte_parser.advance(&mut grid, query_bg);
|
||
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"OSC 11 query must not be forwarded when a pane override is set"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert_eq!(reply, "\u{1b}]11;rgb:0000/1a1a/3a3a\u{7}",);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_10_set_and_query_pane_default_fg() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
10,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
|
||
// Set foreground via OSC 10
|
||
let set_fg = b"\x1b]10;#00e000\x07";
|
||
vte_parser.advance(&mut grid, set_fg);
|
||
|
||
assert_eq!(grid.pane_default_fg, Some((0, 224, 0)));
|
||
|
||
// Query foreground via OSC 10 — pane-scoped override is in place,
|
||
// so the query is answered locally (see OSC 11 equivalent test for
|
||
// the short-circuit rationale).
|
||
let query_fg = b"\x1b]10;?\x07";
|
||
vte_parser.advance(&mut grid, query_fg);
|
||
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"OSC 10 query must not be forwarded when a pane override is set"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert_eq!(reply, "\u{1b}]10;rgb:0000/e0e0/0000\u{7}",);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_110_111_reset_pane_default_colors() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
10,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
|
||
// Set both fg and bg
|
||
let set_fg = b"\x1b]10;#00e000\x07";
|
||
vte_parser.advance(&mut grid, set_fg);
|
||
let set_bg = b"\x1b]11;#001a3a\x07";
|
||
vte_parser.advance(&mut grid, set_bg);
|
||
|
||
assert_eq!(grid.pane_default_fg, Some((0, 224, 0)));
|
||
assert_eq!(grid.pane_default_bg, Some((0, 26, 58)));
|
||
|
||
// Reset foreground via OSC 110
|
||
let reset_fg = b"\x1b]110\x07";
|
||
vte_parser.advance(&mut grid, reset_fg);
|
||
assert_eq!(grid.pane_default_fg, None);
|
||
assert_eq!(grid.pane_default_bg, Some((0, 26, 58)));
|
||
|
||
// Reset background via OSC 111
|
||
let reset_bg = b"\x1b]111\x07";
|
||
vte_parser.advance(&mut grid, reset_bg);
|
||
assert_eq!(grid.pane_default_fg, None);
|
||
assert_eq!(grid.pane_default_bg, None);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_11_set_bg_produces_ansi_in_render_output() {
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
5,
|
||
10,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
|
||
// Set background via OSC 11
|
||
let set_bg = b"\x1b]11;#001a3a\x07";
|
||
vte_parser.advance(&mut grid, set_bg);
|
||
|
||
assert_eq!(grid.pane_default_bg, Some((0, 26, 58)));
|
||
|
||
// Render the grid and check that the pane defaults are stamped on chunks
|
||
let style = Style::default();
|
||
let render_result = grid.render(0, 0, &style).unwrap();
|
||
assert!(render_result.is_some(), "Expected render output");
|
||
|
||
let (chunks, _, _, _) = render_result.unwrap();
|
||
assert!(!chunks.is_empty(), "Expected at least one character chunk");
|
||
|
||
// All chunks should carry the pane default bg
|
||
for chunk in &chunks {
|
||
assert_eq!(
|
||
chunk.pane_default_bg,
|
||
Some(AnsiCode::RgbCode((0, 26, 58))),
|
||
"Chunk should carry pane default background"
|
||
);
|
||
}
|
||
}
|
||
|
||
// =====================================================================
|
||
// Plugin Highlight Engine Tests
|
||
// =====================================================================
|
||
|
||
use crate::panes::grid::MouseTracking;
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
use std::collections::BTreeMap;
|
||
use zellij_utils::data::{HighlightLayer, HighlightStyle, RegexHighlight};
|
||
|
||
fn create_highlight(
|
||
pattern: &str,
|
||
on_hover: bool,
|
||
bold: bool,
|
||
italic: bool,
|
||
underline: bool,
|
||
layer: HighlightLayer,
|
||
) -> RegexHighlight {
|
||
RegexHighlight {
|
||
pattern: pattern.to_string(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer,
|
||
context: BTreeMap::new(),
|
||
on_hover,
|
||
bold,
|
||
italic,
|
||
underline,
|
||
tooltip_text: None,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn set_plugin_regex_highlights_basic_match() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let highlights = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: true,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
let slot = grid.plugin_highlights.get(&1);
|
||
assert!(slot.is_some());
|
||
let entries = slot.unwrap();
|
||
assert_eq!(entries.len(), 1);
|
||
assert!(entries[0].1.regex.is_match("foo"));
|
||
}
|
||
|
||
#[test]
|
||
fn set_plugin_regex_highlights_no_match() {
|
||
let mut grid = create_grid_with_content("hello world bar\n");
|
||
let highlights = vec![create_highlight(
|
||
"xyz123",
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// No position in the viewport should match
|
||
for col in 0..15 {
|
||
assert!(grid.plugin_highlight_at(&Position::new(0, col)).is_none());
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn clear_plugin_highlights_removes_highlights() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let highlights = vec![create_highlight(
|
||
"foo",
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
assert!(grid.plugin_highlights.get(&1).is_some());
|
||
|
||
grid.clear_plugin_highlights(1);
|
||
assert!(grid.plugin_highlights.get(&1).is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn multiple_plugins_highlights_independent() {
|
||
let mut grid = create_grid_with_content("aaa bbb ccc\n");
|
||
let h1 = vec![create_highlight(
|
||
"aaa",
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
let h2 = vec![create_highlight(
|
||
"bbb",
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
assert!(grid.plugin_highlights.get(&1).is_some());
|
||
assert!(grid.plugin_highlights.get(&2).is_some());
|
||
|
||
grid.clear_plugin_highlights(1);
|
||
assert!(grid.plugin_highlights.get(&1).is_none());
|
||
assert!(grid.plugin_highlights.get(&2).is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn upsert_replaces_same_pattern() {
|
||
let mut grid = create_grid_with_content("foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: true,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h2, &Style::default());
|
||
|
||
let entries = grid.plugin_highlights.get(&1).unwrap();
|
||
assert_eq!(entries.len(), 1);
|
||
assert!(!entries[0].1.underline);
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_regex_does_not_crash() {
|
||
let mut grid = create_grid_with_content("hello\n");
|
||
let highlights = vec![create_highlight(
|
||
"[invalid",
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
// Invalid regex should be skipped
|
||
let slot = grid.plugin_highlights.get(&1);
|
||
match slot {
|
||
None => {}, // acceptable
|
||
Some(entries) => assert_eq!(entries.len(), 0),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn plugin_highlight_at_returns_match() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let mut context = BTreeMap::new();
|
||
context.insert("key".to_string(), "value".to_string());
|
||
let highlights = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: context.clone(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: true,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// "foo" starts at column 6 in "hello foo bar"
|
||
let result = grid.plugin_highlight_at(&Position::new(0, 6));
|
||
assert!(result.is_some());
|
||
let (plugin_id, pattern, matched_string, ctx) = result.unwrap();
|
||
assert_eq!(plugin_id, 1);
|
||
assert_eq!(pattern, "foo");
|
||
assert_eq!(matched_string, "foo");
|
||
assert_eq!(ctx.get("key").unwrap(), "value");
|
||
}
|
||
|
||
#[test]
|
||
fn plugin_highlight_at_returns_none_on_miss() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let highlights = vec![create_highlight(
|
||
"foo",
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// Position 0 is in "hello", not "foo"
|
||
let result = grid.plugin_highlight_at(&Position::new(0, 0));
|
||
assert!(result.is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn plugin_highlight_at_wrapped_line() {
|
||
// Create a narrow grid (10 cols) so that a long string wraps
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
5,
|
||
10,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
// Feed a long string that wraps: "abcdefghij" fills row 0, "klmnopqrst" fills row 1
|
||
let content = "abcdefghijklmnopqrst";
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
|
||
// Set a highlight for "jklm" which spans the wrap boundary
|
||
let highlights = vec![create_highlight(
|
||
"jklm",
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// "jklm" spans row 0 col 9 through row 1 col 3
|
||
// Position in the wrapped portion (row 1, col 1 = 'k')
|
||
let result = grid.plugin_highlight_at(&Position::new(1, 1));
|
||
assert!(result.is_some());
|
||
let (plugin_id, _pattern, matched_string, _ctx) = result.unwrap();
|
||
assert_eq!(plugin_id, 1);
|
||
assert_eq!(matched_string, "jklm");
|
||
}
|
||
|
||
const COMBINED_ENTRIES_PATTERN: &str = r"(?:^|\s)((?:abc\.rs|b\.rs)(?:/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+)?(?::\d+(?::\d+)?)?)(?::|\s|$)";
|
||
|
||
#[test]
|
||
fn combined_pattern_highlights_adjacent_entries_separated_by_single_space() {
|
||
let mut grid = create_grid_with_content("abc.rs b.rs\n");
|
||
let highlights = vec![create_highlight(
|
||
COMBINED_ENTRIES_PATTERN,
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
let first = grid.plugin_highlight_at(&Position::new(0, 0)).unwrap();
|
||
assert_eq!(first.2, "abc.rs");
|
||
let second = grid.plugin_highlight_at(&Position::new(0, 8)).unwrap();
|
||
assert_eq!(second.2, "b.rs");
|
||
assert!(grid.plugin_highlight_at(&Position::new(0, 6)).is_none());
|
||
assert_eq!(grid.compute_plugin_highlight_selections().len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn combined_pattern_highlights_entries_on_wrapped_line() {
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
5,
|
||
10,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, "12345 abc.rs b.rs".as_bytes());
|
||
|
||
let highlights = vec![create_highlight(
|
||
COMBINED_ENTRIES_PATTERN,
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
let wrapped = grid.plugin_highlight_at(&Position::new(1, 0)).unwrap();
|
||
assert_eq!(wrapped.2, "abc.rs");
|
||
let after_wrap = grid.plugin_highlight_at(&Position::new(1, 4)).unwrap();
|
||
assert_eq!(after_wrap.2, "b.rs");
|
||
assert_eq!(grid.compute_plugin_highlight_selections().len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn hover_position_triggers_on_hover_highlight() {
|
||
let mut grid = create_grid_with_content("hello link_text bar\n");
|
||
let highlights = vec![create_highlight(
|
||
"link_text",
|
||
true,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// Set hover position inside "link_text" (starts at col 6)
|
||
grid.set_hover_position(Some(Position::new(0, 8)));
|
||
assert!(grid.hover_position.is_some());
|
||
assert_eq!(grid.hover_position.unwrap(), Position::new(0, 8));
|
||
|
||
// The on_hover entry should exist in plugin_highlights
|
||
let entries = grid.plugin_highlights.get(&1).unwrap();
|
||
assert_eq!(entries.len(), 1);
|
||
assert!(entries[0].1.on_hover);
|
||
}
|
||
|
||
#[test]
|
||
fn hover_suppressed_when_mouse_tracking_on() {
|
||
let mut grid = create_grid_with_content("hello link_text bar\n");
|
||
let highlights = vec![create_highlight(
|
||
"link_text",
|
||
true,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// Enable mouse tracking — the render path should skip hover highlights
|
||
grid.mouse_tracking = MouseTracking::Normal;
|
||
grid.set_hover_position(Some(Position::new(0, 8)));
|
||
|
||
// The hover position is set regardless (the guard is in the render path),
|
||
// but we verify that mouse_tracking is non-Off
|
||
assert!(grid.hover_position.is_some());
|
||
assert_ne!(grid.mouse_tracking, MouseTracking::Off);
|
||
}
|
||
|
||
#[test]
|
||
fn wide_char_display_column_mapping() {
|
||
// CJK characters: "你好" = 2 chars, each 2 display cols wide, so "world" starts at display col 4
|
||
let mut grid = create_grid_with_content("你好world\n");
|
||
let highlights = vec![create_highlight(
|
||
"world",
|
||
false,
|
||
false,
|
||
false,
|
||
true,
|
||
HighlightLayer::Hint,
|
||
)];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
|
||
// "你好" occupies display cols 0-3, "world" starts at display col 4
|
||
let result = grid.plugin_highlight_at(&Position::new(0, 4));
|
||
assert!(result.is_some());
|
||
let (plugin_id, _pattern, matched_string, _ctx) = result.unwrap();
|
||
assert_eq!(plugin_id, 1);
|
||
assert_eq!(matched_string, "world");
|
||
|
||
// Position 2 should be inside "你好", not "world"
|
||
let result_miss = grid.plugin_highlight_at(&Position::new(0, 2));
|
||
assert!(result_miss.is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn highlight_style_variants_resolve_colors() {
|
||
use super::resolve_highlight_colors;
|
||
|
||
let style = Style::default();
|
||
|
||
// HighlightStyle::None returns (None, None)
|
||
let (fg, bg): (Option<AnsiCode>, Option<AnsiCode>) =
|
||
resolve_highlight_colors(&HighlightStyle::None, &style);
|
||
assert!(fg.is_none());
|
||
assert!(bg.is_none());
|
||
|
||
// HighlightStyle::CustomRgb with fg only
|
||
let (fg, bg): (Option<AnsiCode>, Option<AnsiCode>) = resolve_highlight_colors(
|
||
&HighlightStyle::CustomRgb {
|
||
fg: Some((255, 0, 0)),
|
||
bg: None,
|
||
},
|
||
&style,
|
||
);
|
||
assert_eq!(fg, Some(AnsiCode::RgbCode((255, 0, 0))));
|
||
assert!(bg.is_none());
|
||
|
||
// HighlightStyle::CustomIndex with bg only
|
||
let (fg, bg): (Option<AnsiCode>, Option<AnsiCode>) = resolve_highlight_colors(
|
||
&HighlightStyle::CustomIndex {
|
||
fg: None,
|
||
bg: Some(42),
|
||
},
|
||
&style,
|
||
);
|
||
assert!(fg.is_none());
|
||
assert_eq!(bg, Some(AnsiCode::ColorIndex(42)));
|
||
|
||
// HighlightStyle::Emphasis0 should return a foreground color from the palette
|
||
let (fg, bg): (Option<AnsiCode>, Option<AnsiCode>) =
|
||
resolve_highlight_colors(&HighlightStyle::Emphasis0, &style);
|
||
assert!(fg.is_some());
|
||
assert!(bg.is_none());
|
||
}
|
||
|
||
fn create_grid_with_scrollback() -> Grid {
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
5,
|
||
40,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
let mut parser = vte::Parser::new();
|
||
for i in 0..25 {
|
||
let line = format!("scrollback line {}\r\n", i);
|
||
parser.advance(&mut grid, line.as_bytes());
|
||
}
|
||
grid
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_scrollback_no_truncation_when_max_none() {
|
||
let grid = create_grid_with_scrollback();
|
||
let result = grid.pane_contents(true, None);
|
||
assert_eq!(
|
||
result.lines_above_viewport.len(),
|
||
21,
|
||
"All scrollback lines should be returned when max is None"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_scrollback_no_truncation_when_max_zero() {
|
||
let grid = create_grid_with_scrollback();
|
||
let result = grid.pane_contents(true, Some(0));
|
||
assert_eq!(
|
||
result.lines_above_viewport.len(),
|
||
21,
|
||
"Some(0) is sentinel for all scrollback — no truncation"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_scrollback_truncates_to_last_n() {
|
||
let grid = create_grid_with_scrollback();
|
||
let result = grid.pane_contents(true, Some(5));
|
||
assert_eq!(result.lines_above_viewport.len(), 5);
|
||
let full = grid.pane_contents(true, None);
|
||
let expected: Vec<String> = full
|
||
.lines_above_viewport
|
||
.iter()
|
||
.rev()
|
||
.take(5)
|
||
.rev()
|
||
.cloned()
|
||
.collect();
|
||
assert_eq!(result.lines_above_viewport, expected);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_scrollback_no_truncation_when_n_exceeds_total() {
|
||
let grid = create_grid_with_scrollback();
|
||
let result = grid.pane_contents(true, Some(100));
|
||
assert_eq!(
|
||
result.lines_above_viewport.len(),
|
||
21,
|
||
"No truncation when N exceeds total scrollback lines"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_no_scrollback_when_flag_false() {
|
||
let grid = create_grid_with_scrollback();
|
||
let result = grid.pane_contents(false, Some(5));
|
||
assert!(
|
||
result.lines_above_viewport.is_empty(),
|
||
"get_full_scrollback=false should never collect scrollback"
|
||
);
|
||
}
|
||
|
||
// =====================================================================
|
||
// pane_contents_with_ansi Tests
|
||
// =====================================================================
|
||
|
||
fn create_grid_with_colored_scrollback() -> Grid {
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
5,
|
||
40,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
let mut parser = vte::Parser::new();
|
||
for i in 0..25 {
|
||
let line = format!("\x1b[31mred line {}\x1b[0m\r\n", i);
|
||
parser.advance(&mut grid, line.as_bytes());
|
||
}
|
||
grid
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_with_ansi_preserves_escape_codes() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let result = grid.pane_contents_with_ansi(false, None);
|
||
let has_ansi = result.viewport.iter().any(|line| line.contains("\x1b["));
|
||
assert!(
|
||
has_ansi,
|
||
"pane_contents_with_ansi should preserve ANSI escape codes in viewport. Lines: {:?}",
|
||
result.viewport
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_strips_escape_codes() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let result = grid.pane_contents(false, None);
|
||
let has_ansi = result.viewport.iter().any(|line| line.contains("\x1b["));
|
||
assert!(
|
||
!has_ansi,
|
||
"pane_contents should strip ANSI escape codes from viewport. Lines: {:?}",
|
||
result.viewport
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_with_ansi_scrollback_preserves_escape_codes() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let result = grid.pane_contents_with_ansi(true, None);
|
||
let has_ansi = result
|
||
.lines_above_viewport
|
||
.iter()
|
||
.any(|line| line.contains("\x1b["));
|
||
assert!(
|
||
has_ansi,
|
||
"pane_contents_with_ansi should preserve ANSI escape codes in scrollback. Lines: {:?}",
|
||
result.lines_above_viewport
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_with_ansi_scrollback_truncation() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let result = grid.pane_contents_with_ansi(true, Some(3));
|
||
assert_eq!(
|
||
result.lines_above_viewport.len(),
|
||
3,
|
||
"Should truncate to 3 scrollback lines"
|
||
);
|
||
let all_have_ansi = result
|
||
.lines_above_viewport
|
||
.iter()
|
||
.all(|line| line.contains("\x1b["));
|
||
assert!(
|
||
all_have_ansi,
|
||
"All truncated scrollback lines should contain ANSI codes. Lines: {:?}",
|
||
result.lines_above_viewport
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_with_ansi_no_scrollback_when_flag_false() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let result = grid.pane_contents_with_ansi(false, Some(5));
|
||
assert!(
|
||
result.lines_above_viewport.is_empty(),
|
||
"get_full_scrollback=false should never collect scrollback even with ansi"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn pane_contents_with_ansi_and_without_have_same_text() {
|
||
let grid = create_grid_with_colored_scrollback();
|
||
let plain = grid.pane_contents(false, None);
|
||
let ansi = grid.pane_contents_with_ansi(false, None);
|
||
assert_eq!(
|
||
plain.viewport.len(),
|
||
ansi.viewport.len(),
|
||
"Both should have the same number of viewport lines"
|
||
);
|
||
// Strip ANSI codes from the ansi version and compare plain text content
|
||
let ansi_escape = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
|
||
for (plain_line, ansi_line) in plain.viewport.iter().zip(ansi.viewport.iter()) {
|
||
let stripped = ansi_escape.replace_all(ansi_line, "").to_string();
|
||
assert_eq!(
|
||
*plain_line, stripped,
|
||
"After stripping ANSI codes, text content should match"
|
||
);
|
||
}
|
||
}
|
||
|
||
// =====================================================================
|
||
// Highlight Layer Priority Tests
|
||
// =====================================================================
|
||
|
||
#[test]
|
||
fn higher_layer_wins_plugin_highlight_at() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis1,
|
||
layer: HighlightLayer::Tool,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
// "foo" starts at column 6
|
||
let result = grid.plugin_highlight_at(&Position::new(0, 6));
|
||
assert!(result.is_some());
|
||
let (plugin_id, _, _, _) = result.unwrap();
|
||
assert_eq!(plugin_id, 2, "Tool layer plugin should win over Hint layer");
|
||
}
|
||
|
||
#[test]
|
||
fn same_layer_both_returned_deterministically() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis1,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
let result = grid.plugin_highlight_at(&Position::new(0, 6));
|
||
assert!(
|
||
result.is_some(),
|
||
"Same-layer conflicts should not cause errors"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn lower_layer_wins_when_higher_layer_absent() {
|
||
let mut grid = create_grid_with_content("foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "bar".into(),
|
||
style: HighlightStyle::Emphasis1,
|
||
layer: HighlightLayer::ActionFeedback,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
// "foo" at col 0 — only Hint layer matches here
|
||
let result_foo = grid.plugin_highlight_at(&Position::new(0, 0));
|
||
assert!(result_foo.is_some());
|
||
assert_eq!(result_foo.unwrap().0, 1);
|
||
|
||
// "bar" at col 4 — only ActionFeedback layer matches here
|
||
let result_bar = grid.plugin_highlight_at(&Position::new(0, 4));
|
||
assert!(result_bar.is_some());
|
||
assert_eq!(result_bar.unwrap().0, 2);
|
||
}
|
||
|
||
#[test]
|
||
fn tooltip_from_higher_layer_wins() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: true,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: Some("hint tooltip".to_string()),
|
||
}];
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis1,
|
||
layer: HighlightLayer::Tool,
|
||
context: BTreeMap::new(),
|
||
on_hover: true,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: Some("tool tooltip".to_string()),
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
// Set hover position inside "foo" (starts at col 6)
|
||
grid.set_hover_position(Some(Position::new(0, 6)));
|
||
assert_eq!(
|
||
grid.cached_hover_tooltip,
|
||
Some("tool tooltip".to_string()),
|
||
"Tool layer tooltip should win over Hint layer tooltip"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn tooltip_from_lower_layer_when_higher_has_none() {
|
||
let mut grid = create_grid_with_content("hello foo bar\n");
|
||
let h1 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::Hint,
|
||
context: BTreeMap::new(),
|
||
on_hover: true,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: Some("hint tooltip".to_string()),
|
||
}];
|
||
let h2 = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis1,
|
||
layer: HighlightLayer::Tool,
|
||
context: BTreeMap::new(),
|
||
on_hover: true,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, h1, &Style::default());
|
||
grid.set_plugin_regex_highlights(2, h2, &Style::default());
|
||
|
||
grid.set_hover_position(Some(Position::new(0, 6)));
|
||
assert_eq!(
|
||
grid.cached_hover_tooltip,
|
||
Some("hint tooltip".to_string()),
|
||
"When higher layer has no tooltip, lower layer tooltip should be used"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn layer_field_stored_in_compiled_highlight() {
|
||
let mut grid = create_grid_with_content("foo bar\n");
|
||
let highlights = vec![RegexHighlight {
|
||
pattern: "foo".into(),
|
||
style: HighlightStyle::Emphasis0,
|
||
layer: HighlightLayer::ActionFeedback,
|
||
context: BTreeMap::new(),
|
||
on_hover: false,
|
||
bold: false,
|
||
italic: false,
|
||
underline: false,
|
||
tooltip_text: None,
|
||
}];
|
||
grid.set_plugin_regex_highlights(1, highlights, &Style::default());
|
||
assert_eq!(
|
||
grid.plugin_highlights.get(&1).unwrap()[0].1.layer,
|
||
HighlightLayer::ActionFeedback,
|
||
"Layer field should be propagated through compilation"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn default_layer_is_hint() {
|
||
assert_eq!(HighlightLayer::default(), HighlightLayer::Hint);
|
||
}
|
||
|
||
#[test]
|
||
fn layer_ordering() {
|
||
assert!(HighlightLayer::Hint < HighlightLayer::Tool);
|
||
assert!(HighlightLayer::Tool < HighlightLayer::ActionFeedback);
|
||
}
|
||
|
||
fn row_text(row: &super::super::Row) -> String {
|
||
row.columns
|
||
.iter()
|
||
.map(|c| c.character)
|
||
.collect::<String>()
|
||
.trim_end()
|
||
.to_string()
|
||
}
|
||
|
||
fn scrollback_texts(grid: &Grid) -> Vec<String> {
|
||
grid.lines_above.iter().map(|r| row_text(r)).collect()
|
||
}
|
||
|
||
fn viewport_texts(grid: &Grid) -> Vec<String> {
|
||
grid.viewport.iter().map(|r| row_text(r)).collect()
|
||
}
|
||
|
||
fn create_grid_with_size_and_raw(rows: usize, cols: usize, content: &[u8]) -> Grid {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
let mut grid = Grid::new(
|
||
rows,
|
||
cols,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
vte_parser.advance(&mut grid, &content);
|
||
grid
|
||
}
|
||
|
||
fn feed_bytes(grid: &mut Grid, bytes: &[u8]) {
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(grid, bytes);
|
||
}
|
||
|
||
// All tests below use a 10-row, 40-col grid with scroll region 1;8
|
||
// (0-based: rows 0-7), leaving rows 8-9 outside the region.
|
||
const PARTIAL_SR: &[u8] = b"\x1b[1;8r";
|
||
const FILL_8_LINES: &[u8] = b"AAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nFFF\r\nGGG\r\nHHH";
|
||
|
||
#[test]
|
||
fn partial_scroll_region_newline_transfers_to_scrollback() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\r\nIII\r\nJJJ");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(scrollback_texts(&grid), vec!["AAA", "BBB"]);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "CCC");
|
||
assert_eq!(vp[1], "DDD");
|
||
assert_eq!(vp[6], "III");
|
||
assert_eq!(vp[7], "JJJ");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_csi_s_transfers_to_scrollback() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\x1b[3S");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(scrollback_texts(&grid), vec!["AAA", "BBB", "CCC"]);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "DDD");
|
||
assert_eq!(vp[4], "HHH");
|
||
assert_eq!(vp[5], "");
|
||
assert_eq!(vp[7], "");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_csi_m_at_top_transfers_to_scrollback() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\x1b[1;1H\x1b[2M");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(scrollback_texts(&grid), vec!["AAA", "BBB"]);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "CCC");
|
||
assert_eq!(vp[5], "HHH");
|
||
assert_eq!(vp[6], "");
|
||
assert_eq!(vp[7], "");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_csi_m_mid_region_does_not_transfer() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
// Cursor to row 3, delete 2 lines
|
||
content.extend_from_slice(b"\x1b[4;1H\x1b[2M");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "AAA");
|
||
assert_eq!(vp[2], "CCC");
|
||
assert_eq!(vp[3], "FFF");
|
||
assert_eq!(vp[5], "HHH");
|
||
assert_eq!(vp[6], "");
|
||
assert_eq!(vp[7], "");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_does_not_transfer_on_alternate_screen() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"\x1b[?1049h");
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\x1b[3S");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "DDD");
|
||
assert_eq!(vp[4], "HHH");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_nonzero_top_does_not_transfer() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"AAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nFFF");
|
||
// Scroll region rows 3-6 (1-based), so top is row 2, not 0
|
||
content.extend_from_slice(b"\x1b[3;6r");
|
||
content.extend_from_slice(b"\x1b[2S");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "AAA");
|
||
assert_eq!(vp[1], "BBB");
|
||
assert_eq!(vp[2], "EEE");
|
||
assert_eq!(vp[3], "FFF");
|
||
assert_eq!(vp[4], "");
|
||
assert_eq!(vp[5], "");
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_selection_adjusted_on_newline() {
|
||
let mut grid = create_grid_with_size_and_raw(10, 40, FILL_8_LINES);
|
||
feed_bytes(&mut grid, PARTIAL_SR);
|
||
|
||
let pos = Position::new(3, 5);
|
||
grid.start_selection(&pos);
|
||
grid.end_selection(&pos);
|
||
let start_before = grid.selection.start.line.0;
|
||
let end_before = grid.selection.end.line.0;
|
||
|
||
feed_bytes(&mut grid, b"\x1b[8;1H\r\nnew line");
|
||
|
||
assert_eq!(grid.selection.start.line.0, start_before - 1);
|
||
assert_eq!(grid.selection.end.line.0, end_before - 1);
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_selection_adjusted_on_csi_s() {
|
||
let mut grid = create_grid_with_size_and_raw(10, 40, FILL_8_LINES);
|
||
feed_bytes(&mut grid, PARTIAL_SR);
|
||
|
||
let pos = Position::new(4, 2);
|
||
grid.start_selection(&pos);
|
||
grid.end_selection(&pos);
|
||
let start_before = grid.selection.start.line.0;
|
||
let end_before = grid.selection.end.line.0;
|
||
|
||
feed_bytes(&mut grid, b"\x1b[2S");
|
||
|
||
assert_eq!(grid.selection.start.line.0, start_before - 2);
|
||
assert_eq!(grid.selection.end.line.0, end_before - 2);
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_selection_adjusted_on_csi_m() {
|
||
let mut grid = create_grid_with_size_and_raw(10, 40, FILL_8_LINES);
|
||
feed_bytes(&mut grid, PARTIAL_SR);
|
||
|
||
let pos = Position::new(5, 2);
|
||
grid.start_selection(&pos);
|
||
grid.end_selection(&pos);
|
||
let start_before = grid.selection.start.line.0;
|
||
let end_before = grid.selection.end.line.0;
|
||
|
||
feed_bytes(&mut grid, b"\x1b[3M");
|
||
|
||
assert_eq!(grid.selection.start.line.0, start_before - 3);
|
||
assert_eq!(grid.selection.end.line.0, end_before - 3);
|
||
}
|
||
|
||
#[test]
|
||
fn partial_scroll_region_all_three_mechanisms_transfer_in_order() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
// Newline scrolls AAA off, CSI S scrolls BBB off, CSI M scrolls CCC off
|
||
content.extend_from_slice(b"\r\nIII");
|
||
content.extend_from_slice(b"\x1b[1S");
|
||
content.extend_from_slice(b"\x1b[1;1H\x1b[1M");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(scrollback_texts(&grid), vec!["AAA", "BBB", "CCC"]);
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_partial_scroll_region_transfers_to_scrollback() {
|
||
// A long line that soft-wraps while the cursor is on the scroll region's
|
||
// bottom row must scroll the region, exactly like a newline there would
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\r\n");
|
||
content.extend_from_slice(&[b'I'; 90]); // 90 cols on a 40-col grid: wraps twice
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
// the newline scrolls AAA off, the two wraps scroll BBB and CCC off
|
||
assert_eq!(scrollback_texts(&grid), vec!["AAA", "BBB", "CCC"]);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "DDD");
|
||
assert_eq!(vp[4], "HHH");
|
||
assert_eq!(vp[5], "I".repeat(40));
|
||
assert_eq!(vp[6], "I".repeat(40));
|
||
assert_eq!(vp[7], "I".repeat(10));
|
||
assert_eq!(vp.len(), 8, "rows below the region must be untouched");
|
||
// the continuation rows are wrapped rows, so resize re-wraps them correctly
|
||
assert!(grid.viewport[5].is_canonical);
|
||
assert!(!grid.viewport[6].is_canonical);
|
||
assert!(!grid.viewport[7].is_canonical);
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_partial_scroll_region_keeps_cursor_inside_region() {
|
||
// Before the fix, the cursor would escape below the region's bottom margin
|
||
// on wrap, and no subsequent line could ever scroll into scrollback again
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\r\n");
|
||
content.extend_from_slice(&[b'I'; 90]);
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
// region is rows 0-7; after two wraps the cursor must still be on row 7
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((10, 7))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_nonzero_top_scroll_region_does_not_transfer() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"AAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nFFF");
|
||
// Scroll region rows 3-6 (1-based), so top is row 2, not 0
|
||
content.extend_from_slice(b"\x1b[3;6r");
|
||
content.extend_from_slice(b"\x1b[6;1H");
|
||
content.extend_from_slice(&[b'X'; 50]); // wraps once at the region's bottom
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
// regions not anchored at the top do not preserve scrolled-off lines
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[0], "AAA", "rows above the region must be untouched");
|
||
assert_eq!(vp[1], "BBB", "rows above the region must be untouched");
|
||
assert_eq!(vp[2], "DDD", "the region's top row (CCC) is discarded");
|
||
assert_eq!(vp[4], "X".repeat(40));
|
||
assert_eq!(vp[5], "X".repeat(10));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_partial_scroll_region_does_not_transfer_on_alternate_screen() {
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"\x1b[?1049h");
|
||
content.extend_from_slice(PARTIAL_SR);
|
||
content.extend_from_slice(FILL_8_LINES);
|
||
content.extend_from_slice(b"\r\n");
|
||
content.extend_from_slice(&[b'I'; 90]);
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[5], "I".repeat(40));
|
||
assert_eq!(vp[6], "I".repeat(40));
|
||
assert_eq!(vp[7], "I".repeat(10));
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_explicit_full_screen_scroll_region_keeps_wrap_flag() {
|
||
// CSI 1;10r on a 10-row grid covers the whole screen; wrap behavior must
|
||
// be identical to having no region set at all (scroll + wrapped row)
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"\x1b[1;10r");
|
||
for i in 1..=10u8 {
|
||
if i > 1 {
|
||
content.extend_from_slice(b"\r\n");
|
||
}
|
||
content.extend_from_slice(b"EARLY-");
|
||
content.push(b'0' + i / 10);
|
||
content.push(b'0' + i % 10);
|
||
}
|
||
content.extend_from_slice(b"\r\n");
|
||
content.extend_from_slice(&[b'I'; 90]); // wraps twice on a 40-col grid
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
// the newline and the two wraps each scrolled one line into scrollback
|
||
assert_eq!(
|
||
scrollback_texts(&grid),
|
||
vec!["EARLY-01", "EARLY-02", "EARLY-03"]
|
||
);
|
||
let vp = viewport_texts(&grid);
|
||
assert_eq!(vp[6], "EARLY-10");
|
||
assert_eq!(vp[7], "I".repeat(40));
|
||
assert_eq!(vp[8], "I".repeat(40));
|
||
assert_eq!(vp[9], "I".repeat(10));
|
||
assert!(grid.viewport[7].is_canonical);
|
||
assert!(
|
||
!grid.viewport[8].is_canonical,
|
||
"continuation must stay a wrapped row"
|
||
);
|
||
assert!(
|
||
!grid.viewport[9].is_canonical,
|
||
"continuation must stay a wrapped row"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn wrap_at_bottom_of_nonzero_top_scroll_region_keeps_hyperlink_tracking() {
|
||
// A plain-text URL that wraps at the bottom of a region not anchored at
|
||
// the top: the auto-linkifier's tracked positions must follow the rows
|
||
// as the region scrolls, or the URL loses its clickable anchors
|
||
let mut content: Vec<u8> = Vec::new();
|
||
content.extend_from_slice(b"AAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nFFF");
|
||
content.extend_from_slice(b"\x1b[3;6r\x1b[6;1H");
|
||
content.extend_from_slice(b"https://example.com/");
|
||
content.extend_from_slice(&[b'a'; 30]); // 50 chars: wraps at the region bottom
|
||
content.extend_from_slice(b" ");
|
||
|
||
let grid = create_grid_with_size_and_raw(10, 40, &content);
|
||
|
||
let start_anchors_per_row: Vec<usize> = grid
|
||
.viewport
|
||
.iter()
|
||
.map(|row| {
|
||
row.columns
|
||
.iter()
|
||
.filter(|c| {
|
||
matches!(
|
||
c.styles.link_anchor,
|
||
Some(crate::panes::terminal_character::LinkAnchor::Start(_))
|
||
)
|
||
})
|
||
.count()
|
||
})
|
||
.collect();
|
||
|
||
// rows 0-3: AAA, BBB, DDD, EEE (CCC scrolled off and discarded) - no anchors;
|
||
// row 4 holds the URL's first 40 columns, row 5 the wrapped remainder
|
||
assert_eq!(start_anchors_per_row, vec![0, 0, 0, 0, 40, 10]);
|
||
}
|
||
|
||
#[test]
|
||
fn scroll_region_newline_sets_bg_color_on_new_row() {
|
||
// Set bg color, set scroll region 1-5, fill 5 lines, then newline to scroll
|
||
let content = b"\x1b[48;2;26;26;26m\x1b[1;5r\
|
||
AAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nFFF";
|
||
let grid = create_grid_with_size_and_raw(10, 40, content);
|
||
// The new row at the bottom of the scroll region (row index 4) should have bg_color
|
||
let new_row = &grid.viewport[4];
|
||
assert_eq!(
|
||
new_row.bg_color,
|
||
Some(AnsiCode::RgbCode((26, 26, 26))),
|
||
"scroll-created row should carry the cursor background color"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn scroll_region_newline_bg_color_used_for_trailing_padding() {
|
||
// Set bg, scroll region 1-5, fill lines, scroll, then write short text on new row
|
||
let content = b"\x1b[48;2;26;26;26m\x1b[1;5rAAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\r\nhi";
|
||
let mut grid = create_grid_with_size_and_raw(10, 40, content);
|
||
// read_changes returns character chunks with padding applied
|
||
let (chunks, _, _) = grid.read_changes(0, 0);
|
||
// Find the chunk for row 4 (the scroll-created row with "hi")
|
||
let row_4_chunk = chunks.iter().find(|c| c.y == 4).expect("row 4 chunk");
|
||
// The trailing padding character (last column) should have the row's bg_color
|
||
let pad_char = &row_4_chunk.terminal_characters[39];
|
||
assert_eq!(
|
||
pad_char.styles.background,
|
||
Some(AnsiCode::RgbCode((26, 26, 26))),
|
||
"trailing padding should use the row's background color"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn scroll_region_newline_bg_color_used_for_cursor_forward_gaps() {
|
||
// Set bg, scroll region 1-5, fill lines, scroll, then cursor-forward and write
|
||
// This simulates vim's [12C behavior on a scroll-created row
|
||
let content = b"\x1b[48;2;26;26;26m\x1b[1;5rAAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\
|
||
\r\n\x1b[0m\x1b[10Cx";
|
||
let grid = create_grid_with_size_and_raw(10, 40, content);
|
||
let new_row = &grid.viewport[4];
|
||
// Position 5 (within the gap created by cursor forward) should have the bg_color
|
||
let gap_char = &new_row.columns[5];
|
||
assert_eq!(
|
||
gap_char.styles.background,
|
||
Some(AnsiCode::RgbCode((26, 26, 26))),
|
||
"cursor-forward gap should use the row's background color"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn scroll_region_bg_color_does_not_override_explicit_background() {
|
||
// Set bg, scroll region 1-5, fill lines, scroll, then write with different bg
|
||
let content = b"\x1b[48;2;26;26;26m\x1b[1;5rAAA\r\nBBB\r\nCCC\r\nDDD\r\nEEE\
|
||
\r\n\x1b[48;2;255;0;0mRED";
|
||
let grid = create_grid_with_size_and_raw(10, 40, content);
|
||
let new_row = &grid.viewport[4];
|
||
// The 'R' character should have the explicitly set red background, not the row bg
|
||
let r_char = &new_row.columns[0];
|
||
assert_eq!(
|
||
r_char.styles.background,
|
||
Some(AnsiCode::RgbCode((255, 0, 0))),
|
||
"explicitly set background should not be overridden by row bg_color"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn full_scroll_region_newline_sets_bg_color_on_new_row() {
|
||
// Full scroll region (entire viewport), set bg, fill, then scroll
|
||
let content = b"\x1b[48;2;26;26;26m\x1b[1;10r\
|
||
L1\r\nL2\r\nL3\r\nL4\r\nL5\r\nL6\r\nL7\r\nL8\r\nL9\r\nL10\r\nL11";
|
||
let grid = create_grid_with_size_and_raw(10, 40, content);
|
||
// The new row at the bottom (row 9) should have bg_color
|
||
let new_row = &grid.viewport[9];
|
||
assert_eq!(
|
||
new_row.bg_color,
|
||
Some(AnsiCode::RgbCode((26, 26, 26))),
|
||
"full scroll region: new row should carry the cursor background color"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn row_without_scroll_has_no_bg_color() {
|
||
// Normal content without scroll should not set bg_color on rows
|
||
let content = b"\x1b[48;2;26;26;26mHello";
|
||
let grid = create_grid_with_size_and_raw(10, 40, content);
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(
|
||
row.bg_color, None,
|
||
"rows not created by scroll should have no bg_color"
|
||
);
|
||
}
|
||
|
||
fn new_grid_for_forwarding_test() -> Grid {
|
||
Grid::new(
|
||
10,
|
||
20,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
Rc::new(RefCell::new(HashMap::new())),
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 8,
|
||
height: 16,
|
||
}))),
|
||
Rc::new(RefCell::new(SixelImageStore::default())),
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn csi_14t_forwards_to_host_not_local() {
|
||
// CSI 14t used to synthesize a local "\x1b[4;H;Wt" reply; after the
|
||
// refactor it must be forwarded to the host instead so apps observe
|
||
// the terminal's real window pixel dimensions.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[14t");
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"local reply path must not fire for 14t"
|
||
);
|
||
assert_eq!(grid.pending_forwarded_queries.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries[0],
|
||
crate::host_query::HostQuery::TextAreaPixelSize,
|
||
"forwarded classification must be TextAreaPixelSize"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_16t_forwards_to_host_not_local() {
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[16t");
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
assert_eq!(grid.pending_forwarded_queries.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries[0],
|
||
crate::host_query::HostQuery::CharacterCellPixelSize,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_18t_still_answered_locally() {
|
||
// 18 reports Zellij's own text-area size in cells — Zellij is
|
||
// authoritative for this, do NOT forward.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[18t");
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
assert!(grid.pending_forwarded_queries.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn osc_11_set_stays_local() {
|
||
// OSC 11;<rgb> (set pane default bg) must stay local — Zellij needs
|
||
// to track it for its own rendering.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b]11;rgb:ffff/ffff/ffff\x07");
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"set (not query) must not forward"
|
||
);
|
||
assert!(grid.pane_default_bg.is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn osc_11_query_without_override_forwards_to_host() {
|
||
// When no pane-local override is in place the query must still be
|
||
// forwarded — the host's actual bg is what the app asked for.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
assert!(grid.pane_default_bg.is_none());
|
||
parser.advance(&mut grid, b"\x1b]11;?\x07");
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"no override → no local reply"
|
||
);
|
||
assert_eq!(grid.pending_forwarded_queries.len(), 1);
|
||
assert!(matches!(
|
||
grid.pending_forwarded_queries[0],
|
||
crate::host_query::HostQuery::DefaultBackground { .. }
|
||
));
|
||
}
|
||
|
||
#[test]
|
||
fn osc_10_query_without_override_forwards_to_host() {
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
assert!(grid.pane_default_fg.is_none());
|
||
parser.advance(&mut grid, b"\x1b]10;?\x07");
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
assert_eq!(grid.pending_forwarded_queries.len(), 1);
|
||
assert!(matches!(
|
||
grid.pending_forwarded_queries[0],
|
||
crate::host_query::HostQuery::DefaultForeground { .. }
|
||
));
|
||
}
|
||
|
||
#[test]
|
||
fn set_pane_default_colors_short_circuits_osc_queries() {
|
||
// The CLI path (`zellij action set-pane-color`) lands on
|
||
// `Grid::set_pane_default_colors`. A later OSC 10/11 query must
|
||
// read that override, not be forwarded — the entire point of the
|
||
// CLI override is that apps see the color Zellij is painting, not
|
||
// the underlying host's.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
grid.set_pane_default_colors(Some("#ff8040".to_string()), Some("#102030".to_string()));
|
||
|
||
parser.advance(&mut grid, b"\x1b]10;?\x07");
|
||
parser.advance(&mut grid, b"\x1b]11;?\x07");
|
||
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"overrides set via set_pane_default_colors must suppress forwarding"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 2);
|
||
let fg_reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
let bg_reply = String::from_utf8(grid.pending_messages_to_pty[1].clone()).unwrap();
|
||
assert_eq!(fg_reply, "\u{1b}]10;rgb:ffff/8080/4040\u{7}");
|
||
assert_eq!(bg_reply, "\u{1b}]11;rgb:1010/2020/3030\u{7}");
|
||
}
|
||
|
||
#[test]
|
||
fn osc_11_override_short_circuits_only_the_overridden_channel() {
|
||
// Setting only the bg must not short-circuit fg queries — each
|
||
// channel's override is independent. If only `pane_default_bg` is
|
||
// populated, an OSC 10 query (fg) still goes to the host.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b]11;rgb:1010/2020/3030\x07");
|
||
assert!(grid.pane_default_bg.is_some());
|
||
assert!(grid.pane_default_fg.is_none());
|
||
|
||
parser.advance(&mut grid, b"\x1b]10;?\x07");
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"fg has no override → must forward"
|
||
);
|
||
assert_eq!(grid.pending_forwarded_queries.len(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_2026_dollar_p_stays_local() {
|
||
// DECRQM mode 2026 (synchronised output) is emulated by Zellij
|
||
// itself — never forwarded. The response is the DECRPM form
|
||
// `\x1b[?2026;2$y` (2 = "reset but recognised"; Zellij brackets its
|
||
// own frames, so individual panes are treated as "not enabled").
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[?2026$p");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"CSI ?2026$p is emulated locally, must not forward"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[0], b"\x1b[?2026;2$y",
|
||
"DECRPM reply must use the `$y` suffix per spec"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_2031_dollar_p_when_disabled_replies_reset() {
|
||
// DECRQM mode 2031 (Application Theme Reporting) is per-pane state
|
||
// tracked locally by Zellij. With no prior `CSI ? 2031 h`, the mode
|
||
// is reset, so the DECRPM reply must report value=2.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[?2031$p");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"CSI ?2031$p is answered locally, must not forward"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[0], b"\x1b[?2031;2$y",
|
||
"DECRPM reply must report value=2 (reset) when 2031 is disabled"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_2031_dollar_p_when_enabled_replies_set() {
|
||
// After `CSI ? 2031 h` enables theme-change notifications on this
|
||
// pane, a DECRQM probe must report value=1 (set).
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[?2031h\x1b[?2031$p");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"CSI ?2031$p is answered locally, must not forward"
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[0], b"\x1b[?2031;1$y",
|
||
"DECRPM reply must report value=1 (set) when 2031 is enabled"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_22t_and_23t_stay_local() {
|
||
// CSI 22t / 23t manipulate the pane's title stack — Zellij owns
|
||
// that state, so forwarding would route an app's push/pop to the
|
||
// host's unrelated title stack instead of the visible pane title.
|
||
// They produce no reply; both queues must stay empty after each.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
grid.set_title("hello".to_string());
|
||
|
||
parser.advance(&mut grid, b"\x1b[22;0t");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"22t is a stack op, not a query"
|
||
);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"22t produces no reply"
|
||
);
|
||
|
||
// The push actually landed on Zellij's per-pane stack: restore it.
|
||
grid.set_title("different".to_string());
|
||
parser.advance(&mut grid, b"\x1b[23;0t");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"23t is a stack op, not a query"
|
||
);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"23t produces no reply"
|
||
);
|
||
assert_eq!(
|
||
grid.title.as_deref(),
|
||
Some("hello"),
|
||
"pop should restore the previously-pushed title"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_4_set_stays_local() {
|
||
// OSC 4;<index>;<rgb> writes to Zellij's in-memory palette; only
|
||
// the query form (`OSC 4;N;?`) ever forwards to the host.
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b]4;5;rgb:ffff/0000/0000\x07");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"OSC 4 set must not forward"
|
||
);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"OSC 4 set produces no reply"
|
||
);
|
||
let changed = grid
|
||
.changed_colors
|
||
.expect("changed_colors should be populated by OSC 4 set");
|
||
assert!(changed[5].is_some(), "index 5 should have been written");
|
||
}
|
||
|
||
#[test]
|
||
fn decset_2031_enables_color_palette_notification() {
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
assert!(
|
||
!grid.color_palette_notification_enabled,
|
||
"default state must be disabled"
|
||
);
|
||
parser.advance(&mut grid, b"\x1b[?2031h");
|
||
assert!(
|
||
grid.color_palette_notification_enabled,
|
||
"DECSET 2031 must enable the flag"
|
||
);
|
||
parser.advance(&mut grid, b"\x1b[?2031l");
|
||
assert!(
|
||
!grid.color_palette_notification_enabled,
|
||
"DECRST 2031 must disable the flag"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn push_color_palette_dsr_emits_when_enabled() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
grid.color_palette_notification_enabled = true;
|
||
grid.push_color_palette_dsr(zellij_utils::data::HostTerminalThemeMode::Dark);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[0],
|
||
b"\x1b[?997;1n".to_vec(),
|
||
"Dark mode emits ?997;1n"
|
||
);
|
||
grid.push_color_palette_dsr(zellij_utils::data::HostTerminalThemeMode::Light);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 2);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[1],
|
||
b"\x1b[?997;2n".to_vec(),
|
||
"Light mode emits ?997;2n"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn push_color_palette_dsr_noop_when_disabled() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
assert!(!grid.color_palette_notification_enabled);
|
||
grid.push_color_palette_dsr(zellij_utils::data::HostTerminalThemeMode::Dark);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"no DSR is queued when the app has not opted in via CSI ?2031h"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_996n_pushes_color_palette_mode_query_to_forwarded_queries() {
|
||
use crate::host_query::HostQuery;
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
parser.advance(&mut grid, b"\x1b[?996n");
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries,
|
||
vec![HostQuery::ColorPaletteMode],
|
||
"CSI ?996n must push HostQuery::ColorPaletteMode for Screen to short-circuit"
|
||
);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"Grid must NOT answer locally — Screen owns the host_terminal_theme_mode cache"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn csi_5n_status_query_still_handled_locally() {
|
||
let mut parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
// Plain DSR 5 (no `?` intermediate) is not the new theme query and
|
||
// must continue to receive its `\e[0n` "all good" reply locally.
|
||
parser.advance(&mut grid, b"\x1b[5n");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"DSR 5 is not a forwarded query"
|
||
);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1b[0n".to_vec()],
|
||
"DSR 5 must still produce its local 'all good' reply"
|
||
);
|
||
}
|
||
|
||
fn new_grid_for_nested_frames() -> Grid {
|
||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||
Grid::new(
|
||
41,
|
||
110,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
terminal_emulator_color_codes,
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
sixel_image_store,
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
)
|
||
}
|
||
|
||
fn nested_announce_message() -> zellij_utils::nested_session::NestedSessionMessage {
|
||
zellij_utils::nested_session::NestedSessionMessage::Announce {
|
||
session_name: "guest-session".to_owned(),
|
||
capabilities: vec![zellij_utils::nested_session::NestedSessionCapability::NestedControl],
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn nested_session_frame_is_staged_for_dispatch() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
let message = nested_announce_message();
|
||
vte_parser.advance(
|
||
&mut grid,
|
||
&zellij_utils::nested_session::encode_frame(&message),
|
||
);
|
||
assert_eq!(grid.pending_nested_session_messages, vec![message]);
|
||
}
|
||
|
||
#[test]
|
||
fn nested_session_frame_split_across_feeds_is_staged() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
let message = nested_announce_message();
|
||
let frame = zellij_utils::nested_session::encode_frame(&message);
|
||
let (first_half, second_half) = frame.split_at(frame.len() / 2);
|
||
vte_parser.advance(&mut grid, first_half);
|
||
assert!(grid.pending_nested_session_messages.is_empty());
|
||
vte_parser.advance(&mut grid, second_half);
|
||
assert_eq!(grid.pending_nested_session_messages, vec![message]);
|
||
}
|
||
|
||
#[test]
|
||
fn dcs_with_wrong_magic_param_is_not_staged() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
vte_parser.advance(&mut grid, b"\x1bP26662nAAAA\x1b\\");
|
||
assert!(grid.pending_nested_session_messages.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn dcs_with_intermediates_is_not_staged() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
vte_parser.advance(&mut grid, b"\x1bP26661$nAAAA\x1b\\");
|
||
assert!(grid.pending_nested_session_messages.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn dcs_n_without_params_is_not_staged() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
vte_parser.advance(&mut grid, b"\x1bPnAAAA\x1b\\");
|
||
assert!(grid.pending_nested_session_messages.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn nested_session_frame_with_garbage_payload_is_dropped() {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
vte_parser.advance(&mut grid, b"\x1bP26661n!!!not-base64!!!\x1b\\");
|
||
assert!(grid.pending_nested_session_messages.is_empty());
|
||
}
|
||
|
||
fn serialize_text_bytes(text: &str) -> String {
|
||
text.as_bytes()
|
||
.iter()
|
||
.map(|byte| byte.to_string())
|
||
.collect::<Vec<_>>()
|
||
.join(",")
|
||
}
|
||
|
||
fn render_ui_component(component_name: &str, serialized_params: &str) -> Grid {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = new_grid_for_nested_frames();
|
||
let dcs = format!("\u{1b}Pz{};{}\u{1b}\\", component_name, serialized_params);
|
||
vte_parser.advance(&mut grid, dcs.as_bytes());
|
||
grid
|
||
}
|
||
|
||
fn styled_cells_of(
|
||
grid: &Grid,
|
||
needle: char,
|
||
) -> Vec<crate::panes::terminal_character::CharacterStyles> {
|
||
let mut styles = vec![];
|
||
for line in grid.as_character_lines() {
|
||
for terminal_character in line {
|
||
if terminal_character.character == needle {
|
||
styles.push(*terminal_character.styles);
|
||
}
|
||
}
|
||
}
|
||
styles
|
||
}
|
||
|
||
#[test]
|
||
fn disabled_ribbon_renders_italic_non_bold_unselected() {
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
let grid = render_ui_component("ribbon", &format!("d{}", serialize_text_bytes("RES")));
|
||
let letter_styles = styled_cells_of(&grid, 'R');
|
||
assert!(!letter_styles.is_empty());
|
||
for style in letter_styles {
|
||
assert_eq!(style.italic, Some(AnsiCode::On));
|
||
assert_ne!(style.bold, Some(AnsiCode::On));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn disabled_text_with_opaque_flag_keeps_a_background() {
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
let grid = render_ui_component("text", &format!("zd{}", serialize_text_bytes("RES")));
|
||
let letter_styles = styled_cells_of(&grid, 'R');
|
||
assert!(!letter_styles.is_empty());
|
||
for style in letter_styles {
|
||
assert_eq!(style.italic, Some(AnsiCode::On));
|
||
assert_ne!(style.bold, Some(AnsiCode::On));
|
||
assert!(style.background.is_some());
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn disabled_text_renders_italic_non_bold() {
|
||
use crate::panes::terminal_character::AnsiCode;
|
||
let grid = render_ui_component("text", &format!("d{}", serialize_text_bytes("RES")));
|
||
let letter_styles = styled_cells_of(&grid, 'R');
|
||
assert!(!letter_styles.is_empty());
|
||
for style in letter_styles {
|
||
assert_eq!(style.italic, Some(AnsiCode::On));
|
||
assert_ne!(style.bold, Some(AnsiCode::On));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn ui_component_flag_prefixes_parse_order_independently() {
|
||
let baseline = render_ui_component("text", &format!("xzd{}", serialize_text_bytes("RES")));
|
||
let baseline_styles = styled_cells_of(&baseline, 'R');
|
||
for prefix in ["xdz", "zxd", "zdx", "dxz", "dzx"] {
|
||
let grid = render_ui_component(
|
||
"text",
|
||
&format!("{}{}", prefix, serialize_text_bytes("RES")),
|
||
);
|
||
assert_eq!(
|
||
styled_cells_of(&grid, 'R'),
|
||
baseline_styles,
|
||
"prefix {prefix} should parse the same flags as xzd"
|
||
);
|
||
}
|
||
}
|
||
|
||
use crate::panes::kitty_graphics::{InterceptorResult, KittyApcInterceptor, KittyHostSupport};
|
||
use crate::panes::sixel::PixelRect;
|
||
|
||
const KITTY_PNG_2X2: [u8; 75] = [
|
||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d,
|
||
0x24, 0x00, 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xf8, 0xcf, 0xc0, 0xf0,
|
||
0x1f, 0x0c, 0x81, 0x34, 0x18, 0x00, 0x00, 0x49, 0xc8, 0x09, 0xf7, 0x03, 0xd9, 0x64, 0xf1, 0x00,
|
||
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||
];
|
||
|
||
fn new_kitty_grid(rows: usize, columns: usize) -> (Grid, Rc<RefCell<KittyImageStore>>) {
|
||
let kitty_image_store = Rc::new(RefCell::new(KittyImageStore::default()));
|
||
let grid = Grid::new(
|
||
rows,
|
||
columns,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
Rc::new(RefCell::new(HashMap::new())),
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(Some(SizeInPixels {
|
||
width: 10,
|
||
height: 20,
|
||
}))),
|
||
Rc::new(RefCell::new(SixelImageStore::default())),
|
||
kitty_image_store.clone(),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
(grid, kitty_image_store)
|
||
}
|
||
|
||
fn feed_kitty_bytes(
|
||
grid: &mut Grid,
|
||
vte_parser: &mut vte::Parser,
|
||
interceptor: &mut KittyApcInterceptor,
|
||
bytes: &[u8],
|
||
) {
|
||
for byte in bytes {
|
||
match interceptor.advance(*byte) {
|
||
InterceptorResult::Forward(fwd) => {
|
||
vte_parser.advance(grid, fwd.as_slice());
|
||
},
|
||
InterceptorResult::Swallow => {},
|
||
InterceptorResult::Captured(cmd) => {
|
||
grid.handle_kitty_apc(&cmd);
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
fn kitty_apc(control: &str, payload: &[u8]) -> Vec<u8> {
|
||
let mut out = Vec::new();
|
||
out.extend_from_slice(b"\x1b_G");
|
||
out.extend_from_slice(control.as_bytes());
|
||
if !payload.is_empty() {
|
||
out.push(b';');
|
||
out.extend_from_slice(BASE64_ENCODER.encode(payload).as_bytes());
|
||
}
|
||
out.extend_from_slice(b"\x1b\\");
|
||
out
|
||
}
|
||
|
||
fn rgb_raster(width: usize, height: usize) -> Vec<u8> {
|
||
(0..width * height * 3).map(|i| (i % 251) as u8).collect()
|
||
}
|
||
|
||
fn rgba_raster(width: usize, height: usize) -> Vec<u8> {
|
||
(0..width * height * 4).map(|i| (i % 251) as u8).collect()
|
||
}
|
||
|
||
fn kitty_placement_id_set(grid: &Grid) -> Vec<(u32, u32)> {
|
||
let mut ids: Vec<(u32, u32)> = grid
|
||
.kitty_placements()
|
||
.iter()
|
||
.map(|placement| (placement.image_id, placement.placement_id))
|
||
.collect();
|
||
ids.sort();
|
||
ids
|
||
}
|
||
|
||
fn build_three_kitty_placements(
|
||
grid: &mut Grid,
|
||
vte_parser: &mut vte::Parser,
|
||
interceptor: &mut KittyApcInterceptor,
|
||
) {
|
||
feed_kitty_bytes(
|
||
grid,
|
||
vte_parser,
|
||
interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
feed_kitty_bytes(grid, vte_parser, interceptor, b"\x1b[6;11H");
|
||
feed_kitty_bytes(
|
||
grid,
|
||
vte_parser,
|
||
interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=2,z=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
feed_kitty_bytes(grid, vte_parser, interceptor, b"\x1b[11;21H");
|
||
feed_kitty_bytes(
|
||
grid,
|
||
vte_parser,
|
||
interceptor,
|
||
&kitty_apc("a=T,f=32,s=10,v=20,i=5,z=-1,C=1", &rgba_raster(10, 20)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_transmit_and_display_raw_rgb() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 0,
|
||
width: 20,
|
||
height: 40,
|
||
}
|
||
);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_transmit_and_display_png() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=100,i=1", &KITTY_PNG_2X2),
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 0,
|
||
width: 2,
|
||
height: 2,
|
||
}
|
||
);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_cursor_advances_after_display() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1", &rgb_raster(20, 40)),
|
||
);
|
||
let (cursor_x, cursor_y, _) = grid.cursor_coordinates().unwrap();
|
||
assert_eq!((cursor_x, cursor_y), (2, 1));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_cursor_unmoved_with_c1() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
let (cursor_x, cursor_y, _) = grid.cursor_coordinates().unwrap();
|
||
assert_eq!((cursor_x, cursor_y), (0, 0));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_survives_text_overwrite_and_erase() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"XXXX");
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
for erase_sequence in [
|
||
&b"\x1b[K"[..],
|
||
&b"\x1b[1K"[..],
|
||
&b"\x1b[0J"[..],
|
||
&b"\x1b[1J"[..],
|
||
] {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, erase_sequence);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
}
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_replace_same_ids_moves_placement() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,p=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[10;5H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=p,i=1,p=1,C=1", b""),
|
||
);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 40,
|
||
y: 180,
|
||
width: 20,
|
||
height: 40,
|
||
}
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_all_visible() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=A", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![]);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_by_image_id() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=i,i=2", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=2,p=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=p,i=2,p=2,C=1", b""),
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=i,i=2,p=1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(2, 2)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 3200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=I,i=2", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 2);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 4000);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_by_image_number() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,I=7,C=1", &rgb_raster(10, 20)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 4);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 8000);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=n,I=7", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 8000);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=N,I=7", b""),
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_cursor_intersecting() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=c", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=C", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 2);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 6400);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_cell_intersecting() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=p,x=11,y=6", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=P,x=11,y=6", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 4000);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_cell_with_z() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=q,x=11,y=6,z=5", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0), (5, 0)]);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=q,x=11,y=6,z=1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=Q,x=11,y=6,z=1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 4000);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_column() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=x,x=1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(2, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=X,x=1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(2, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 4000);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_row() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=y,y=6", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=Y,y=6", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 4000);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_by_z_index() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=z,z=-1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=Z,z=-1", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(1, 0), (2, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 6400);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_image_id_range() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=r,x=1,y=2", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 7200);
|
||
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=R,x=1,y=2", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(5, 0)]);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 800);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_delete_image_id_range_with_omitted_lower_bound() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
build_three_kitty_placements(&mut grid, &mut vte_parser, &mut interceptor);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=d,d=r,y=2", b""),
|
||
);
|
||
assert_eq!(kitty_placement_id_set(&grid), vec![(5, 0)]);
|
||
let replies: Vec<String> = grid
|
||
.pending_messages_to_pty
|
||
.iter()
|
||
.map(|bytes| String::from_utf8_lossy(bytes).into_owned())
|
||
.collect();
|
||
assert!(
|
||
!replies.iter().any(|reply| reply.contains("EINVAL")),
|
||
"d=r with omitted lower bound must not emit EINVAL, got {:?}",
|
||
replies
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_retransmit_to_existing_id_replaces_image() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=32,s=4,v=4,i=1,C=1", &rgba_raster(4, 4)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 64);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=t,f=32,s=2,v=2,i=1", &rgba_raster(2, 2)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(kitty_image_store.borrow().total_bytes(), 16);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_c_r_scaling_produces_exact_cell_rect_and_variant() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=10,c=3,r=2,i=1", &rgb_raster(10, 10)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
let placement = &grid.kitty_placements()[0];
|
||
assert_eq!(placement.dest_cells, (3, 2));
|
||
assert_eq!(
|
||
placement.display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 0,
|
||
width: 30,
|
||
height: 40,
|
||
}
|
||
);
|
||
let internal_id = placement.internal_id;
|
||
assert_eq!(
|
||
kitty_image_store
|
||
.borrow()
|
||
.scaled_variant(internal_id, placement.scaled_image().unwrap())
|
||
.unwrap()
|
||
.len(),
|
||
30 * 40 * 4
|
||
);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_aspect_fit_single_axis() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=10,c=4,i=1", &rgb_raster(10, 10)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
let placement = &grid.kitty_placements()[0];
|
||
assert_eq!(placement.dest_cells, (4, 2));
|
||
assert_eq!(
|
||
placement.display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 0,
|
||
width: 40,
|
||
height: 40,
|
||
}
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_yazi_kgpold_stream_roundtrip() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
let raster = rgb_raster(2, 2);
|
||
let full_b64 = BASE64_ENCODER.encode(&raster);
|
||
let (b64_chunk_one, b64_chunk_two) = full_b64.split_at(8);
|
||
let mut stream = Vec::new();
|
||
stream.extend_from_slice(
|
||
format!(
|
||
"\x1b_Gq=2,a=T,z=-1,C=1,f=24,s=2,v=2,m=1;{}\x1b\\",
|
||
b64_chunk_one
|
||
)
|
||
.as_bytes(),
|
||
);
|
||
stream.extend_from_slice(format!("\x1b_Gm=0;{}\x1b\\", b64_chunk_two).as_bytes());
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, &stream);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
let placement = &grid.kitty_placements()[0];
|
||
assert_eq!(placement.z_index, -1);
|
||
assert_eq!(placement.image_id, 0);
|
||
let (cursor_x, cursor_y, _) = grid.cursor_coordinates().unwrap();
|
||
assert_eq!((cursor_x, cursor_y), (0, 0));
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Gq=2,a=d,d=A\x1b\\",
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_csi_2j_clears_placements() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2J");
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_ris_clears_placements_and_store() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1bc");
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_image_in_alternate_buffer() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[?1049h");
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=32,s=10,v=20,i=2,C=1", &rgba_raster(10, 20)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 2);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[?1049l");
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_snapshot!(format!("{:?}", grid));
|
||
assert_eq!(
|
||
kitty_image_store.borrow().image_count(),
|
||
1,
|
||
"alternate screen image was freed when leaving the alternate screen"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placements_are_reaped_when_scrolled_off() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,i=1,C=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
for _ in 0..10_040 {
|
||
grid.add_canonical_line();
|
||
}
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_scroll_region_moves_inside_placement_and_clips_straddler() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2;5r");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,i=1,C=1", &rgb_raster(10, 20)),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[5;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=40,i=2,C=1", &rgb_raster(10, 40)),
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 40,
|
||
width: 10,
|
||
height: 20,
|
||
}
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[1].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 80,
|
||
width: 10,
|
||
height: 40,
|
||
}
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\n");
|
||
assert_eq!(grid.kitty_placement_count(), 2);
|
||
let placement_a = &grid.kitty_placements()[0];
|
||
assert_eq!(
|
||
placement_a.display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 20,
|
||
width: 10,
|
||
height: 20,
|
||
}
|
||
);
|
||
assert_eq!(placement_a.emit_y, 0);
|
||
let placement_b = &grid.kitty_placements()[1];
|
||
assert_eq!(
|
||
placement_b.display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 100,
|
||
width: 10,
|
||
height: 20,
|
||
}
|
||
);
|
||
assert_eq!(placement_b.emit_y, 20);
|
||
assert_eq!(placement_b.emit_x, 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_query_probe_exact_bytes() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Ga=q,i=31,s=1,v=1,t=d,f=24;AAAA\x1b\\",
|
||
);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1b_Gi=31;OK\x1b\\".to_vec()]
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_display_unknown_id_enoent() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=p,i=10", &[]),
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert!(reply.starts_with("\x1b_Gi=10;ENOENT"));
|
||
assert!(reply.ends_with("\x1b\\"));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_quiet_one_suppresses_ok_not_errors() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=2,v=2,i=1,q=1,C=1", &rgb_raster(2, 2)),
|
||
);
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=p,i=99,q=1", &[]),
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert!(reply.contains("i=99;ENOENT"));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_quiet_two_suppresses_everything() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=2,v=2,i=1,q=2,C=1", &rgb_raster(2, 2)),
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=p,i=99,q=2", &[]),
|
||
);
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_transmit_with_image_number_echoes_assigned_id() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=t,I=5,f=24,s=2,v=2", &rgb_raster(2, 2)),
|
||
);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![format!("\u{1b}_Gi={},I=5;OK\u{1b}\\", u32::MAX).into_bytes()]
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_anonymous_transmit_and_display_is_silent() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=2,v=2,C=1", &rgb_raster(2, 2)),
|
||
);
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_malformed_control_einval_without_ids() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Gnot-a-kv\x1b\\",
|
||
);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1b_G;EINVAL:malformed control data\x1b\\".to_vec()]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_icat_detection_sequence() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
let dir = tempfile::tempdir().unwrap();
|
||
let path = dir.path().join("icat-detect.bin");
|
||
std::fs::write(&path, [1u8, 2, 3]).unwrap();
|
||
let path_bytes = path.to_str().unwrap().as_bytes();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Gt=d,a=q,i=1,s=1,v=1,f=24,S=3;MTIz\x1b\\",
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
format!(
|
||
"\x1b_Gt=t,a=q,i=2,s=1,v=1,f=24,S={};{}\x1b\\",
|
||
path_bytes.len(),
|
||
BASE64_ENCODER.encode(path_bytes)
|
||
)
|
||
.as_bytes(),
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
format!(
|
||
"\x1b_Gt=s,a=q,i=3,s=1,v=1,f=24,S=3;{}\x1b\\",
|
||
BASE64_ENCODER.encode(b"/some-shm")
|
||
)
|
||
.as_bytes(),
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 3);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[0],
|
||
b"\x1b_Gi=1;OK\x1b\\".to_vec()
|
||
);
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty[1],
|
||
b"\x1b_Gi=2;OK\x1b\\".to_vec()
|
||
);
|
||
let third = String::from_utf8(grid.pending_messages_to_pty[2].clone()).unwrap();
|
||
assert!(third.starts_with("\x1b_Gi=3;ENOTSUPPORTED"));
|
||
assert!(third.ends_with("\x1b\\"));
|
||
assert!(path.starts_with(std::env::temp_dir()));
|
||
assert!(!path.exists());
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_reply_query_when_host_unsupported() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
grid.update_kitty_host_support(KittyHostSupport::Unsupported);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Ga=q,i=31,s=1,v=1,t=d,f=24;AAAA\x1b\\",
|
||
);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert!(reply.starts_with("\x1b_Gi=31;ENOTSUPPORTED"));
|
||
assert!(reply.ends_with("\x1b\\"));
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_disabled_protocol_answers_queries_with_silence() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
grid.update_kitty_host_support(KittyHostSupport::ProtocolDisabled);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b_Ga=q,i=31,s=1,v=1,t=d,f=24;AAAA\x1b\\",
|
||
);
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"a disabled protocol must not reply to queries at all"
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_disabled_protocol_ignores_transmitted_images() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
grid.update_kitty_host_support(KittyHostSupport::ProtocolDisabled);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
assert!(grid.pending_messages_to_pty.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_disabled_protocol_does_not_leak_apc_bytes_into_the_grid() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(20, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
grid.update_kitty_host_support(KittyHostSupport::ProtocolDisabled);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"a");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"b");
|
||
assert_eq!(row_text(&grid.viewport[0]), "ab");
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_image_put_cursor_positions() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,i=1", &rgb_raster(10, 20)),
|
||
);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((1, 0))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_image_put_scaled_cursor() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=40,c=3,i=1", &rgb_raster(20, 40)),
|
||
);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((3, 2))
|
||
);
|
||
assert_eq!(grid.kitty_placements()[0].dest_cells, (3, 3));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_image_put_full_width_wraps() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=100,v=20,i=1", &rgb_raster(100, 20)),
|
||
);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((0, 1))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_image_put_pixel_offsets_cursor() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,X=5,Y=5,i=1", &rgb_raster(10, 20)),
|
||
);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((2, 1))
|
||
);
|
||
assert_eq!(grid.kitty_placements()[0].dest_cells, (2, 2));
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_bottom_row_placement_scrolls() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[5;1H");
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=40,i=1", &rgb_raster(10, 40)),
|
||
);
|
||
assert_eq!(grid.lines_above.len(), 1);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((1, 4))
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 80,
|
||
width: 10,
|
||
height: 40,
|
||
}
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_bottom_row_placement_c1_does_not_scroll() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[5;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=40,i=1,C=1", &rgb_raster(10, 40)),
|
||
);
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
assert_eq!(
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y)),
|
||
Some((0, 4))
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
PixelRect {
|
||
x: 0,
|
||
y: 80,
|
||
width: 10,
|
||
height: 40,
|
||
}
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_full_screen_index_scroll_reaps_image() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,i=1,C=1", &rgb_raster(10, 20)),
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
let scroll_buffer_size = *SCROLL_BUFFER_SIZE.get().unwrap();
|
||
for _ in 0..(scroll_buffer_size + grid.height) {
|
||
grid.add_canonical_line();
|
||
}
|
||
let _ = grid.read_changes(0, 0);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_margin_scroll_leaves_outside_images_untouched() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=10,v=20,i=1,C=1", &rgb_raster(10, 20)),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2;4r");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[4;1H");
|
||
let outside_before = grid.kitty_placements()[0].display_rect;
|
||
for _ in 0..3 {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\n");
|
||
}
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(grid.kitty_placements()[0].display_rect, outside_before);
|
||
assert_eq!(grid.kitty_placements()[0].emit_y, 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_conformance_transmit_with_both_id_and_number_is_einval() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(5, 10);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=t,f=24,s=1,v=1,I=1,i=3", &rgb_raster(1, 1)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 0);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 1);
|
||
let reply = String::from_utf8(grid.pending_messages_to_pty[0].clone()).unwrap();
|
||
assert!(
|
||
reply.contains("EINVAL"),
|
||
"expected EINVAL for transmit with both i and I, got {:?}",
|
||
reply
|
||
);
|
||
}
|
||
|
||
fn kitty_image_px_below_canonical_line(grid: &Grid, canonical_index: usize) -> isize {
|
||
let placement = &grid.kitty_placements()[0];
|
||
let cell_height = 20isize;
|
||
let line_start = buffer_cell_row_of_canonical_line(grid, canonical_index);
|
||
placement.display_rect.y - line_start * cell_height
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_keeps_screen_row_across_width_reflow() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(6, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
for line in 0..5 {
|
||
let text = format!("row{}-{}", line, "x".repeat(30));
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
text.as_bytes(),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\r\n");
|
||
}
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
let anchor_canonical_line = grid.kitty_placements()[0].vertical_anchor.canonical_line;
|
||
let px_below_before = kitty_image_px_below_canonical_line(&grid, anchor_canonical_line);
|
||
let lines_above_before = grid.lines_above.len();
|
||
|
||
grid.change_size(6, 10);
|
||
assert_ne!(
|
||
grid.lines_above.len(),
|
||
lines_above_before,
|
||
"width reflow must change the scrollback row count for this test to be meaningful"
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
kitty_image_px_below_canonical_line(&grid, anchor_canonical_line),
|
||
px_below_before,
|
||
"image must keep the same pixel offset below its anchor canonical line (track the text) across a narrowing width reflow"
|
||
);
|
||
|
||
grid.change_size(6, 40);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
kitty_image_px_below_canonical_line(&grid, anchor_canonical_line),
|
||
px_below_before,
|
||
"image must keep the same pixel offset below its anchor canonical line (track the text) across a widening width reflow"
|
||
);
|
||
}
|
||
|
||
fn kitty_image_absolute_cell_row(grid: &Grid) -> isize {
|
||
let placement = &grid.kitty_placements()[0];
|
||
let cell_height = 20isize;
|
||
placement.display_rect.y.div_euclid(cell_height)
|
||
}
|
||
|
||
fn buffer_cell_row_of_canonical_line(grid: &Grid, canonical_index: usize) -> isize {
|
||
let mut canonical_seen = 0usize;
|
||
let mut wrapped_row = 0isize;
|
||
for row in grid.lines_above.iter().chain(grid.viewport.iter()) {
|
||
if row.is_canonical {
|
||
if canonical_seen == canonical_index {
|
||
return wrapped_row;
|
||
}
|
||
canonical_seen += 1;
|
||
}
|
||
wrapped_row += 1;
|
||
}
|
||
-1
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_follows_text_across_width_reflow() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
for line in 0..3 {
|
||
let text = format!("r{}-{}", line, "x".repeat(34));
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
text.as_bytes(),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\r\n");
|
||
}
|
||
let image_canonical_line = 3usize;
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[7;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
|
||
let image_row_before = kitty_image_absolute_cell_row(&grid);
|
||
let text_row_before = buffer_cell_row_of_canonical_line(&grid, image_canonical_line);
|
||
assert_eq!(
|
||
image_row_before, text_row_before,
|
||
"image top must sit on the buffer cell row of its canonical line before reflow"
|
||
);
|
||
|
||
grid.change_size(10, 10);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
let image_row_narrow = kitty_image_absolute_cell_row(&grid);
|
||
let text_row_narrow = buffer_cell_row_of_canonical_line(&grid, image_canonical_line);
|
||
assert_eq!(
|
||
image_row_narrow, text_row_narrow,
|
||
"image must track its canonical text line after narrowing reflow (lines above rewrap into more rows)"
|
||
);
|
||
assert!(
|
||
text_row_narrow > text_row_before,
|
||
"narrowing must push the image's canonical line further down the buffer for the test to be meaningful"
|
||
);
|
||
|
||
grid.change_size(10, 40);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
let image_row_wide = kitty_image_absolute_cell_row(&grid);
|
||
let text_row_wide = buffer_cell_row_of_canonical_line(&grid, image_canonical_line);
|
||
assert_eq!(
|
||
image_row_wide, text_row_wide,
|
||
"image must track its canonical text line after widening reflow (lines above rewrap into fewer rows)"
|
||
);
|
||
assert!(
|
||
text_row_wide < text_row_narrow,
|
||
"widening must pull the image's canonical line back up for the test to be meaningful"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_moves_with_csi_insert_lines() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[6;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
let row_before = kitty_image_absolute_cell_row(&grid);
|
||
assert_eq!(row_before, 5);
|
||
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
let inserted = 2usize;
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2L");
|
||
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
row_before + inserted as isize,
|
||
"image below the insertion point must shift down by the inserted line count"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_moves_with_csi_delete_lines() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[6;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
let row_before = kitty_image_absolute_cell_row(&grid);
|
||
assert_eq!(row_before, 5);
|
||
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
let deleted = 2usize;
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2M");
|
||
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
row_before - deleted as isize,
|
||
"image below the deletion point must shift up by the deleted line count"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_in_deleted_range_is_removed() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[4;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(kitty_image_absolute_cell_row(&grid), 3);
|
||
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[4;1H");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1M");
|
||
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
0,
|
||
"an image whose only row is deleted by CSI M must be removed"
|
||
);
|
||
}
|
||
|
||
fn kitty_row_starts_the_marker_line(row: &super::super::Row) -> bool {
|
||
row.is_canonical && row_text(row).starts_with("MARKER")
|
||
}
|
||
|
||
fn kitty_marker_is_below_the_viewport(grid: &Grid) -> bool {
|
||
grid.lines_below
|
||
.iter()
|
||
.any(kitty_row_starts_the_marker_line)
|
||
}
|
||
|
||
fn kitty_marker_absolute_cell_row(grid: &Grid) -> Option<isize> {
|
||
grid.lines_above
|
||
.iter()
|
||
.chain(grid.viewport.iter())
|
||
.position(kitty_row_starts_the_marker_line)
|
||
.map(|row| row as isize)
|
||
}
|
||
|
||
fn kitty_marker_extended_absolute_cell_row(grid: &Grid) -> Option<isize> {
|
||
grid.lines_above
|
||
.iter()
|
||
.chain(grid.viewport.iter())
|
||
.chain(grid.lines_below.iter())
|
||
.position(kitty_row_starts_the_marker_line)
|
||
.map(|row| row as isize)
|
||
}
|
||
|
||
fn kitty_scrollback_has_merged_wrapped_rows(grid: &Grid) -> bool {
|
||
grid.lines_above.iter().any(|row| row.width() > grid.width)
|
||
}
|
||
|
||
fn kitty_marker_viewport_row(grid: &Grid) -> Option<usize> {
|
||
let marker_row = kitty_marker_absolute_cell_row(grid)?;
|
||
let viewport_row = marker_row - grid.lines_above.len() as isize;
|
||
if viewport_row >= 0 && (viewport_row as usize) < grid.viewport.len() {
|
||
Some(viewport_row as usize)
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_tracks_text_when_a_wrapped_row_moves_into_the_scrollback() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(5, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"AAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"MARKER");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[3;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid)
|
||
);
|
||
for _ in 0..5 {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\r\nx");
|
||
}
|
||
assert!(kitty_scrollback_has_merged_wrapped_rows(&grid));
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
"the image must stay on the row of its marker line after a wrapped row is merged into the scrollback"
|
||
);
|
||
}
|
||
|
||
fn kitty_wrapped_scrollback_setup(
|
||
wrapped_line_width: usize,
|
||
) -> (
|
||
Grid,
|
||
Rc<RefCell<KittyImageStore>>,
|
||
vte::Parser,
|
||
KittyApcInterceptor,
|
||
) {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(5, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
let wrapped_line = format!("{}\r\n", "A".repeat(wrapped_line_width));
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
wrapped_line.as_bytes(),
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"MARKER");
|
||
let marker_viewport_row = kitty_marker_viewport_row(&grid).expect("marker must be visible");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
format!("\x1b[{};1H", marker_viewport_row + 1).as_bytes(),
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
"the image must start on the row of its marker line"
|
||
);
|
||
for _ in 0..5 {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\r\nx");
|
||
}
|
||
assert!(
|
||
kitty_scrollback_has_merged_wrapped_rows(&grid),
|
||
"the wrapped line must have been merged into a single scrollback row"
|
||
);
|
||
(grid, kitty_image_store, vte_parser, interceptor)
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_tracks_text_when_a_wrapped_row_is_split_out_of_the_scrollback() {
|
||
let (mut grid, _kitty_image_store, _vte_parser, _interceptor) =
|
||
kitty_wrapped_scrollback_setup(25);
|
||
for step in 0..2 {
|
||
grid.scroll_up_one_line();
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"scrolling the scrollback must not free the image (step {})",
|
||
step
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
"the image must stay on the row of its marker line after scrollback scroll up (step {})",
|
||
step
|
||
);
|
||
}
|
||
assert!(
|
||
!grid.viewport[0].is_canonical,
|
||
"the wrapped scrollback row must have been split back into the viewport"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_tracks_text_when_a_multiply_wrapped_row_is_split_out_of_the_scrollback() {
|
||
let (mut grid, _kitty_image_store, _vte_parser, _interceptor) =
|
||
kitty_wrapped_scrollback_setup(45);
|
||
let mut non_canonical_viewport_rows = 0;
|
||
for step in 0..4 {
|
||
grid.scroll_up_one_line();
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"scrolling the scrollback must not free the image (step {})",
|
||
step
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
"the image must stay on the row of its marker line after scrollback scroll up (step {})",
|
||
step
|
||
);
|
||
non_canonical_viewport_rows = grid.viewport.iter().filter(|row| !row.is_canonical).count();
|
||
}
|
||
assert!(
|
||
non_canonical_viewport_rows >= 2,
|
||
"a canonical line wrapping into three rows must be split into at least two continuation rows in the viewport"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_geometry_survives_a_scrollback_scroll_round_trip() {
|
||
let (mut grid, _kitty_image_store, _vte_parser, _interceptor) =
|
||
kitty_wrapped_scrollback_setup(45);
|
||
let display_rect_before = grid.kitty_placements()[0].display_rect;
|
||
let anchor_before = grid.kitty_placements()[0].vertical_anchor;
|
||
let lines_above_before = grid.lines_above.len();
|
||
let marker_row_before = kitty_marker_absolute_cell_row(&grid);
|
||
for _ in 0..4 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
for _ in 0..4 {
|
||
grid.scroll_down_one_line();
|
||
}
|
||
assert_eq!(grid.lines_above.len(), lines_above_before);
|
||
assert_eq!(
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
marker_row_before,
|
||
"the round trip must restore the marker line row"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].display_rect,
|
||
display_rect_before,
|
||
"the round trip must restore the placement pixel geometry"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].vertical_anchor,
|
||
anchor_before,
|
||
"the round trip must restore the placement anchor"
|
||
);
|
||
}
|
||
|
||
fn kitty_marker_canonical_line_index(grid: &Grid) -> Option<usize> {
|
||
let mut canonical_lines_seen = 0usize;
|
||
for row in grid.lines_above.iter().chain(grid.viewport.iter()) {
|
||
if row.is_canonical {
|
||
canonical_lines_seen += 1;
|
||
}
|
||
if row_text(row) == "MARKER" {
|
||
return Some(canonical_lines_seen.saturating_sub(1));
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_anchor_survives_scroll_buffer_front_eviction() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
let scroll_buffer_size = *SCROLL_BUFFER_SIZE.get().unwrap();
|
||
for _ in 0..(scroll_buffer_size + grid.height) {
|
||
grid.add_canonical_line();
|
||
}
|
||
assert_eq!(
|
||
grid.lines_above.len(),
|
||
scroll_buffer_size,
|
||
"the scroll buffer must be full for this test to be meaningful"
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"MARKER");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid)
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].vertical_anchor.canonical_line,
|
||
kitty_marker_canonical_line_index(&grid).expect("marker must exist")
|
||
);
|
||
let front_drops_before = grid.kitty_grid.front_drops();
|
||
|
||
grid.change_size(5, 20);
|
||
|
||
let evicted_lines = grid.kitty_grid.front_drops() - front_drops_before;
|
||
assert!(
|
||
evicted_lines >= 5,
|
||
"shrinking the viewport must evict rows from the front of a full scroll buffer"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"a placement below the evicted rows must survive the eviction"
|
||
);
|
||
let marker_canonical_line =
|
||
kitty_marker_canonical_line_index(&grid).expect("marker must still exist");
|
||
assert_eq!(
|
||
grid.kitty_placements()[0].vertical_anchor.canonical_line,
|
||
marker_canonical_line,
|
||
"the placement anchor must still resolve to its marker text line after front eviction"
|
||
);
|
||
assert_eq!(
|
||
Some(kitty_image_absolute_cell_row(&grid)),
|
||
kitty_marker_absolute_cell_row(&grid),
|
||
"the image must stay on the row of its marker line across scroll buffer front eviction"
|
||
);
|
||
}
|
||
|
||
fn kitty_marker_with_image_on_first_row(
|
||
rows: usize,
|
||
columns: usize,
|
||
) -> (
|
||
Grid,
|
||
Rc<RefCell<KittyImageStore>>,
|
||
vte::Parser,
|
||
KittyApcInterceptor,
|
||
) {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(rows, columns);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"MARKER");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(grid.kitty_placement_count(), 1);
|
||
assert_eq!(kitty_marker_absolute_cell_row(&grid), Some(0));
|
||
assert_eq!(kitty_image_absolute_cell_row(&grid), 0);
|
||
(grid, kitty_image_store, vte_parser, interceptor)
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_top_anchored_scroll_up_keeps_image_in_scrollback() {
|
||
let (mut grid, kitty_image_store, mut vte_parser, mut interceptor) =
|
||
kitty_marker_with_image_on_first_row(5, 20);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[S");
|
||
assert_eq!(
|
||
grid.lines_above.len(),
|
||
1,
|
||
"a top anchored scroll region moves the top row into the scrollback"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"an image whose text was preserved into the scrollback must not be freed"
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(kitty_marker_absolute_cell_row(&grid), Some(0));
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
0,
|
||
"the image must follow its text into the scrollback"
|
||
);
|
||
assert_eq!(grid.kitty_placements()[0].display_rect.height, 20);
|
||
assert_eq!(grid.kitty_placements()[0].emit_y, 0);
|
||
|
||
grid.scroll_up_one_line();
|
||
assert_eq!(grid.lines_above.len(), 0);
|
||
assert_eq!(kitty_marker_absolute_cell_row(&grid), Some(0));
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
0,
|
||
"the image must still sit on its marker row when scrolled back into view"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_top_anchored_delete_line_keeps_image_in_scrollback() {
|
||
let (mut grid, kitty_image_store, mut vte_parser, mut interceptor) =
|
||
kitty_marker_with_image_on_first_row(5, 20);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1M");
|
||
assert_eq!(
|
||
grid.lines_above.len(),
|
||
1,
|
||
"a top anchored line deletion moves the deleted row into the scrollback"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"an image whose text was preserved into the scrollback must not be freed"
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 1);
|
||
assert_eq!(kitty_marker_absolute_cell_row(&grid), Some(0));
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
0,
|
||
"the image must follow its text into the scrollback"
|
||
);
|
||
|
||
grid.scroll_up_one_line();
|
||
assert_eq!(kitty_marker_absolute_cell_row(&grid), Some(0));
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
0,
|
||
"the image must still sit on its marker row when scrolled back into view"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_non_top_anchored_region_scroll_still_frees_image() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(5, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2;5r");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[2;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(kitty_image_absolute_cell_row(&grid), 1);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[S");
|
||
assert_eq!(
|
||
grid.lines_above.len(),
|
||
0,
|
||
"a region that does not start at the top of the viewport discards its top row"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
0,
|
||
"an image scrolled out of a non top anchored region must be freed"
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_alternate_screen_top_anchored_scroll_frees_image() {
|
||
let (mut grid, kitty_image_store) = new_kitty_grid(5, 20);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[?1049h");
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[1;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
assert_eq!(kitty_image_absolute_cell_row(&grid), 0);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[S");
|
||
assert_eq!(
|
||
grid.lines_above.len(),
|
||
0,
|
||
"the alternate screen has no scrollback"
|
||
);
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
0,
|
||
"an image scrolled off the alternate screen must be freed"
|
||
);
|
||
assert_eq!(kitty_image_store.borrow().image_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn kitty_placement_rejoins_its_text_after_editing_while_scrolled_back() {
|
||
let (mut grid, _kitty_image_store) = new_kitty_grid(10, 40);
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut interceptor = KittyApcInterceptor::new();
|
||
for line in 0..20 {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[10;1H");
|
||
let text = format!("\r\npad{}", line);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
text.as_bytes(),
|
||
);
|
||
}
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b[10;1H\r\nMARKER",
|
||
);
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[10;1H");
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
&kitty_apc("a=T,f=24,s=20,v=20,i=1,C=1", &rgb_raster(20, 20)),
|
||
);
|
||
for line in 0..3 {
|
||
feed_kitty_bytes(&mut grid, &mut vte_parser, &mut interceptor, b"\x1b[10;1H");
|
||
let text = format!("\r\ntail{}", line);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
text.as_bytes(),
|
||
);
|
||
}
|
||
let _ = grid.read_changes(0, 0);
|
||
for _ in 0..8 {
|
||
grid.scroll_up_one_line();
|
||
}
|
||
let _ = grid.read_changes(0, 0);
|
||
assert!(
|
||
kitty_marker_is_below_the_viewport(&grid),
|
||
"the marker line must be scrolled below the viewport for this scenario"
|
||
);
|
||
feed_kitty_bytes(
|
||
&mut grid,
|
||
&mut vte_parser,
|
||
&mut interceptor,
|
||
b"\x1b[1;1H\x1b[1M",
|
||
);
|
||
let _ = grid.read_changes(0, 0);
|
||
grid.reset_viewport();
|
||
let _ = grid.read_changes(0, 0);
|
||
assert!(
|
||
!kitty_marker_is_below_the_viewport(&grid),
|
||
"the marker line must be back inside the buffer after restoring the viewport"
|
||
);
|
||
let marker_row = kitty_marker_extended_absolute_cell_row(&grid)
|
||
.expect("the marker line must survive the edit");
|
||
assert_eq!(
|
||
grid.kitty_placement_count(),
|
||
1,
|
||
"the image must survive an edit made while scrolled back"
|
||
);
|
||
assert_eq!(
|
||
kitty_image_absolute_cell_row(&grid),
|
||
marker_row,
|
||
"the image must rejoin its marker line once the viewport is restored"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn primary_device_attributes_advertise_sixel_when_host_supports_it() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
grid.update_sixel_host_support(true);
|
||
vte_parser.advance(&mut grid, b"\x1b[c");
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1b[?62;4;52c".to_vec()]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn primary_device_attributes_omit_sixel_when_host_does_not_support_it() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
grid.update_sixel_host_support(false);
|
||
vte_parser.advance(&mut grid, b"\x1b[c");
|
||
assert_eq!(grid.pending_messages_to_pty, vec![b"\x1b[?62;52c".to_vec()]);
|
||
}
|
||
|
||
#[test]
|
||
fn xtsmgraphics_color_registers_report_failure_when_host_does_not_support_sixel() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
grid.update_sixel_host_support(false);
|
||
vte_parser.advance(&mut grid, b"\x1b[?1;1S");
|
||
assert_eq!(grid.pending_messages_to_pty, vec![b"\x1b[?1;3;0S".to_vec()]);
|
||
}
|
||
|
||
#[test]
|
||
fn xtsmgraphics_geometry_reports_failure_when_host_does_not_support_sixel() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
grid.update_sixel_host_support(false);
|
||
vte_parser.advance(&mut grid, b"\x1b[?2;1S");
|
||
assert_eq!(grid.pending_messages_to_pty, vec![b"\x1b[?2;3;0S".to_vec()]);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_52_read_is_forwarded_to_the_host() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]52;c;?\x07");
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"clipboard reads must never be answered locally"
|
||
);
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries,
|
||
vec![crate::host_query::HostQuery::ClipboardContent {
|
||
selection: 'c',
|
||
terminator: crate::host_query::OscTerminator::Bel,
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_52_read_keeps_the_selection_and_terminator_of_the_query() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]52;p;?\x1b\\");
|
||
assert_eq!(
|
||
grid.pending_forwarded_queries,
|
||
vec![crate::host_query::HostQuery::ClipboardContent {
|
||
selection: 'p',
|
||
terminator: crate::host_query::OscTerminator::St,
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_52_write_is_not_forwarded() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]52;c;aGVsbG8=\x07");
|
||
assert!(
|
||
grid.pending_forwarded_queries.is_empty(),
|
||
"copying to the clipboard must not go through the forward path"
|
||
);
|
||
assert_eq!(grid.pending_clipboard_update.as_deref(), Some("hello"));
|
||
}
|
||
|
||
#[test]
|
||
fn xtgettcap_answers_the_ms_capability() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1bP+q4d73\x1b\\");
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1bP1+r4D73=1B5D35323B25703125733B257032257307\x1b\\".to_vec()]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn xtgettcap_rejects_unknown_capabilities() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1bP+q62656c\x1b\\");
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1bP0+r\x1b\\".to_vec()]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn xtgettcap_answers_each_requested_capability() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1bP+q62656c;4d73\x1b\\");
|
||
assert_eq!(grid.pending_messages_to_pty.len(), 2);
|
||
assert_eq!(grid.pending_messages_to_pty[0], b"\x1bP0+r\x1b\\".to_vec());
|
||
assert!(String::from_utf8_lossy(&grid.pending_messages_to_pty[1]).starts_with("\x1bP1+r4D73="));
|
||
}
|
||
|
||
#[test]
|
||
fn xtgettcap_rejects_malformed_hex() {
|
||
let mut grid = create_grid_with_content("");
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1bP+q4d7\x1b\\");
|
||
assert_eq!(
|
||
grid.pending_messages_to_pty,
|
||
vec![b"\x1bP0+r\x1b\\".to_vec()]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn sixel_dcs_is_not_confused_with_xtgettcap() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1bPq#0;2;0;0;0\x1b\\");
|
||
assert!(
|
||
grid.pending_messages_to_pty.is_empty(),
|
||
"a sixel image must not produce an XTGETTCAP reply"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn ech_over_wide_character_preserves_background_on_padding_cell() {
|
||
use crate::panes::terminal_character::NamedColor;
|
||
// Use a wide character to exercise replacement of a wide cell.
|
||
assert_eq!(crate::panes::TerminalCharacter::new('A').width(), 2);
|
||
|
||
let content = "\x1b[44mA\x1b[1;1H\x1b[1X".as_bytes();
|
||
let grid = create_grid_with_size_and_raw(2, 4, content);
|
||
|
||
let lines = grid.as_character_lines();
|
||
|
||
assert_eq!(lines[0][0].character, ' ');
|
||
assert_eq!(lines[0][1].character, ' ');
|
||
|
||
assert_eq!(
|
||
lines[0][0].styles.background,
|
||
Some(crate::panes::terminal_character::AnsiCode::NamedColor(
|
||
NamedColor::Blue
|
||
))
|
||
);
|
||
assert_eq!(
|
||
lines[0][1].styles.background,
|
||
Some(crate::panes::terminal_character::AnsiCode::NamedColor(
|
||
NamedColor::Blue
|
||
))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_9_notification_is_parsed() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;the build finished\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc9 {
|
||
body: "the build finished".to_owned()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_9_notification_with_semicolons_keeps_the_whole_body() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;done: 1;2;3\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc9 {
|
||
body: "done: 1;2;3".to_owned()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_empty_osc_9_notification_is_ignored() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;\x07");
|
||
assert!(grid.pending_desktop_notifications.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn osc_777_notification_is_parsed() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]777;notify;the title;the body\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc777 {
|
||
title: "the title".to_owned(),
|
||
body: "the body".to_owned()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_777_notification_body_may_contain_semicolons() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]777;notify;title;a;b;c\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc777 {
|
||
title: "title".to_owned(),
|
||
body: "a;b;c".to_owned()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_777_non_notify_subcommand_is_ignored() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]777;precmd;something\x07");
|
||
assert!(grid.pending_desktop_notifications.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_777_notification_without_a_body_is_kept() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]777;notify;just a title\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc777 {
|
||
title: "just a title".to_owned(),
|
||
body: String::new()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn a_notification_carries_its_title_and_body_across_protocols() {
|
||
use crate::panes::grid::PendingNotification;
|
||
assert_eq!(
|
||
PendingNotification::Osc9 {
|
||
body: "body".to_owned()
|
||
}
|
||
.title_and_body(),
|
||
(String::new(), "body".to_owned())
|
||
);
|
||
assert_eq!(
|
||
PendingNotification::Osc777 {
|
||
title: "title".to_owned(),
|
||
body: "body".to_owned()
|
||
}
|
||
.title_and_body(),
|
||
("title".to_owned(), "body".to_owned())
|
||
);
|
||
assert_eq!(
|
||
PendingNotification::Osc99 {
|
||
payload: "i=1;the title".to_owned(),
|
||
terminator: "\u{7}".to_owned(),
|
||
wants_report: false,
|
||
display: Some(("the title".to_owned(), "the body".to_owned())),
|
||
}
|
||
.title_and_body(),
|
||
("the title".to_owned(), "the body".to_owned())
|
||
);
|
||
}
|
||
|
||
fn osc99_display(grid: &Grid, index: usize) -> Option<(String, String)> {
|
||
use crate::panes::grid::PendingNotification;
|
||
match grid.pending_desktop_notifications.get(index) {
|
||
Some(PendingNotification::Osc99 { display, .. }) => display.clone(),
|
||
other => panic!("expected an OSC 99 notification, got: {:?}", other),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_9_conemu_progress_report_is_not_a_notification() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;4;0;0\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]9;4;1;50\x07");
|
||
assert!(
|
||
grid.pending_desktop_notifications.is_empty(),
|
||
"progress reports are not desktop notifications, got: {:?}",
|
||
grid.pending_desktop_notifications
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn other_osc_9_conemu_subcommands_are_not_notifications() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;9;/home/user\x07");
|
||
vte_parser.advance(&mut grid, b"\x1b]9;3;a tab title\x07");
|
||
assert!(
|
||
grid.pending_desktop_notifications.is_empty(),
|
||
"ConEmu subcommands are not desktop notifications, got: {:?}",
|
||
grid.pending_desktop_notifications
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_9_notification_starting_with_a_number_is_still_a_notification() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]9;4 tests failed;in 2 crates\x07");
|
||
assert_eq!(
|
||
grid.pending_desktop_notifications,
|
||
vec![PendingNotification::Osc9 {
|
||
body: "4 tests failed;in 2 crates".to_owned()
|
||
}]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_99_notification_is_assembled_from_its_chunks() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:d=0;Hello world\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:p=body;This is cool\x1b\\");
|
||
assert_eq!(
|
||
osc99_display(&grid, 0),
|
||
None,
|
||
"an unfinished notification has nothing to show yet"
|
||
);
|
||
assert_eq!(
|
||
osc99_display(&grid, 1),
|
||
Some(("Hello world".to_owned(), "This is cool".to_owned())),
|
||
"the finished notification carries all of its chunks"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_99_notification_payload_may_be_base64_encoded() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:e=1;dGhlIGJ1aWxkIGZpbmlzaGVk\x1b\\");
|
||
assert_eq!(
|
||
osc99_display(&grid, 0),
|
||
Some(("the build finished".to_owned(), String::new()))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn osc_99_requests_that_display_nothing_are_not_downgraded() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:p=close;\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=2:p=?;\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=3:p=alive;\x1b\\");
|
||
assert_eq!(osc99_display(&grid, 0), None, "a close shows nothing");
|
||
assert_eq!(osc99_display(&grid, 1), None, "a query shows nothing");
|
||
assert_eq!(
|
||
osc99_display(&grid, 2),
|
||
None,
|
||
"a liveness poll shows nothing"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn the_report_intent_of_an_osc_99_notification_is_remembered() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:a=report;the build finished\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:p=close;\x1b\\");
|
||
let wants_report: Vec<bool> = grid
|
||
.pending_desktop_notifications
|
||
.iter()
|
||
.map(|notification| match notification {
|
||
PendingNotification::Osc99 { wants_report, .. } => *wants_report,
|
||
other => panic!("expected an OSC 99 notification, got: {:?}", other),
|
||
})
|
||
.collect();
|
||
assert_eq!(
|
||
wants_report,
|
||
vec![true, true],
|
||
"a close request inherits the intent of the notification it closes"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_explicit_action_replaces_the_remembered_report_intent() {
|
||
use crate::panes::grid::PendingNotification;
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:a=report;the build started\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:a=focus;the build finished\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:p=close;\x1b\\");
|
||
let wants_report: Vec<bool> = grid
|
||
.pending_desktop_notifications
|
||
.iter()
|
||
.map(|notification| match notification {
|
||
PendingNotification::Osc99 { wants_report, .. } => *wants_report,
|
||
other => panic!("expected an OSC 99 notification, got: {:?}", other),
|
||
})
|
||
.collect();
|
||
assert_eq!(
|
||
wants_report,
|
||
vec![true, false, false],
|
||
"an escape that states its own action decides, and is remembered from then on"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn an_osc_99_notification_is_finished_by_a_payload_it_cannot_show() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:d=0;the build finished\x1b\\");
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:p=buttons:d=1;retry\x1b\\");
|
||
assert_eq!(
|
||
osc99_display(&grid, 1),
|
||
Some(("the build finished".to_owned(), String::new())),
|
||
"the assembled text is shown even when the last chunk carries buttons"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn the_notification_state_remembered_per_pane_is_bounded() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
for index in 0..1000 {
|
||
vte_parser.advance(
|
||
&mut grid,
|
||
format!("\x1b]99;i={}:a=report;notification\x1b\\", index).as_bytes(),
|
||
);
|
||
}
|
||
assert!(
|
||
grid.notification_tracker.known_ids.len() <= 256
|
||
&& grid.notification_tracker.wants_report.len() <= 256
|
||
&& grid.notification_tracker.assemblies.is_empty(),
|
||
"an app sending endless notifications does not grow the pane's state without bound"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn a_single_notification_being_assembled_is_bounded() {
|
||
let mut grid = new_grid_for_forwarding_test();
|
||
let mut vte_parser = vte::Parser::new();
|
||
let chunk = "\u{5efa}".repeat(300);
|
||
for _ in 0..100 {
|
||
vte_parser.advance(
|
||
&mut grid,
|
||
format!("\x1b]99;i=1:d=0;{}\x1b\\", chunk).as_bytes(),
|
||
);
|
||
}
|
||
|
||
let assembled_bytes = grid
|
||
.notification_tracker
|
||
.assemblies
|
||
.get("1")
|
||
.map(|assembly| assembly.title.len())
|
||
.unwrap_or(0);
|
||
assert!(
|
||
assembled_bytes <= 4096,
|
||
"an app streaming an endless single notification does not grow the pane's state without \
|
||
bound, got {} bytes",
|
||
assembled_bytes
|
||
);
|
||
|
||
vte_parser.advance(&mut grid, b"\x1b]99;i=1:d=1;\x1b\\");
|
||
let last_notification = grid.pending_desktop_notifications.len() - 1;
|
||
let (title, _body) =
|
||
osc99_display(&grid, last_notification).expect("the truncated notification is still shown");
|
||
assert_eq!(
|
||
title,
|
||
"\u{5efa}".repeat(1365),
|
||
"the assembled text is truncated on a character boundary"
|
||
);
|
||
}
|
||
|
||
fn rendered_row(grid: &Grid, row_index: usize) -> String {
|
||
grid.viewport[row_index]
|
||
.columns
|
||
.iter()
|
||
.map(|terminal_character| terminal_character.character)
|
||
.collect()
|
||
}
|
||
|
||
fn cursor_position(grid: &Grid) -> Option<(usize, usize)> {
|
||
grid.cursor_coordinates().map(|(x, y, _)| (x, y))
|
||
}
|
||
|
||
#[test]
|
||
fn cjk_characters_occupy_two_columns_each() {
|
||
let grid = create_grid_with_content("\u{4f60}\u{597d}\u{4e16}\u{754c}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 4);
|
||
assert!(row
|
||
.columns
|
||
.iter()
|
||
.all(|terminal_character| terminal_character.width() == 2));
|
||
assert_eq!(row.width(), 8);
|
||
assert_eq!(cursor_position(&grid), Some((8, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn kana_and_hangul_syllables_occupy_two_columns_each() {
|
||
let grid = create_grid_with_content("\u{30c6}\u{30ad}\u{d55c}\u{ad6d}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 4);
|
||
assert_eq!(row.width(), 8);
|
||
assert_eq!(cursor_position(&grid), Some((8, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn zwj_emoji_sequence_keeps_one_wide_cell_per_emoji() {
|
||
let grid =
|
||
create_grid_with_content("\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(
|
||
rendered_row(&grid, 0),
|
||
"\u{1f468}\u{1f469}\u{1f467}\u{1f466}"
|
||
);
|
||
assert_eq!(row.width(), 8);
|
||
assert_eq!(cursor_position(&grid), Some((8, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn skin_tone_modifier_occupies_its_own_two_columns() {
|
||
let grid = create_grid_with_content("\u{1f44b}\u{1f3fd}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 2);
|
||
assert!(row
|
||
.columns
|
||
.iter()
|
||
.all(|terminal_character| terminal_character.width() == 2));
|
||
assert_eq!(row.width(), 4);
|
||
assert_eq!(cursor_position(&grid), Some((4, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn variation_selector_and_zero_width_joiner_do_not_advance_the_cursor() {
|
||
let grid = create_grid_with_content("\u{26a0}\u{fe0f}\u{200d}\u{200b}");
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "\u{26a0}");
|
||
assert_eq!(grid.viewport[0].width(), 1);
|
||
assert_eq!(cursor_position(&grid), Some((1, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn combining_marks_are_dropped_and_do_not_advance_the_cursor() {
|
||
let grid = create_grid_with_content("e\u{301}a\u{300}\u{308}o\u{331}");
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "eao");
|
||
assert_eq!(grid.viewport[0].width(), 3);
|
||
assert_eq!(cursor_position(&grid), Some((3, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn precomposed_and_decomposed_forms_occupy_the_same_number_of_columns() {
|
||
let precomposed = create_grid_with_content("\u{e9}cole");
|
||
let decomposed = create_grid_with_content("e\u{301}cole");
|
||
|
||
assert_eq!(precomposed.viewport[0].width(), 5);
|
||
assert_eq!(decomposed.viewport[0].width(), 5);
|
||
assert_eq!(cursor_position(&precomposed), cursor_position(&decomposed));
|
||
}
|
||
|
||
#[test]
|
||
fn ambiguous_width_characters_occupy_one_column_each() {
|
||
let grid = create_grid_with_content("\u{b1}\u{b0}\u{2192}\u{3b1}\u{203b}\u{d7}\u{f7}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 7);
|
||
assert!(row
|
||
.columns
|
||
.iter()
|
||
.all(|terminal_character| terminal_character.width() == 1));
|
||
assert_eq!(row.width(), 7);
|
||
assert_eq!(cursor_position(&grid), Some((7, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn box_drawing_characters_occupy_one_column_each() {
|
||
let grid = create_grid_with_content(
|
||
"\u{250c}\u{2500}\u{252c}\u{2510}\u{2502}\u{251c}\u{253c}\u{2524}\u{2514}\u{2534}\u{2518}\u{2554}\u{2550}\u{2557}\u{2588}\u{2589}",
|
||
);
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 16);
|
||
assert!(row
|
||
.columns
|
||
.iter()
|
||
.all(|terminal_character| terminal_character.width() == 1));
|
||
assert_eq!(row.width(), 16);
|
||
assert_eq!(cursor_position(&grid), Some((16, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn a_wide_character_that_does_not_fit_the_last_column_wraps_whole() {
|
||
let grid = create_grid_with_size_and_raw(4, 6, "abcde\u{4e16}".as_bytes());
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "abcde");
|
||
assert_eq!(grid.viewport[0].width(), 5);
|
||
assert_eq!(rendered_row(&grid, 1), "\u{4e16}");
|
||
assert_eq!(cursor_position(&grid), Some((2, 1)));
|
||
}
|
||
|
||
#[test]
|
||
fn a_wide_character_ending_exactly_on_the_last_column_stays_on_its_row() {
|
||
let grid = create_grid_with_size_and_raw(4, 6, "abcd\u{4e16}".as_bytes());
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "abcd\u{4e16}");
|
||
assert_eq!(grid.viewport[0].width(), 6);
|
||
assert_eq!(grid.viewport.len(), 1);
|
||
assert_eq!(cursor_position(&grid), None);
|
||
}
|
||
|
||
#[test]
|
||
fn ech_over_the_leading_half_of_a_wide_character_blanks_both_columns() {
|
||
let grid =
|
||
create_grid_with_size_and_raw(3, 10, "\u{4e16}\u{754c}abc\u{1b}[1;1H\u{1b}[1X".as_bytes());
|
||
|
||
let lines = grid.as_character_lines();
|
||
assert_eq!(lines[0][0].character, ' ');
|
||
assert_eq!(lines[0][1].character, ' ');
|
||
assert_eq!(lines[0][2].character, '\u{754c}');
|
||
assert_eq!(lines[0][3].character, 'a');
|
||
assert_eq!(lines[0][4].character, 'b');
|
||
assert_eq!(lines[0][5].character, 'c');
|
||
}
|
||
|
||
#[test]
|
||
fn ech_over_a_wide_character_keeps_the_background_of_the_padding_cell() {
|
||
use crate::panes::terminal_character::NamedColor;
|
||
|
||
let grid = create_grid_with_size_and_raw(
|
||
3,
|
||
10,
|
||
"\u{1b}[44m\u{4e16}\u{754c}\u{1b}[1;1H\u{1b}[1X".as_bytes(),
|
||
);
|
||
|
||
let lines = grid.as_character_lines();
|
||
assert_eq!(
|
||
lines[0][0].styles.background,
|
||
Some(crate::panes::terminal_character::AnsiCode::NamedColor(
|
||
NamedColor::Blue
|
||
))
|
||
);
|
||
assert_eq!(
|
||
lines[0][1].styles.background,
|
||
Some(crate::panes::terminal_character::AnsiCode::NamedColor(
|
||
NamedColor::Blue
|
||
))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn cursor_forward_past_content_pads_the_row_before_a_wide_character() {
|
||
let grid =
|
||
create_grid_with_size_and_raw(6, 20, "\u{1b}[2B\r\u{1b}[4C\u{4e16}\u{754c}".as_bytes());
|
||
|
||
assert_eq!(rendered_row(&grid, 2), " \u{4e16}\u{754c}");
|
||
assert_eq!(grid.viewport[2].width(), 8);
|
||
assert_eq!(cursor_position(&grid), Some((8, 2)));
|
||
}
|
||
|
||
#[test]
|
||
fn yijing_and_trigram_symbols_are_wide_under_the_new_width_tables() {
|
||
let grid = create_grid_with_content("\u{2630}\u{2637}\u{268a}\u{4dc0}\u{4dff}");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 5);
|
||
assert!(row
|
||
.columns
|
||
.iter()
|
||
.all(|terminal_character| terminal_character.width() == 2));
|
||
assert_eq!(row.width(), 10);
|
||
assert_eq!(cursor_position(&grid), Some((10, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn halfwidth_katakana_sound_marks_are_zero_width_under_the_new_width_tables() {
|
||
let grid = create_grid_with_content("\u{ff76}\u{ff9e}\u{ff8a}\u{ff9f}");
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "\u{ff76}\u{ff8a}");
|
||
assert_eq!(grid.viewport[0].width(), 2);
|
||
assert_eq!(cursor_position(&grid), Some((2, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn soft_hyphen_and_hangul_filler_are_zero_width_under_the_new_width_tables() {
|
||
let grid = create_grid_with_content("a\u{ad}b\u{3164}c");
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "abc");
|
||
assert_eq!(grid.viewport[0].width(), 3);
|
||
assert_eq!(cursor_position(&grid), Some((3, 0)));
|
||
}
|
||
|
||
#[test]
|
||
fn a_character_wider_than_two_columns_advances_the_cursor_by_its_full_width() {
|
||
let grid = create_grid_with_content("\u{17d8}x");
|
||
|
||
let row = &grid.viewport[0];
|
||
assert_eq!(row.columns.len(), 2);
|
||
assert_eq!(row.columns[0].width(), 3);
|
||
assert_eq!(row.width(), 4);
|
||
assert_eq!(cursor_position(&grid), Some((4, 0)));
|
||
}
|
||
|
||
fn create_sized_grid_with_content(rows: usize, columns: usize, content: &str) -> Grid {
|
||
let mut vte_parser = vte::Parser::new();
|
||
let mut grid = Grid::new(
|
||
rows,
|
||
columns,
|
||
Rc::new(RefCell::new(Palette::default())),
|
||
Rc::new(RefCell::new(HashMap::new())),
|
||
Rc::new(RefCell::new(LinkHandler::new())),
|
||
Rc::new(RefCell::new(None)),
|
||
Rc::new(RefCell::new(SixelImageStore::default())),
|
||
Rc::new(RefCell::new(KittyImageStore::default())),
|
||
Style::default(),
|
||
false,
|
||
true,
|
||
true,
|
||
true,
|
||
false,
|
||
);
|
||
vte_parser.advance(&mut grid, content.as_bytes());
|
||
grid
|
||
}
|
||
|
||
fn feed(grid: &mut Grid, content: &str) {
|
||
let mut vte_parser = vte::Parser::new();
|
||
vte_parser.advance(grid, content.as_bytes());
|
||
}
|
||
|
||
#[test]
|
||
fn same_size_forced_reflow_keeps_cursor_at_start_of_wrapped_row() {
|
||
let mut grid = create_sized_grid_with_content(5, 10, "0123456789a\u{8}");
|
||
assert_eq!(cursor_position(&grid), Some((0, 1)));
|
||
|
||
grid.force_change_size(5, 10);
|
||
assert_eq!(cursor_position(&grid), Some((0, 1)));
|
||
|
||
feed(&mut grid, "\rX");
|
||
assert_eq!(rendered_row(&grid, 0), "0123456789");
|
||
assert_eq!(rendered_row(&grid, 1), "X");
|
||
}
|
||
|
||
#[test]
|
||
fn height_only_resize_keeps_cursor_at_start_of_wrapped_row() {
|
||
let mut grid = create_sized_grid_with_content(5, 10, "0123456789a\u{8}");
|
||
|
||
grid.force_change_size(6, 10);
|
||
assert_eq!(cursor_position(&grid), Some((0, 1)));
|
||
|
||
grid.change_size(7, 10);
|
||
assert_eq!(cursor_position(&grid), Some((0, 1)));
|
||
|
||
feed(&mut grid, "\rX");
|
||
assert_eq!(rendered_row(&grid, 0), "0123456789");
|
||
assert_eq!(rendered_row(&grid, 1), "X");
|
||
}
|
||
|
||
#[test]
|
||
fn prompt_redraw_after_same_size_reflow_does_not_duplicate_wrapped_prompt() {
|
||
let prompt = "> ~/c/zellij-code-2 on main";
|
||
let mut grid = create_sized_grid_with_content(5, 20, prompt);
|
||
for _ in 0..3 {
|
||
feed(&mut grid, "\r");
|
||
grid.force_change_size(5, 20);
|
||
feed(&mut grid, "\u{1b}[1A\u{1b}[J");
|
||
feed(&mut grid, prompt);
|
||
}
|
||
assert_eq!(rendered_row(&grid, 0), "> ~/c/zellij-code-2 ");
|
||
assert_eq!(rendered_row(&grid, 1).trim_end(), "on main");
|
||
assert_eq!(grid.viewport.len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn saved_cursor_follows_its_own_line_when_wrapped_lines_are_above_it() {
|
||
let mut grid = create_sized_grid_with_content(
|
||
6,
|
||
10,
|
||
"0123456789abcdefghij\n\rXY\u{1b}7Z\n\rline3\n\rline4",
|
||
);
|
||
|
||
grid.change_size(6, 20);
|
||
feed(&mut grid, "\u{1b}8Q");
|
||
|
||
assert_eq!(rendered_row(&grid, 0), "0123456789abcdefghij");
|
||
assert_eq!(rendered_row(&grid, 1), "XYQ");
|
||
assert_eq!(rendered_row(&grid, 2), "line3");
|
||
assert_eq!(rendered_row(&grid, 3), "line4");
|
||
}
|
||
|
||
#[test]
|
||
fn shrinking_keeps_cursor_on_its_own_line_when_it_sits_past_the_content() {
|
||
let mut grid = create_sized_grid_with_content(6, 31, "first\n\r> prompt text here okay \x1b[J");
|
||
assert_eq!(cursor_position(&grid), Some((24, 1)));
|
||
|
||
grid.change_size(6, 23);
|
||
assert_eq!(cursor_position(&grid), Some((22, 1)));
|
||
|
||
feed(&mut grid, "\rX");
|
||
assert_eq!(rendered_row(&grid, 0), "first");
|
||
assert_eq!(rendered_row(&grid, 1), "X prompt text here okay");
|
||
assert_eq!(grid.viewport.len(), 2);
|
||
}
|