From c89b416d764d80a72130821506f36157a08321e9 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 8 Jul 2022 17:19:42 +0200 Subject: [PATCH] feat(terminal): sixel support (#1557) * work * work * work * work * work * more work * work * work * work * hack around stdin repeater * refactor(sixel): rename sixel structs * feat(sixel): render text above images * fix(sixel): reap images once they're past the end of the scrollbuffer * fix(sixel): display images in the middle of the line * fix(sixel): render crash * fix(sixel): react to SIGWINCH * fix(sixel): behave properly in alternate screen mode * fix(sixel): reap images on terminal reset * feat(sixel): handle DECSDM * fix(terminal): properly respond to XTSMGRAPHICS and device attributes with Sixel * Add comment * fix(sixel): hack for unknown event overflow until we fix the api * feat(input): query terminal for all OSC 4 colors and respond to them in a buggy way * fix(sixel): do not render corrupted image * feat(input): improve STDIN queries * fix(client): mistake in clear terminal attributes string * fix(ansi): report correct number of supported color registers * fix(sixel): reap images that are completely covered * style(comment): fix name * test(sixel): infra * test(sixel): cases and fixes * fix(sixel): forward dcs bytes to sixel parser * refactor(client): ansi stdin parser * refactor(output): cleanup * some refactorings * fix test * refactor(grid): sixel-grid / sixel-image-store * refactor(grid): grid debug method * refactor(grid): move various logic to sixel.rs * refactor(grid): remove unused methods * fix(sixel): work with multiple users * refactor(pane): remove unused z_index * style(fmt): prepend unused variable * style(fmt): rustfmt * fix(tests): various apis * chore(dependencies): use published version of sixel crates * style(fmt): rustfmt * style(fmt): rustfmt * style(lint): make clippy happy * style(lint): make clippy happy... again * style(lint): make clippy happy... again (chapter 2) * style(comment): remove unused * fix(colors): export COLORTERM and respond to XTVERSION * fix(test): color register count * fix(stdin): adjust STDIN sleep times --- Cargo.lock | 34 +- default-plugins/status-bar/src/tip/utils.rs | 1 - src/tests/e2e/remote_runner.rs | 20 +- src/tests/fixtures/sixel-image-100px.six | 1 + src/tests/fixtures/sixel-image-500px.six | 1 + .../terminal_emulator_startup_response | 1 + zellij-client/src/fake_client.rs | 8 +- zellij-client/src/input_handler.rs | 66 +- zellij-client/src/lib.rs | 29 +- zellij-client/src/os_input_output.rs | 4 +- zellij-client/src/stdin_ansi_parser.rs | 348 +++++---- zellij-client/src/stdin_handler.rs | 31 +- zellij-client/src/unit/input_handler_tests.rs | 733 ------------------ ...dler_tests__pixel_info_sent_to_server.snap | 6 + ...tdin_tests__pixel_info_sent_to_server.snap | 6 + zellij-client/src/unit/stdin_tests.rs | 409 ++++++++++ zellij-server/Cargo.toml | 3 + zellij-server/src/output/mod.rs | 328 +++++++- zellij-server/src/panes/grid.rs | 368 ++++++++- zellij-server/src/panes/mod.rs | 2 + zellij-server/src/panes/plugin_pane.rs | 6 +- zellij-server/src/panes/sixel.rs | 466 +++++++++++ zellij-server/src/panes/terminal_pane.rs | 16 +- zellij-server/src/panes/unit/grid_tests.rs | 704 +++++++++++++++++ ...s__grid__grid_tests__ansi_csi_at_sign.snap | 52 +- ...rid__grid_tests__bash_cursor_linewrap.snap | 26 +- ...id_tests__bash_delete_wide_characters.snap | 22 +- ...grid__grid_tests__clear_scroll_region.snap | 27 +- ...erver__panes__grid__grid_tests__csi_b.snap | 51 +- ...anes__grid__grid_tests__csi_capital_i.snap | 51 +- ...anes__grid__grid_tests__csi_capital_z.snap | 51 +- ...iddle_of_line_with_multiple_widechars.snap | 22 +- ..._char_in_middle_of_line_with_widechar.snap | 22 +- ...s__delete_wide_character_under_cursor.snap | 21 +- ..._delete_wide_characters_before_cursor.snap | 22 +- ...rsor_when_cursor_is_on_wide_character.snap | 22 +- ...ests__display_tab_characters_properly.snap | 15 +- ...anes__grid__grid_tests__emacs_longbuf.snap | 58 +- ...rid__grid_tests__fish_paste_multiline.snap | 9 +- ...s__fish_select_tab_completion_options.snap | 25 +- ...id_tests__fish_tab_completion_options.snap | 25 +- ...__fish_wide_characters_override_clock.snap | 23 +- ...character_in_line_with_wide_character.snap | 22 +- ...multiple_wide_characters_under_cursor.snap | 51 +- ..._wide_characters_with_wide_characters.snap | 51 +- ...__replace_wide_character_under_cursor.snap | 21 +- ...ts__sixel_image_in_alternate_buffer-2.snap | 36 + ...ests__sixel_image_in_alternate_buffer.snap | 36 + ...__sixel_with_image_scrolling_decsdm-2.snap | 36 + ...ts__sixel_with_image_scrolling_decsdm.snap | 36 + ...s__grid__grid_tests__terminal_reports.snap | 6 +- ...es__grid__grid_tests__wide_characters.snap | 21 +- ...grid_tests__wide_characters_line_wrap.snap | 19 +- ...character_cell_size_with_sixel_images.snap | 26 + ...p_working_after_corrupted_sixel_image.snap | 26 + ..._tests__multiple_sixel_images_in_pane.snap | 26 + ...wing_sixel_image_inside_terminal_pane.snap | 26 + ...tial_sixel_image_inside_terminal_pane.snap | 26 + ...ests__resizing_pane_with_sixel_images.snap | 26 + ...ts__scrolling_through_a_sixel_image-2.snap | 26 + ...ts__scrolling_through_a_sixel_image-3.snap | 26 + ...ests__scrolling_through_a_sixel_image.snap | 26 + ...sts__sixel_image_inside_terminal_pane.snap | 26 + .../src/panes/unit/terminal_pane_tests.rs | 353 ++++++++- zellij-server/src/route.rs | 36 +- zellij-server/src/screen.rs | 31 +- zellij-server/src/tab/mod.rs | 36 +- ...ests__floating_pane_above_sixel_image.snap | 26 + ...__move_floating_pane_with_sixel_image.snap | 26 + .../src/tab/unit/tab_integration_tests.rs | 181 ++++- zellij-server/src/tab/unit/tab_tests.rs | 11 +- zellij-server/src/ui/pane_contents_and_ui.rs | 17 +- zellij-utils/src/envs.rs | 4 + zellij-utils/src/errors.rs | 1 + zellij-utils/src/ipc.rs | 1 + 75 files changed, 4385 insertions(+), 1068 deletions(-) create mode 100644 src/tests/fixtures/sixel-image-100px.six create mode 100644 src/tests/fixtures/sixel-image-500px.six create mode 100644 src/tests/fixtures/terminal_emulator_startup_response delete mode 100644 zellij-client/src/unit/input_handler_tests.rs create mode 100644 zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap create mode 100644 zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap create mode 100644 zellij-client/src/unit/stdin_tests.rs create mode 100644 zellij-server/src/panes/sixel.rs create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer-2.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm-2.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__changing_character_cell_size_with_sixel_images.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__keep_working_after_corrupted_sixel_image.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__multiple_sixel_images_in_pane.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__overflowing_sixel_image_inside_terminal_pane.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__partial_sixel_image_inside_terminal_pane.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__resizing_pane_with_sixel_images.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-2.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-3.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image.snap create mode 100644 zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__sixel_image_inside_terminal_pane.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__floating_pane_above_sixel_image.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_with_sixel_image.snap diff --git a/Cargo.lock b/Cargo.lock index 337f7b7f..4a3d08ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-channel" version = "1.6.1" @@ -1144,9 +1150,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.14.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc3e639bcba360d9237acabd22014c16f3df772db463b7446cd81b070714767" +checksum = "689960f187c43c01650c805fb6bc6f55ab944499d86d4ffe9474ad78991d8e94" dependencies = [ "console", "once_cell", @@ -2213,6 +2219,25 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "sixel-image" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84880b5da046b87741b9bad6ee916919d5548cca182f009b3a240e20a7f7c99f" +dependencies = [ + "sixel-tokenizer", +] + +[[package]] +name = "sixel-tokenizer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71b7f6629ac964d60179c1fb00dfb80da265fc3465a17245e26c1eaf678d476" +dependencies = [ + "arrayvec 0.7.2", + "thiserror", +] + [[package]] name = "slab" version = "0.4.6" @@ -2764,7 +2789,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] @@ -3273,6 +3298,7 @@ name = "zellij-server" version = "0.31.0" dependencies = [ "ansi_term", + "arrayvec 0.7.2", "async-trait", "base64", "byteorder", @@ -3284,6 +3310,8 @@ dependencies = [ "insta", "log", "serde_json", + "sixel-image", + "sixel-tokenizer", "sysinfo", "typetag", "unicode-width", diff --git a/default-plugins/status-bar/src/tip/utils.rs b/default-plugins/status-bar/src/tip/utils.rs index 0d5d935c..70ea3cd0 100644 --- a/default-plugins/status-bar/src/tip/utils.rs +++ b/default-plugins/status-bar/src/tip/utils.rs @@ -50,7 +50,6 @@ pub fn get_cached_tip_name() -> String { } let quicknav_show_count = local_cache.get_cached_data().get("quicknav").unwrap_or(&0); - eprintln!("quicknav_show_count: {:?}", quicknav_show_count); if quicknav_show_count <= &MAX_CACHE_HITS { let _ = local_cache.caching("quicknav"); return String::from("quicknav"); diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 9e556e10..21c91e51 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; +use zellij_server::panes::sixel::SixelImageStore; use zellij_server::panes::{LinkHandler, TerminalPane}; use zellij_utils::data::{Palette, Style}; -use zellij_utils::pane_size::{Dimension, PaneGeom, Size}; +use zellij_utils::pane_size::{Dimension, PaneGeom, Size, SizeInPixels}; use zellij_utils::vte; use ssh2::Session; @@ -76,6 +78,7 @@ fn start_zellij(channel: &mut ssh2::Channel) { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) { @@ -90,6 +93,7 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirrored: bool) { @@ -108,6 +112,7 @@ fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirr ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) { @@ -121,6 +126,7 @@ fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn start_zellij_without_frames(channel: &mut ssh2::Channel) { @@ -135,6 +141,7 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) { @@ -153,6 +160,7 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } fn read_from_channel( @@ -174,6 +182,11 @@ fn read_from_channel( let mut retries_left = 3; let mut should_sleep = false; let mut vte_parser = vte::Parser::new(); + let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { + height: 21, + width: 8, + }))); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut terminal_output = TerminalPane::new( 0, pane_geom, @@ -181,8 +194,10 @@ fn read_from_channel( 0, String::new(), Rc::new(RefCell::new(LinkHandler::new())), - Rc::new(RefCell::new(None)), + character_cell_size, + sixel_image_store, Rc::new(RefCell::new(Palette::default())), + Rc::new(RefCell::new(HashMap::new())), ); // 0 is the pane index loop { if !should_keep_running.load(Ordering::SeqCst) { @@ -322,6 +337,7 @@ impl RemoteTerminal { ) .unwrap(); channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } pub fn load_fixture(&mut self, name: &str) { let mut channel = self.channel.lock().unwrap(); diff --git a/src/tests/fixtures/sixel-image-100px.six b/src/tests/fixtures/sixel-image-100px.six new file mode 100644 index 00000000..74b99efa --- /dev/null +++ b/src/tests/fixtures/sixel-image-100px.six @@ -0,0 +1 @@ +Pq"1;1;100;100#0;2;0;0;0#1;2;3;3;3#2;2;6;6;6#3;2;9;9;9#4;2;13;13;13#5;2;22;22;22#6;2;38;28;25#7;2;35;41;41#8;2;41;41;41#9;2;44;44;44#10;2;47;47;47#11;2;50;50;50#12;2;19;19;19#13;2;50;47;47#14;2;56;50;47#15;2;75;56;53#16;2;82;63;56#17;2;88;66;60#18;2;72;53;50#19;2;94;69;63#20;2;66;53;50#21;2;60;50;47#22;2;82;60;53#23;2;91;66;60#24;2;97;72;66#25;2;97;75;69#26;2;85;60;53#27;2;88;63;56#28;2;94;72;66#29;2;60;50;50#30;2;97;75;66#31;2;85;66;60#32;2;91;72;63#33;2;88;69;63#34;2;56;38;35#35;2;35;22;22#36;2;56;44;41#37;2;82;56;50#38;2;56;35;31#39;2;41;28;25#40;2;60;41;38#41;2;63;44;41#42;2;69;47;44#43;2;72;63;56#44;2;69;50;44#45;2;60;38;35#46;2;78;56;50#47;2;78;60;53#48;2;60;47;44#49;2;50;44;44#50;2;56;41;41#51;2;85;60;56#52;2;53;47;47#53;2;75;47;44#54;2;75;60;56#55;2;75;53;47#56;2;56;56;56#57;2;72;50;47#58;2;75;50;44#59;2;91;72;66#60;2;82;53;50#61;2;91;69;63#62;2;38;25;22#63;2;28;19;16#64;2;41;25;25#65;2;35;22;19#66;2;25;16;16#67;2;35;47;47#68;2;13;41;44#69;2;13;31;35#70;2;19;41;47#71;2;25;19;19#72;2;31;44;44#73;2;6;56;60#74;2;3;35;41#75;2;6;31;38#76;2;6;44;47#77;2;41;44;44#78;2;0;60;66#79;2;3;47;53#80;2;9;31;38#81;2;53;44;44#82;2;78;53;47#83;2;44;47;47#84;2;3;44;50#85;2;41;25;22#86;2;3;50;53#87;2;3;41;44#88;2;3;50;56#89;2;3;41;47#90;2;3;60;63#91;2;16;47;50#92;2;35;53;53#93;2;38;50;53#94;2;28;44;44#95;2;3;53;60#96;2;6;50;53#97;2;3;63;66#98;2;6;72;75#99;2;6;69;72#100;2;22;66;69#101;2;19;50;53#102;2;3;31;38#103;2;3;69;72#104;2;6;75;75#105;2;47;44;44#106;2;6;35;41#107;2;13;50;53#108;2;9;41;44#109;2;0;56;63#110;2;3;38;44#111;2;28;50;50#112;2;22;44;50#113;2;0;63;69#114;2;0;66;69#115;2;25;60;60#116;2;0;63;66#117;2;63;66;72#118;2;16;56;63#119;2;35;56;63#120;2;22;60;63#121;2;56;63;69#122;2;9;60;66#123;2;41;60;63#124;2;47;60;66#125;2;3;47;50#126;2;6;60;63#127;2;19;60;63#128;2;3;60;66#129;2;60;63;69#130;2;3;28;35#131;2;44;60;66#132;2;13;82;82#133;2;47;63;69#134;2;6;25;31#135;2;9;72;75#136;2;3;28;28#137;2;3;38;41#138;2;0;25;25#139;2;0;19;22#140;2;3;35;35#141;2;53;56;63#142;2;0;9;9#143;2;0;6;6#144;2;0;13;16#145;2;3;63;69#146;2;6;9;9#147;2;3;44;47#0!100~-!45~!10^!45~$#1!45?_!8?_$#2!46?__!4?__$#3!48?!4_-#0!29~^NNFFBBB@@@#4?@#6?@#7!11?@!4?A??C#12C#1C?G?O#4_$!29?_#8_?O?G!5?A!4?@!8?@!4?A!5?G?O?_$#1!30?O?G?C#12C#7C??A!4?@#6!11?@#5@???A!4?G?O$#10!31?__OOGG?CC?AAA!4?!4@!4?AAA?CC?GGOO__$#5!31?O?G!4?A???@#4!15?@#2@??A$#11!33?__oowww!4{!12}!4{wwwoo__$#9!37?C!8?@@!4?@@!8?C$#2!37?A??@#0!19?@@@BBBFFNN^!29~-!22~^NFB@#1@#10!41?@?ACGOO_$#5!22?_OG???@#9!41?@???G$#10!23?_OOGCA?@#5!40?@???GO_$#11!24?__ow{}}!38~}}{wo__$#9!25?G???@#8!42?AC#12C$!25?C#8CA#1!44?@#0@BFN^!22~$#3!26?A!46?A-#0!17~^FB@#10!27?_?OO???OO__!18?BCGO#1G#4_$!17?_#5O??@#13!27?_??OOO#5!23?@??O$#9!18?_??A#14!28?_!5?_#9!21?A??_$#1!18?G#11_ow{!25~^^!7N^^!18~{wo_$#10!19?OGCB#15!28?_???_#0!23?@BF^!17~$#8!19?GC#16!31?_#17__#8!24?CG$#3!19?C#12A!58?A#3C-#0!14~NB#11_w}!25~F@#22G?A#19[A!5@?A#13@?G#10!20?@EW_#12_$#1!14?OC@#10!27?WEB@#17W_@!5?@#20@A?O#8!21?@CO$#12!14?_#10_WE@#13!25?_G#21CA#14@#18@#24SA#33O#36_#31!4?_G_#1!22?@CO$#8!15?OC@#15!27?_#26o{#27c#23A#30G#25KM]}}}[Wo#11BN!19~}w_#0BN!14~$#5!15?GA#20!28?O#34!4?_#32O!5?_#28C#10BCo#5!21?AG$#35!51?__#29!6?C-#0!12~N@#11o}!26~FF#49G!6?C#18G!5?QCG#11@@p!20~}o#0@N!12~$#2!12?OA#9A#13!27?_G@#42C!7?@#15G#33C!4?_#9A?C#10!20?@K_#4_$!12?_#10_K@!26?W#41O!6?AAD!5?C#6?AG#10AI#7!21?@G$!13?G@#57!28?_#26_?@p#16G!5?O!4?O#36C#56_o#9!22?A#2AO$#5!13?C#14!30?E#53Oo!6?G#24@#17o#28O#34C?B#43@#7C#5C!23?C$#8!13?O#55!30?O#38G???B#20G!5?C@?G#8??G!23?O$#22!45?B#40G!4?@AC#46A#31G#54G#13?G??O$#58!45?_#37AG@#47C!7?Q??O$#6!46?C#39CE?@!7?@$#44!47?A#32oOO!6?__$#25!49?!4_??B`??_$#51!49?G#48C!6?G$#19!51?OOO??A$#52!51?G#45?A#50C$#59!53?_#60_-#0!11~F#5@#11[NN^BBB!4FN!15~^NG#57@!4?G#25@B@!4?@??FE#6o#11B!15~!4^!4N{#5@#0F!11~$#1!11?G#6A#10bOO_cCC!4Go!15?_OVWO#34M!5?O??A??_?O_#8G#10{!15?_??_oO?oB#6A#1G$#2!11?o#7C#14?__???G???O#67!17?_#68_#69_?_#39CGO?O!4?K?C#28@#44_#18O#29E#13!17?__???O#7??C#2o$#8!12?G#13!4?W??Oo#18O_#13!19?C#55A!5?_C?A?AAAC#47G#32G#56@#20!21?__#8??G$#9!12?o#21!5?g#17_#20_#27?_#48!20?A#22@!7?_#51@!4?_#59?@#9!26?o$#22!18?O#47O#20!24?G#42@???O!7?O$#60!44?C#8O#53@!8?OO$#70!44?_#65?O!7?_?_$#63!46?G_!6?C_$#40!46?A???G?C#62K#64G?K?G$#71!46?_#26@!4?A#46?@$#66!47?O_#38_???_!4?O$#37!47?A#19BC?A???@??A$#44!47?C#27C#17GC_!5?@$#24!49?A#35?GG!4?G$#41!52?OOO$#61!52?@-#0!11~#2o#10FJKO_@A!6?BCGEAAM_OGG?CA@#76E?CA@#34C!4?@???@@@?A#68GG#10CFC?GGOO_uAAKOOGE@??@???@@F#2o#0!11~$#3!11?N#13G?OG?ADG!6?GOG??O#7?_???G#87_?G??O#90G_#53GOO#63NKCCE[?A?C#18@#84_#97O_#11BFFFNN^@@@BNNF@#15?@?C???I#105?_#3N$#48!12?O??C?_?AO?E#44G#47?GO_??W#9???O#102__#75oO?_??@?@#40CG#66@?A???AECC#76_?O#8@#98O_?_#67O#94_#13??G?CO!6?C???A?GG$#8!12?_#18O@!6?A??A#11?BF!4@^NFFFB@#74o??B#89G_#5?B#41_?O!7?@#23@#96o#9?A#92G#93G#83G???_#20??_!6?C?G?O?C??O$#11!13?C#52A!6?@#60?C#21!4?_?C#69???_#94O#80O!8?A#48O#65BE??II??WGG#71G#91??G#99?O#100O#101O#86_#14!4?C!4?OGA??A!4?C$#47!13?_#22_#14_!8?@!4?C?_#83!4?G?C!6?G#101_#85CG#26__#39O!4?O#7_??C#103_#104??_#16!6?GG!6?O??GAO?o$#17!15?@C?O?bK?RC!5?_#68!7?G#86G??_#79?A#58???_#38?O#37__#35W!5?C#47!11?O#17_!5?GCO_Ap?_$#26!15?A??_#41?G#46O#22!7?_#70!9?C#84Oo?c#47!7?@#62O!7?B#32!11?O!8?O_?GC$#81!16?O??C!8?O#26O#77!9?A#72@#73@??CO#60!6?@??_#17_#64A?A#18!13?_?_!8?_$#29!16?@!8?C#51???G#88!11?GG?O#44!8?@#55_#6??O#75O#21!15?_!5?@?@K?O$#55!16?G??@??@#78!18?E#95O#46!10?_#31!22?_??A#29A???@?A$#16!16?A#19G?_?_o_wo_!50?_o__#24G#28C$#15!17?C?O??G#22!54?O#54?G$#37!17?O#20G#82?C-#0!11~}#8@_#60@!4?G#58O#24C???GO?O@H#74_GGc?@!5?A???G!9?G#94C#73@??@!4?B#84@#79[#10@FO!9?_OGCC]#2_#0}!11~$#1!11?@#2_#10]CGGOo_#30?GGWwo_?M]E#20B#84o??{}~p@ACCGI??C?OO!4?OC#107A#108A#76A!8?aO#83G#13G!8?_!4?A@#4O#1@$#4!12?O#81@AC?G#36@O_#23O??AEC#32?@#9?_#29C#75A@!7?@??C#89A???G!9?@!9?_#91_#21C!7?_??G#20?A#16@#8_@$#5!12?G#11?woo_#27C#19CNBVfD@J~#16??O#70O#102CEB@#79??MA??G???O#112G!5?O???GC#86CA!6?A#94@#72AO#7_#19FW{wA@DBBB@#5!4?G$#6!12?C#17??@AA?A!8?__#83?G#94@#87OOA!4?@?@C?G!8?O#47A@#122_#115G#95?@!5?CC#68?G#20?B#47G_#17?C?EA!4?@#6???C$#7!12?A#46??A?C#47A#37@#53?_#26_#76!10?_!7?A???@E#111OO??O???C#88O!9?w#70??C#8??_#24@??_??C!4?@#7??A$#20!16?@#14@#48G#89!15?G#86!4?oC??@OO??C#26@BF#17CC#15C#113?_??Ow{_#98Do^#48!6?O#30C?AG_??C?A$#41!16?C#95!22?G???O???A#119_!9?O#109??A!5?G#25!5?AB@PWWWGC#11_oww$#88!39?C?G#78O???_#101@AC#70G#118_?_???O#123_#97???@!4?o#26!8?C#32?_??G#28CA$#90!40?G!5?@!7?_!5?C#103?AW#99?_#31!14?O#29O?C$#116!40?_!4?_!9?_???g#114??[_$#113!40?Oo___#110C!9?G#72A#104!6?AN$#106!43?A@#120??_#68G!5?G??@$#44!48?@#18AC#29G#23A@$#109!49?__#27?@#19AB@$#54!52?G#14G$#117!52?O$#121!52?_-#0!13~w#10BW_???`#77g#75KA!7?B?WG_#79???~#78@#113!6~o?@~???~~~w??~~~#103o?E#109@#74_?o??OA#111@!5?_#8???_#1_#0w!13~$#7!13?@G!6?E!4?A#113ow[#103o#96_#87_??G#90!4?E#97!6?C?C!7?A?@!6?A@!9?A_#100M#10Co?_WB#2C$!13?C#1_#11F^~~~]!8?@#107@#110G#130??O#89C#95!4?o#122!6?A???o?H???@!9?A#87P@OG?S#79A#83@!4?J#9!4?C#5A$!13?A#9C#8_!5?@@#48@#76w!6?O@b?A#109!4?G#116!6?G?A!7?C#124F#114!4?N#98~WW#76G?C???G#113?K[k#7@!6?G@$#12!14?O#83!6?O#102A{!8?K?_#106O#118!11?@???H#117w#99!12?@C#110O?A_#68A!7?@#67O#12!4?O$#69!22?O#74?C!6?CQCF#119!13?r!9?OO#104!5?_#126_#73C#75m??oB#88_c#116O#11@!4?N~^F$#80!22?_#15?@#84KC!8?@~~~#123!9?K!9?G_#84!9?GGC_#70@#78??a#114OG#122_$#68!24?A#17@#27@#116GC#97AK#120!18?_!8?_G#95!10?C???Ga#20?@#99AO$#79!25?o#86G#37@#82@#114_#126A#127!18?O?E?o!5?C#86!10?A???O#103???S$#94!25?A#95O#67A#101A#128!20?G???E!5?A#125!10?@#94@#102K$#109!26?_#88C#121!24?B$#129!52?C-#0!15~{o_#9O#83?A#68E#75B!8?C{??DW_#86??w#113N!6~o?B???!4~_?@B#104owB#74GoC?COAC#116AG?@#99A#132_}#92W#5_?C@$!15?@C?_#70??H#87W??A!8?o?AO#116???O!6?G??w!6?W#124I#98?_GEK@#76@wOB?CIO#113EAGH#97@#93?_#10NE@$#1!15?AG#11@F^_#72O#74CAB!7?Bo@w#106C#78!4?_#97!6?C?CE!6?C?E???OA#84??K??GOG#90@#103_??G@#115F#9O#0_o{!15~$#10!16?@EG_\#77_#106_#84{{L!4?OO?FK??N~~#120!8?@_O??@#127!4?@?O#103OC@#75?OA?@GN@#86`__#114[US#98S#8???GA$!16?AG#102!5?@#79?oG?C#97OD#110I#102???A_#118!11?A#123M???o!5?_#114?KB#102??_KB#87a#134o#89_#79o#95?DO#78@#104__A#4???O#1GA$#4!17?O#88!8?O??a?_!8?E#119!8?P_??K#131!5?P#96!5?C#11!16?@$#95!26?@G?C_!9?@#122!9?G@!6?A?G???_$#76!26?CC???@?GA?@#133!15?o!6?C$#90!26?_A#114pG#73A#117!21?B#100A!6?_$#86!26?A#113p?@#96G#121!21?K$#109!28?A$#116!28?G-#0!18~}{wo_#84@^??OG@!4?CK??@N{_#113BN^!4~{!4?B@@#100??AB#95???G??o??_CA??C#132@NB#127A#3A$#1!18?@#8@A#7D#87GO!8?a?GB??A#79?BW_#78O_#122!4?@?A#131_#103?gC?A@???_O!8?oW??O#1_???@$#3!19?A#12CG#75A#76A_??_C???K?A!4?O#90?@#116C!8?@#133^#114?CAA@!5?_!9?Co#98?K#108O#0ow{}!18~$#10!20?@#77A#106D#89K!8?O?@!4?_#109??G#97!6?A#119C_?]!5?@#86!4?O!5?AB@?@#140?_#135?C@#7@$#138!22?O#139_#86?@?GQ??_!9?CO_#123!6?B??`#99O#104Ow{{ww^^F!9?_??A#137?G#136C$#88!25?}???A?O#74@@?OK#115!13?GO#98???gC?A??_?G#84_?_A?G@#78_?@?_$#95!26?_?_?_H!9?A#118!8?_C#135!7?CC#126???F#75W??_@#90?OC??G$#113!26?MA?oM#109E#75?]__oi{#127!11?O#120G#102!13?F#74B?^A#109?G??AA$#116!26?@??GP#130??_???O#134!27?_#87C??C#113??GAG$#78!26?O?@#102!5?O?BD#76!28?O@#116!6?CP$#90!27?C?C#89!36?G#88G??[$#114!27?@#79!39?C?o-#0!24~}{woo_#1_#75OEU?BcMYA??G#79@?_??C#109_!6?A!8?O#72C?O#1_#0_oow{}!24~$#139!24?@A#87AC???G!6?C_g??OG!4?A??C!4?C#113AA#90G?C@#89B#79C!4?G#143?G#132@#136A@$#84!25?@?@??GA!8?PRbeeEM???O#119@@#103_?@@?O?@@!9?CB#135A#116@$#86!26?@A??@#74?@!4?O_TAK?GOOO!8?C#98_@@@#135?_#78G!7?@B#139?C$#142!26?C#143G#90@#76G?C!5?_?G?_S??G?AAC!8?CCC!5?@?CM#144O$#95!28?C#79CE@#102WGF?W@@?C#86???@?_K???OOO!9?A#75?@???O$#116!28?A#144O?_#134_???B#95!8?@??_???G!8?AA?_??B#110A??G$#136!28?G#78@#140O#94??_?o#78!10?@#88_G!6?A!4G?C?KO!7?C$!29?A#130???@#69GK#116!11?@@@!4?@??A??QG@#84OK!4?@$#7!34?O#110!12?O!8?C#99_??_#126?O??_#70A#7_#5_$#10!34?_#106!13?O#68O?CA#107GAG#114OO?O#132??_#96_#73?G#101O#10O#74@$#101!49?G#97__!9?O!8?E$#67!50?C#73G#145_#93CC#120O#122??__#108!5?A#111G#83G#125G$#70!50?A#94A#141?AG#131_#118!10?_#107C$#115!50?G#124??@#121O$#118!50?@#133???_-#0!32~}}{{{www!5o!10_!5o!4w{{}}!32~$#1!32?@?A!5?G!4?O!8?O!8?C???@$#5!33?@#8@#70@#102@#143C!8?O!6?O!5?G!5?A$#146!35?A#138A#76@??@!6?G#109@??G#97@@#93G#112G#87G#136G???C!4?@$#87!37?A?@A@#144G!8?O#124??F#119E#139??G#103@@#132@@#96A#138A#125@$#84!38?BA?A???CC?GG#116E?AA!4?C#114?A#98A#135A#99@$#139!38?C#140C#75CCC#89C??G#73@#78?A!7?@#147??C#144?C$#142!41?G!5?!4O?O#123??@$#74!42?BB#86@???C?G#113ECC??EEBA$#136!43?G#79EAAC#90?@@?G$#106!44?G#88@?A#126???@!4?@$#110!45?G#95@?AC???G!5?C!4?@-#0!100~-!100N\ \ No newline at end of file diff --git a/src/tests/fixtures/sixel-image-500px.six b/src/tests/fixtures/sixel-image-500px.six new file mode 100644 index 00000000..dcc55af1 --- /dev/null +++ b/src/tests/fixtures/sixel-image-500px.six @@ -0,0 +1 @@ +Pq"1;1;500;500#0;2;0;0;0#1;2;3;3;3#2;2;9;9;9#3;2;13;13;13#4;2;16;16;16#5;2;19;19;19#6;2;22;22;22#7;2;25;25;25#8;2;28;28;28#9;2;31;31;31#10;2;38;36;36#11;2;38;38;38#12;2;41;41;41#13;2;44;44;44#14;2;47;47;47#15;2;50;50;50#16;2;41;39;38#17;2;38;35;35#18;2;35;31;31#19;2;35;31;28#20;2;38;28;28#21;2;39;30;28#22;2;44;41;41#23;2;41;31;28#24;2;53;39;35#25;2;64;50;44#26;2;74;53;49#27;2;82;60;53#28;2;85;63;58#29;2;88;63;56#30;2;88;66;60#31;2;50;35;31#32;2;44;41;38#33;2;91;66;60#34;2;94;69;63#35;2;35;28;28#36;2;35;25;25#37;2;33;27;24#38;2;69;50;45#39;2;91;63;56#40;2;78;56;50#41;2;74;52;49#42;2;61;44;39#43;2;88;60;53#44;2;36;27;25#45;2;47;36;33#46;2;97;72;66#47;2;45;31;30#48;2;97;75;69#49;2;97;78;69#50;2;97;75;66#51;2;97;80;72#52;2;97;82;75#53;2;85;66;58#54;2;94;75;66#55;2;97;85;78#56;2;75;56;50#57;2;58;39;36#58;2;88;75;69#59;2;82;64;60#60;2;30;19;17#61;2;25;16;16#62;2;22;16;16#63;2;22;16;13#64;2;19;13;13#65;2;31;22;19#66;2;75;66;60#67;2;19;13;9#68;2;47;44;44#69;2;35;22;22#70;2;86;71;67#71;2;55;47;45#72;2;28;19;16#73;2;38;22;20#74;2;50;30;28#75;2;61;39;36#76;2;69;41;38#77;2;69;47;42#78;2;31;24;22#79;2;41;25;24#80;2;56;30;30#81;2;72;44;41#82;2;75;44;41#83;2;75;47;41#84;2;75;47;44#85;2;78;60;53#86;2;44;44;41#87;2;82;56;50#88;2;45;30;27#89;2;94;72;66#90;2;85;56;50#91;2;78;50;44#92;2;61;56;58#93;2;67;44;39#94;2;82;53;50#95;2;41;28;25#96;2;85;60;53#97;2;78;50;47#98;2;82;53;47#99;2;78;66;66#100;2;22;19;19#101;2;75;50;47#102;2;74;49;45#103;2;38;28;25#104;2;28;24;24#105;2;24;22;20#106;2;94;63;60#107;2;38;25;22#108;2;44;28;25#109;2;13;9;9#110;2;55;50;52#111;2;56;53;55#112;2;85;74;75#113;2;64;61;66#114;2;42;42;45#115;2;86;80;85#116;2;72;67;69#117;2;78;75;82#118;2;82;77;83#119;2;35;35;38#120;2;66;63;69#121;2;77;72;78#122;2;67;56;60#123;2;27;33;33#124;2;50;44;49#125;2;31;28;28#126;2;72;69;75#127;2;88;69;63#128;2;69;64;71#129;2;85;82;88#130;2;85;60;56#131;2;25;30;30#132;2;74;67;75#133;2;25;16;13#134;2;75;69;78#135;2;91;69;63#136;2;85;82;91#137;2;85;56;53#138;2;64;42;39#139;2;35;22;19#140;2;72;41;38#141;2;22;13;13#142;2;31;25;25#143;2;63;41;38#144;2;78;38;41#145;2;91;52;50#146;2;94;69;60#147;2;9;31;31#148;2;31;19;19#149;2;19;27;28#150;2;9;36;38#151;2;9;33;35#152;2;6;44;47#153;2;16;19;19#154;2;13;35;36#155;2;3;50;53#156;2;0;56;63#157;2;9;22;25#158;2;9;25;27#159;2;13;22;25#160;2;13;22;22#161;2;6;41;44#162;2;14;30;30#163;2;9;41;41#164;2;0;63;69#165;2;3;53;56#166;2;3;28;38#167;2;3;28;35#168;2;3;31;41#169;2;13;28;28#170;2;3;47;50#171;2;0;63;66#172;2;9;38;41#173;2;3;31;38#174;2;9;28;31#175;2;3;47;53#176;2;6;30;35#177;2;0;60;66#178;2;9;45;47#179;2;0;60;63#180;2;13;25;25#181;2;6;25;28#182;2;0;53;60#183;2;0;66;69#184;2;3;44;50#185;2;0;56;60#186;2;3;35;41#187;2;3;64;71#188;2;3;38;44#189;2;20;28;30#190;2;17;35;36#191;2;11;66;69#192;2;25;42;45#193;2;3;41;47#194;2;2;36;42#195;2;13;82;82#196;2;8;56;60#197;2;5;35;38#198;2;8;75;78#199;2;3;69;72#200;2;20;31;33#201;2;3;50;56#202;2;13;25;28#203;2;9;78;82#204;2;9;78;78#205;2;6;72;75#206;2;35;35;35#207;2;3;66;69#208;2;13;75;75#209;2;6;38;41#210;2;6;25;31#211;2;47;41;45#212;2;78;72;82#213;2;31;33;36#214;2;41;38;41#215;2;6;66;69#216;2;13;19;22#217;2;6;22;28#218;2;0;14;17#219;2;2;2;2#220;2;6;6;6#221;2;0;8;8#222;2;3;22;25#223;2;6;38;44#224;2;0;33;35#225;2;0;28;31#226;2;3;44;47#227;2;2;27;27#228;2;9;82;82#229;2;13;17;19#230;2;3;25;35#231;2;0;50;56#232;2;3;41;44#0!500~-!500~-!500~-!500~-!500~-!500~-!500~-!500~-!500~-!229~!14^N^!10N^N!13^!230~$#1!229?__#2__#3_#4_#5_#6_#7_#8_#9__#10__#1O?!10O?O#10__#9__#8_#7_#6_#5_#4_#3_#2__#1_$#11!243?__!9?_?_$#12!245?!9_?_-#0!198~!4^!4N!5F!5B!6@#13AA!7?@#14!30?@?@?@!6?A?A!4?C?C??G?G??O?O???_$#1!198?_!7?G#3G!4?C#6C#4!4?A#7A#1??@#3?@#5@#6@#10?@#12?@#13!36?@@!7?AA#11A!5?C!4?G#5?G#1?G!7?_$#2!199?_??OO!7?C!4?AA!5?@#12!45?@@#10@#8@#7@!6?A#4A#12?C!9?O???_$#6!200?_#10_???O#5??G#9G!4?C!5?A!6?@#5!46?@#3@#2@!5?AA!4?C!8?O??_$#12!202?_???O!9?C#1!60?@@#0!5@!5B!5F!4N!4^!198~$#14!203?_???OO???G?G??C?C!4?A!7?@@@?@#9!42?A!5?C!4?G#10???O???_$#15!204?!5_!4owowww{w!5{!10}~}!25~}~}~!7}{}!5{w{wwwowooo_o!4_$#7!204?O#11!5?G!4?C!5?A!7?@#6!56?C!8?O???_$#13!211?G#3!75?C!4?G???O$#13!288?G-#0!180~^^^NNNFFFBBB@@@#4?@#8@#12@#14!99?@?@??A?C!8?_#5O#12_#9_#4_$#1!180?_#4_#9_#2O??G??C??A??@#12!105?@#8@#4@#2@??A??C??G??O$#12!183?_#15___!4oww{w{{}}}~}!96~}~}}}{{w{wwwooo_?_$#5!184?O#10O!8?A#13!109?A??C??G??O$!186?O!5?C??A#0!109?@@@!4BFFNNN^^^!180~$#6!187?G!5?A#10!111?A!8?O$#11!188?G??C#6!114?A??C??G$#14!189?GG???C?A??@?@#11!106?C??G$#7!190?C#1!120?C!7?_-#0!167~^^NNFFBB@@@#6?@#14!138?@?A??CG!4?__$#3!167?_?O#15__oowww{{}}}!136~}}{{{wowoo_#3?O$#10!168?_#14_!6?C!4?@#11!137?@!4?C#6C#12G$#8!170?O#2G?C!4?@#7!141?@!6?G$#13!171?O!6?A#2!142?@!4?C?G$#7!172?G#12G!6?@#13!140?A!6?O$#5!174?C#1A#4A#9A#0!144?@@@BBFFNN^^!167~$#11!175?C#9!146?A!6?O?_$#4!323?A!8?_$#1!324?A-#0!155~^^NNFFBB@@#14!167?@AACCGG#9G#13O?_#7_$#2!155?_?O#15__oowww{}}!164~}{{wwooo__#2O?_$#6!156?_#13_?O#9G#14G?CCA?@#10!165?@?A?C#3C?G#8O$!158?O#3G?C#10C?A?@#4!167?@?A$!163?A?@#0!169?@@BBFFNN^^!155~-!145~^^NNFBB@#11??@#14!187?@AACCG?O_#13_#6_$#1!145?_!7?@#12!190?@!4?G$#6!146?_#2O??C#7C#3A#5!192?@!4?G$#13!147?_#15__owww{}!187~}{{wwoo_$#10!148?O!4?A#1!192?@!4?G??_$#5!149?G!4?@#10!191?A!4?O$#14!149?O??CCA#0!192?@BBFFN^^!145~$#12!150?G#3!196?A#8C#2C??O-#0!137~^NFFB@@#3@#14!209?A?C??O#9O#12_#4_$!137?_#2O??C#10C??@!208?@??C#2C??O$#12!138?_#15__oww{}}!207~{{wwo__$#1!139?G??A#7A#13!211?A??G#6G$#9!139?O#14O??C??@#3!208?@#0@@BFFN^!137~$#6!140?G#13G??A#7!211?A#1A??G-#0!129~^NFFB@#10A#14!225?@@AC?GO#9O#11_$#2!129?_O???A#7!228?@???G$#11!130?_#15__ow{{}!223~}}{wwo__$#1!131?G???@!228?@???G$#9!131?O#14OG??A@#10!226?A#0@BFFN^!129~$#7!132?G???@#2!228?A???O_$#4!133?C#12C!230?C#4C-#0!122~^NFBB@#4@#13@!240?@#14ACGO_#12_#3_$!122?_#2OG#15oww{}!240~}{wo_#2GO$#12!123?_#14_??CA#4!242?@#0@@BFN^!122~$#11!124?O#1C#8C#6A!244?A#1AC#11O$#9!125?G#8!247?C#10G-#0!116~^NFB@#13A#12@!254?@#14CKGO_#7_$!116?_O#15__w{}!254~}woo_$#14!117?_OWC#4!257?@#0@BFN^!116~$#6!118?G#5C#4A@#13!256?A#5AC#6GO-#0!110~^NFB@#14!268?@ACGo_#5_$!110?_#14_OGCA@#7!267?@ACG#6O$!111?OG#7CA@#0!269?@BFN^!110~$#15!112?_ow{}!266~}{wo-#0!104~^NFB@#13A#14@!278?B??O#2GO#1_$!104?_#2OG#12GC#5!281?@#0@BFN^!104~$#9!105?_#15_ow{}!278~{{w__#9_$#10!106?O#3C#4A@#13!280?A#12CG#11O$#4!391?A#3C-#0!99~^NFB@#8@#14!289?@AC#0BFN^!99~$#1!99?_#2OG#4C#14CA#8!290?@#1@???_$#9!100?_#15_ow{!289~}{wo_#9_$#11!101?O#13G#6A!292?A#4C#2GO$#13!397?G#11O-#0!95~^FB@#14!300?@??O_$#4!95?_??A#7!301?@??O$#14!96?_O?A@#13!299?A#0@BF^!95~$#7!96?O??@#4!301?A??_$#1!96?G#15_w{}!298~}{w_#1G$#2!97?C#12C!302?C#2C$#10!97?G!304?G-#0!91~NF@#9A#12@#14!167?_?!6_?_!130?@?C?_#7_$#1!91?O?A#12!310?@#2@?G$#7!91?_#2G?@#9!310?A#0@FN!91~$#14!92?_?C#1!311?A?O$#10!92?O#15ow}!167~^~!6^~^!130~}}wo#10O$#6!93?C!312?C$#13!93?G!312?G-#0!87~^F@#3@#14!155?_?O?G!6?A@?@#13@#18??@#28?A#21?@@!5?A#18A#15@@@BFFNN^!122~}wo#2G#5_$!87?_#10O?A#12!157?_?O#19O!7?A!5?@!4?@@#31??A??C#35C!5?_#13!123?@?G$#14!88?_?C#20!158?_#23_?O?G?CC???A#30!6?A#22!5?@#14@@?AA!4?O#10!124?A?O$#2!88?G#15ow}!154~^~N^FNFFFBB@?@#20A!5?@@#16!9?A#24?G?O#37O#23_#14!125?C?_$#13!89?G?@#38!159?_#29_!4?G!9?A?AA!4?C#13!4?C?G??_#3!123?@#0@F^!87~$#1!89?A#22!162?G#17G!9?@!8?@@!5?C?G?O#1!126?A$#6!89?C#25!163?O!9?A!9?A#36!6?G#40?_#6!127?C$#39!253?__#24G??C???A$#27!254?O!5?C!4?A!5?A!6?G?O$#33!255?o?OGG?CC$#32!255?C#34__oowww!12{wwwoo__$#26!256?G??C!4?A!7?A???C$#30!256?O#16?A???@-#0!83~^FB#8A#12@#14!149?__O?C?AA@#21A#38A#28O?C#46!11?__???O?!4O?O?O???_#33??@???GO_#12!122?@??_$#2!83?_??@#17!152?_!5?A#23??@#34_ow{}}!7~!5^N^!4N^N^N!4^~~}}{wo_#19G#14!122?A?o#1G$!84?G#15_w{!149~^^NNBF@@#29oKgcIA@@#48!11?__#49!11_#48__#50_#42!4?@!4?O_#2!122?@??_$#5!84?O#14O?A#16!152?O#13G!4?@#39??O???@#37!31?@#16@#14@ACGW_#8!120?A#0BF^!83~$#11!84?_#3C#13C#47!153?_#27_??G#42C#41???@#40!35?A#21A#17A#15@BFF^!119~{w#3C#5O$#10!85?G#20!155?O#33_O?G!4?GC??@#29!31?C#45C#18C?O_#13!121?C#10G$#19!242?G!4?@#24!40?G$#25!242?O#30_O??CA?@?A$#11!243?C#43_?oOGC?A$#31!243?G#44C-#0!80~^F@#6@#14!147?__G?A?@#28_??S_HQC@#48!7?_??CA#51_?OG?_GC_OCAOC_S?COC_S?G_O?_?_#33?@A??_#22G#14!119?@CW_#1O$#3!80?_#2GA#13A#20!149?_O??A#19@#43oOg[ulBA#49!8?oww{]}nv~^vz^nz|nz^j~znz^j~v]m}[{Wwoo__#15@FN^!117~}w_#3G_$#14!81?_W#13!150?OGCA#29_OGkAA??G?@#46!7?GC??@@#50!22?@??A???G#24@???_#7!120?@#0@FN!80~$#10!81?O#15_{!147~^^FF@@#38C#42A#24@#30@#39?@#46!41?@!4?C#17??@A??_#13!119?A#2A#9O$#8!82?C#25!151?_#34_OGC!7?o{!6~^FBB@!26?@@BBBEKWO#16C#35O#8!121?C$#44!235?GC#48!51?C#29???C#14@A?O_$#41!235?O#33_OGEA@???oKA#31!44?A#23C#20G$#26!236?G#27!56?G#38O-#0!77~^F@#14!148?oK?@#28C_??CGQ_I?g@#46!8?WB#51?_OCAOGA`OCA`G?cPGAOCAP?I_S?H_CQ?GCaOH#52?BV{wo#15@BN!117~}w_#3?_$!77?_#14_WE@#16!147?O#13C#34_WCA@!8?o!7~F#49w~^nz|nv|]nz|]v~Zmv|nz|m~t^j~u^zl~vz\nu~{gA#21@??_#14!118?@EW_$#10!78?OC@#20!148?_#19G#33O_I@!9?K#50!7?_#48C#30!41?@#26A#12@??_#10!118?@CO$#4!78?GA#24!150?O#17A#18@#29OW?P@?C?OAs@#48!51?C#24C#14ACO#0!119?@F^!77~$#15!79?_w}!146~NBB#21C#31A#42@#30CA!8?A#35!52?A#17C#13G#4!120?AG$#27!230?_#41G#43??_{iulZtnTI#53!53?G#25O#18O$#44!298?G$#54!298?_-#0!74~^F@#14E@!146?_KB#33O_GB#30!11?A#34^!7~#46_#49~~v\nuz|]nvz}nz\n~t^ynu^|mvz^|vz\m~\vzn]z^|O#55@]{o#14@?O#9!118?@C#0F^!74~$#2!74?_G#15ow}!146~^B#11C#34_[F#43okZulYvi^itn#39K#33_#48!7?F#51??GaOHCA`OGC@OCaO?I_DOH_APGC_AGCaP?aGCO`C_!4?A#8@C#15FN!118~w_#2G_$#7!75?O#3A#10@#17!148?_?@#42@#29OCB??A?G@?S??p#50!8?W#52!42?An}`#56@C#22AG#13_#14!118?EO#8O$#12!75?_#9C#22!149?O#19G#27GA#28_GOcHOd?S_@IO#18!57?AG#17O#1!120?@#12_$#13!76?G#31!150?O#20A#58!71?G_#35_#3!120?A$#38!227?_#57C#25!72?O#13!121?G-#0!72~N@#5@#14!149?K@#33OOE#34!15?@^~~^NFFB!4?@!6?@#49@@@B@BEBFBEFJDNENLVM^ZN\FMJL^Vzn#55?It^~w#17CO#15V!118~}w_#2O$!72?O#15_o!149~B#20G@#28@?c?S_CHQcHQcHO`C#33E_!4?G?C#63G!6?_A!4?C__#52EKoKWkWWcWoWoo_o__!9?O~tI_?C#18A_#14!119?@C#0@N!72~$#6!72?_#1A#11A#13!149?O#12A#34_M@#30!15?G#40???_#27O#64_oO?GGCcCo?GAW?WGG#37G#21_#51_@???@??A?@?AG@?COAG@CA?GC#66!6?AO#14Bg#6!119?@?_$#3!73?C#14K#16!149?_#17C#25C#30_#29WBH?@O!7?AGAo#36!4?_#47O#25G?C#53A???@@!8?C#24O#55OGo_O__O_?_#29!6?_#24_#46O?O#47_#25_#33_#24!7?@#11@#16G#11!120?A#1A$#10!73?G#23!151?O#27G#43?_Wuj]julZulZulUx#67!8?_ooowWwK[S[C[COOO#70G#36!19?_#34O#37_#48O#58!10?_#68C#3!121?C$#13!73?O#24!151?_#31A#69!25?G#49@@#26A!6?@#59@#65A#31A#56A#23C#65!22?_#71!12?G#9!122?G$#48!253?A#21C#61C??A??_#72_?_#48??A#13!158?O$#51!255?@#45A#60A!5?_?__$#50!256?@#62??AA??A$#27!260?@#41@-#0!70~F#15Ow!150~A#20E#29CoF!7?@?C?O#93???_!4?_?O#94O!6?O#74@#77!5?@#41@#42@?A#49L]}{}k{k{WkwowWoo!4_??_???@#55?@BFN~#17@g#15!121~w_#0F!70~$#2!70?G#12G@#17!150?_@#33wA#43wlZulZulYvi\julZUD!6?_?!4O??_#84!6?A#26?A#21@#29O#52@@A@ABABCBCMCBK!5?__!5?@A??O#56?O#11O#14!121?EO#2G$#5!70?O#14_E#12!150?O#23G#25@#30K#28?QcHQcHQcGPaCHQcHA!5?_?_?_???_!8?A#78@#89C#48A#33_#55?@?@?@?B?B@BCB#70G!7?_#24@!8?A#12C!121?@#1@#5O$#10!70?_#1@#13!151?G#24O#27A#34@#90!17?G!7?O#83G??CC??GCSC{AoO_#51!6?O?O?_O???_!4?OO#58O!6?A?CG#92_?G#14@#4!122?A#10_$#4!71?A#14!151?D#42_#57!19?O#63OGC!5?A??@#87O??_!7?CG#48!16?@OO#21?G#65G#59O#71O#78@???C#25C#23G#47O#66??_#86A#9!122?C$!71?C#62!172?_#64__OMJCC#75G?C!5?@#25!25?C#64AA?D?DAGQCk?_o_#71??C#13!123?G$#31!245?GC#23A#61O!5?A??@#81A?A!5?C#85!19?A#20@#63C!4?@!5?G$#33!245?B#67OG?CBBB@@@#80A#76A???!4@#37!22?C#67@FAFAKEKWOwO$#87!245?C#27A#56@#65@#88O?G?C#82GCGGKMSYiyA{GkWo_#38!16?G#23GG#62??G#60?O?a$#34!246?@#95_#96?_#72G?C#29_?__#39_#73@#42!36?_?A$#91!255?G???O?_#27!34?@$#79!255?A-#0!67~^F#15o}!151~A#17@#29_SP?H#94?_?O#2___ooOWWw{[K!4C#3C!7?C?CCC?C???G!4?_#49@ANn|u~vFB@@!9?@?A#66?C!5?B#4OO??O#5O#10!4?_#13!113?@G#0F!68~$#1!67?_#2G@#13@#14!151?|#19M#25@#28g?c?cHQCHAC@A?@?@!4?O??_#27G#4C?C#74O!7?G!9?G#50A?O!5?G???@!6?@??A#42O?OC#65??@C#100G#17f#15B!4FNFNNN^!112~}o#3G$#6!68?OA#20!153?O#27S#30A#43mZuZULJEDBE@BA@!8?@?@#62C?C#64CC?C!6?C!4?G#34?@?G!6?_??A??!4@??AA?!4_#62??_#71AC#105G#14c???G?G??O_#6!113?AO$#11!68?_C#21!153?_#40A#33@#24!5?_#61_!6?_#64_???B#29_??@?P?@#93O?G!6?AA#103G!6?_#94A#30C!8?O!9?A#48@???A#25A??_#58?@#109O#9?G!5?O#11!117?C_$#14!69?G#96!154?G#57!8?O#87G??C!9?@???_#57_#102G#76G!6?_???AA#65C?O?G#60?O_#53_#46!5?OCA??@!4?@#40CC#56C??C#67?@?DC?_#11??G_!4?O#2!116?@$#69!234?O#3O??G#78?C#56A#88A#65@!9?_#35_#73OO#92_#108O#25A?A#2C!5?GG?OO_#51@?OAH?G#33?GC?A!9?_!4?_#13!6?_?G$#38!235?G#31G??C#90A#97?@#67A#26_#40O???G?G#91O#68??_#75GGGWG!6?O??c#26???O#29!7?_GC?!5A#83__#23O#26?CC#60??A?O#12!6?G!5?O$#60!237?G!7?@#42@!6?A?!4A?A???A#91O?_???@?G#48C#87!8?O!6?C???_#51@#99@#7!12?_?O$#63!238?_??C?_!6?C#98O#101G#110???_#107O#111_#23_#81OO?_???A?_?C#31!11?_!4?_#75_#63O!7?@?C$#25!238?C!9?G#94?_#95!9?_#80G!7?C#24!14?O??G!6?OO?P?OO$#47!244?O#23AG#34_o!5?@?@@@#28??@#82_P`_@?b?BAC#90!11?G???C#96C#76_#4G#78O$#100!245?G#33_??O???@?@???@@#84?O?O?_#79?C#67!15?_#2_?OO!5?G!5?GGW!8?___$#24!246?AI!5A?A#98!7?@#47G#67C!5?O#91!14?G!7?_$#106!247?O#83!17?@?@?B#61!15?O!4?GO$#78!265?G#62K#88?O#28!16?C?C#44G#104G#27?C$#101!266?O#3!19?O??O???G?!4G!4?W???OO?_???_$#64!286?_!5?G!6?GBAA_$#43!286?C#69_#39???A$#93!286?G-#0!66~F#15_{!147~!5^~#21?^??G#65_??@#80O#93O???A?@#56?C!7?OC_#116GC!7?GOW#81@??HOo!6?O#115!4A?A#38@!6?G#29C!4?A?AC???AG#45o#9_!8?OA#13!115?@G#0F!66~$#2!66?G@#14!148?__?__?~#20_#87OG?A!7?O#29O?O??O#22G!7?G#111C?@?CE?@A@#55C#76@ACG?_#91???@#99A???C#34_???H#43_!6?A#30A???A!4?G#38???C#62C#13OO??C!4?_#14!116?AO#4O$!66?O#13G@!149?_#28!5?NA#2oWKKMFFB@@!5?BF#38G!7?_#113IA!6?OAC#24o#82@EALDye[???_#29G#112C?C?C#9C#35O!7?G#15__#23C#55O_#36G!5?AG#12E!4?@#111__#6_#2!118?@G$#8!66?_#14OA#24!155?_#43DB@@!6?__O_wg#24G!8?A#122O#65@#17G!9?G#83A@CAIDXA#28_#4@#2F!8?MB#84_?C#39G!7?@#103O#34@BF]{o#64B#11@??G#4G_O#6!120?A#8_$#6!67?A#3!157?_??OA?G??A#81C?A?@#112??oO#33CAwUIA#69C#66?O#9O???O#18?_#115AG#88A#108C#74G?_#98!4?_#40O#92C#5G#17A#51@#52@H@_#50o#23G#64@G#83OO#98G!6?@@#42C??_#67???@#22?G#15NFE@!5?o!115~{_$#10!67?C#47!157?O#40C#103C#31A!5?C!5?@!8?_?@#110_#32_#92@!5?___#80?ooO#59!5?C#114A#56_#18C#94O#46O#49owX#26?O#85_#116_!8?G???_#69O#27O#5!6?_!4?@??C#10!117?C$#75!228?_!4?G!6?A!7?_#23G#71O#86??C#114C???AC#117C#79@#85!10?G#117_#122O#24@#97_#48G#58C#55?A#68?A#3?C#63@#24`_O???C!9?@#14!4?G@AA!4?K$#61!229?O!6?@#90G?C!9?@#123!4?O???OO#111!12?@#120G#31?G#118!4?C#124???O#74A#82IB@#33C?AAA???@ACw`#2!5?__oWKN^B$#42!229?@#82_?_oWGC?AA#78???A!7?A#7??_?G?_#112@#121!13?O#75!11?C#81C#91O?@#87@??@#121G#47C#28AC#100_#8!8?O!4?O?G$#76!229?_#79O!4?A#94G?C#60!4?@!5?O#14!4?G?@#11CG#119G#93!25?G#90??C??@@#45C#92?G#37G#101?G#3!9?O#7?CA???@$#83!231?_#69G#63C!7?C#37?C#34w{FHC#12!5?AG#105O#22!31?_#114_O??O_#117_#126O$#91!234?_??C#70!5?_#96@#30@?_@?@#15!4?A?AC#97!29?A#125O#20G#113_??O$#28!237?_G?O_??A#6!10?O_G#111!32?_#14OW$#74!237?@#8!17?_?_#25!33?C$#13!255?@?@-#0!64~N#14_I!147?K!7?@#4A#3B@#83G[Aa~XV#84g#43gylZulA!9?A!5?G?G!9?A#50O!8?_???G#43O???O_#7CC#45G#8CGC#92E?G#86G#25O#49_w#50o#29O#19g#14{!8?@#12!118?@O#0N!64~$#1!64?O@#15s!147~B@#42CG?G?A#47?C#56G#28_!8?O@OcHQC#33K!6?A?C@J?SSOCSSOOA!4?C?A_M!5?B!4?E!6?O?_o!4?_???@Am#20B#2!4@#12A@#117K#128@#14!120?I_#3_$!64?_#2A#12@!147?_!6?@#104?@#88C#108A@#91?_#87!5?A#29CA???X@!5?@??@ECHG!7?@H??C???@!6?O!8?w??__#63A#9HA#12@#116?CC#111C#36@#40G#46A#21??S#6A#8A#9AA#4@#92C?G#2!121?AO$#6!65?C#13!148?OA#75_?G#27?IOc#94??O#90!8?C#39!5?_A!7?A#40!4?@!4?@#27@#28@#94C!4?A#112???_G#66G!9?@#59_G!7?O#16G#66?W#55@#65?A#53U#48C#111!8?A?o#1!121?@$#10!65?G#17!149?C!6?A#98!11?@#34!6?oo?F~}}{{wwoqAae_!4aoooWGGC?Y#127O@#41O#83Gu#52!4?a}_#96G#99OO#74B#28gW!9?O#133K#89@#113!9?G?C#6!121?C$#13!65?O#23!149?O??@@@#112!21?@B_#30?@!6?C???O??C???G!6?@!5?k#39!4?@_#126C#85C!9?O#129B#96O#120!12?O?A#10!121?G$#20!215?GA@#82__!5?Gsa\\?egV#51!8?E#55_#58O#87???@#90@!5?G?G??GKG#96A@#48_???K!8?G#130!4?E#128B#80B#93C#113@!7?H#115?A#121!12?_Q$#31!215?_#22@#76o?O!5?C?@#54!14?G#115W#70G#46!9?_#38!4@#87G!5?C#49_ow!9?V~\@O#134?G#121_#25?C#11@!9?@#126!12?`$#25!216?O#34C??CKWoo#118!18?C#89!13?A#97!5?CA#82A?@!6?sH}#122!10?A#10A#14@#15@!7?A!7?!4{???~}!118~s$#40!216?G#26A#81O?_!5?A#98!37?C@???@#70??E#117@#56@#17!13?C#40G#34OO?!4_?__??L@$#33!218?AE???G#91!42?A?@!4?_??@#131!12?A#6AC#5A#123CA$#30!218?C#57?O_#46!44?O??_C#99?_#121A#101A#71!15?G#114@#119?@#27O$#81!266?@#118!5?O#132C-#0!63~B#15o!149~#16L#42GA#83@!4?A!4?W??Hv#98?_O#28PAS@S#34O~~{??B?GOO!4?CC?AA@@!5?GC@@!6?K!9?AEG?S??_A?@!5?@#23F#11_#15^^F?_w!122~}_#0B!63~$#1!63?C#6@#17!149?A#24R#87_??A!6?C!7?G#30!5?@#51??@!8?_#58!11?_??O#85_??C@#90_#97_#28?_!7?CG??H?`#51!8?OG#24???O#12O#111_??@OC#14!122?@W#2K$#3!63?G#10A#22!149?_#31_#75@!5?@#97???A!6?O_#33!5?m#54??A#49__?AEEEFBBQA!4W[[[]NFFB#70GC#112AA#87_???O#48o!6?_!4?CG?O?CC?A_?_??C#42_#13K#126?_OC#120G#116@#7!123?@#6O$#5!63?O#12C#32!149?O#57C#94O!4?A#43O__?_!7?@i\juH#48!4?W???@@@???`XB@@C?AA?O?G#33??A!7?B!5?@Gc?_?AQA_aA?@#45!7?G#14B#118??_GB#9!124?A#8_$!63?_#14G#76!151?CC@#82@!7?F??uG~#90?E#115!9?EGO?__#50G?K[K???C!6?O?C@#83_?OGDID#98G#49?~~~??C!7?GGWWW[[]NV^^W#92!4?G_#121C#12!124?C$#91!216?G#93wA!4?A#91???_?E???K#118!10?@#112CGO??_#58_?_#51!11?g#115__OGC#82GSYTyB#52!4?~#55~w#46O!7?C??_??@!4?A#113!5?A?A$#81!217?A???@#102!6?BO#70!15?A??O#89!17?O#59??OG#94_#91A???C#50!6?A!6?C??C??a_?_?___#117!5?O$#27!218?W!5?C#24???G#77_#52!15?O__#84!22?o#118@#43!12?A!4?@$#28!218?_???G?O#42???C#135!16?@?G#130!36?@!4?O$#101!218?C!4?C#88!4?O#89!17?C#46D?G?WO??__e_`a_``!4?A#29!16?O_!4?!4@$#33!219?CG!4?R#107??_#112!56?B??O$#34!219?wo???BK#30!60?O!4?_A??@$#29!220?CK_P?_G!8?C_?Ga#58!46?G#121G?_$#84!220?@!5?@??H???B#70!52?@#115O_$#90!220?A?CG#117!62?C#39@$!221?O??G?O#134!59?A#127C$#30!221?_-#0!61~^#11_@#14!150?E_#94A!8?@!8?@#87A#29@A_F_#48???A?_???@???A!8?A#33A??CAA??CG??C!7?_???@#115@@#112@!6?A?A??@#55_#20C#15C}}!127~o#1@#0!62~$#1!61?_@#15w!150~w#20A_#77O!6?G!6?o#84O#90?G`#34!4?@~~~w_#51??G_QHC??gA?OI#46_#34OGC???w{KEC!5?O!6?O^][wAG_#58@!5A?A#99!4?B#23G#11_#126@#92@#11!127?@_$#2!62?A#13A!150?@#23@O#93l!4?_?O#25A#42?_?@G#82I@[_#33!5?]#50???CO!6?A!6?_OGC#90GCA!9?C#49?E^^vBcA!6?E[{SwgWw[{KWGC#24?O#12O#13!129?A#2A$#3!62?C#14C#17!151?G#87@!5?A#90_#98C!8?@#97O#49!9?@N^~v^kuw}{U{{kSKC#91_OG#75CA#26A#77@#28O@??A#88_#87A#48?@?_!4?B!9?!4C???C#25???_#13G#14!129?K#3C$#6!62?G#19!152?C#38C!4?_#43@IB!9?EGU|Ug#115!12?@?@!6?!4@#24w#43g!4?@A@BG_#50G!6?C#56_#31_#27BCOO#70??@@?@@?@?@@#124??A#111A#6!130?G$#8!62?O#22!152?O#57G#138A_#34~~!5?{#40@#60E#31CO#83DM_#55!19?@?@?@?@#136@#29_O!6?_??@CKOP#46?_!4?_G#25??_#26Ag_#127!4?@#52_?_?__owO#128@#117@#9!130?O$#27!218?K!6?w@G#61G#47G#107_#129!25?@?@#82!4?__#112@#39O#137??@#27GG???_#52!4?GwA[#40!5?C#89!8?@?@???C$#26!218?O!6?C#62??O#57A#52!27?AA?AAA#81?O??@#69??o#30AG?O?I_!8?@$#29!218?B#28??C?T???E!7?Sg?HO#53!26?@#62!4?O#36O#25O#91?@#51!5?C!9?AAg?O!4?OAEAG$#101!221?O!8?E#93!35?C#64!4?_#67_#63_#55!8?X@$#56!221?G!5?O#63_#88O$#33!221?B!4?A#65?@#103_$#91!222?G!7?@?_A$#97!222?C#75?_$#102!222?O-#0!61~#2B#15s!153~w#24@!9?@!4?O#91@?DGOo#85_#33?BKo#48???@?A???O#30??_!8?C??A@!7?O??c#47_#56A#23A?O#61@#90A???G#49@F]{g_#55E[a]`^#46?G#17@#13o#15!129~{#2B#0!61~$#4!61?C#12@#17!153?@?_#38A?O!9?BG#41ooO#90@#29@?WPk#34BN~~~}{{wwWG?_OKC`ooW[W???@#64o_?W!9?_?_#87G???CAC#98O#52?@BF^xb\`]_~#48o#19E#14N#13!129?B#4C$#7!61?G#14I!153?Co#31G!6?G?A#62W?o_#101BME_O_#28E?i?_#50!5?A??CCCc???@#94C!4?@??C!8?G!9?C!5?_#51???O#53!8?A#21G#7!131?G$#9!61?O#13!154?A#16G#75DC??@?D#80C#87_#108_???G#26C??G_#43A@FCO#39O#49!5?@@BBAbXNFA#29_!5?A!4?A!7?@BAA!5?G#42O?A#101@#43AG_#85!11?@#23O#9!131?O$#11!61?_#19!155?C#138A_?_#42AC?A!5?C#73_#40?G#82ACG#87G#46!11?C?_??OG?A#33A@Gc_CG?@#75G???C?CA?C???G!4?A#25??@#30?O#89!13?C#47_#11!131?_$#103!217?A#20O#29@?G???_S#47C#61E?K#83???@#97?A#94C#51!14?@?A??@#90G??C!4?C??CA#38C!9?C#107??O#62@$#76!219?GO??AA#88??W#63`_#65@O#98!25?_O?A??@#25_#27O#63_?_??O?Oo!8?_?___$#102!219?O#27C@!4?G#64??W#60A#28!26?O?O#43G!5?G#101A??D!5?@#27_!5?@???KA_$#34!220?BCw_??@#69??A#97!29?G#91CB#82@!9?A#88CG!7?O???O?_$#57!220?G#33A?WoOA#100??C#87!32?A??A??C??A#139@@@!8?OA#82C$#93!220?_#30?C#84@#56G@#103???@#39!33?A#65?_#42@!8?O#24A!5?C?@#95A$#76!265?@#47O??G#67O#61___#28?G??s!9?G$#93!266?A#62_???G#81?AA!7?GG#140C$#73!267?O#60O?G!4?_#76C!6?C$#77!267?G#95???@#72?G#91?C!5?O??G$#103!275?@#40OA#93G!8?@$#69!276?_#84G#34?_@@@!6?Ow?AG_$#33!279?D?A!6?PGC`CO$#98!279?OO$#77!279?G#138G!7?C-#0!60~#1@#15o!26~^NNFNFNN^!121~w_#19O#90CG?OW#87@#42O!8?@CO#26_#130@?G_#33BK!5?_??A@???@#64CC???@!8?__@OOC???_#73!4?KO_??O#29A?_!8?C#21C#16@#15D!129~o#2B#0!60~$#2!60?A#12@#14!26?_O?G?G?O!122?EO#22_#30G#27A!4?_#61ABA?E???GO#94?S#40A#28ABSOo#89G#46???wI#23_!7?_#95OO#79WGCO#107GG#72!5?O!5?CQ???A!4?G#88A#31C??_!9?O#45@#17A#12_!129?@#3C$!60?C#13A#17!27?_?O?O#12O#20_#16!122?@G#25C#38O#33P__DO#88C!8?A#101@H#53??@CG_#34BV~~~FPKE@???@#24?A#47??@#139??GC?A!4?A!7?C!4?A#103?@#27@@A#98C?O#50G_!5?OG@#47A#62O#13O!129?A#5G$!60?G#14K#22!28?O!5?_#31!122?@!7?G!7?A#78C#23G#27???C#77O#29BG#127?_#50!4?C?@#63GGG??GG?C!5?@!6?@_???___@?@??@A#23A#43?@#57G#33@C#34O!6?__#36_#60G#63_#14I!129?K#7O$!60?O#23!29?_???_#20!124?A?_#41_#28C??I#79B?o#65_?O??_#39!8?C#48!7?B#60O__??OO??AC?A?@a?AK|A@C!4?GKI?A?}Oo??_w_#83G#46A!8?OA#64o#104G#8!131?_$!60?_#42!30?_?_#17!125?C#47G#75@#102C#29O??C#69?O!4?A#41@???EJ{W#56!10?O#61_OOOCG??G??B??A?`PpA`?IMC???@OK\O@`ACC[#94?@#87C#49@F^}O??OCB#38G#142?C$#18!92?O#56!127?A#76A#34?BFa_#62?@?@G@#24@C!7?_#42!10?GC!4?A??@#141!13?OACW!4?_#97!8?A#108O#30G#52??@n}oNA$#93!92?_#98!127?@#97??G#94G#60???KC[o#139G#38?A??A#65!17?C!4?O??_oO!7?C!5?_!7?K?gO#55!9?@N?@$#36!228?_#73G??_#72?G#138@!5?_#101!13?A#69_?CC?A??CGc_OSGA?OK!4?A!5?K#48!16?_GC$#63!231?C?{o?o_#27!18?AA???@#36!15?@#21_#38@#57@$#64!231?A#103???G??_#90!17?@#62G!5?A??@???_!4?O?GI?Q?@Q?_!8?_$#141!231?@#107???O!20?C#73___?_g??_AOCGC??G???_#24@$#108!235?C#39!21?@#25A#30?@$#72!257?O-#0!59~B#10@#15!27~O!9?@BFN!5~NB@B@B@BBFN!103~}!5{w#60@!4?q#64[O??]Wg#88O!9?A!7?G!4?G!4?A!4?@#76C!7?G#41C#74G?O_#69C!4?AQI!7?_#63A?C#11@#15D!129~#10@#0@!59~$#1!59?C#11A#16!27?A!8?@A!9?G#77O#38G!4?_#12!105?@!5?AO#65G__!9?A!7?OG?_I@???A!4?@#145?__O?OOOWOW?WWGWGWOOO_#58C?GO_#23O!5?CO#27_???O#21O#64W@B#17A#14y#11!129?A#1E$#2!59?W#13[#12!27?C#22_!9?C!9?C#19CC#20C?GO_!105?@!5?A#79B??o!7?C#23`#42@A!6?C!8?_G!9?A#98A#34_!5?AA??_!8?_!6?C?w{]C#26A#67C#61U#72G#18C#12!130?C#2W$#3!59?_#14_#13!27?H#18O#87C_#30_!6?_#14GO!5?oCA?A?ACCGo!104?A!4?C#61?o!5?A?W#75?@!4?O#85E#29CC#78_#63_o!5?G#27O?O#80__O?OG?G!7C!4?C?C??G?W??_#139@A?C??C#33@?O??_G#28@#141_#62g#139o#104o#13!130?W#3_$#14!88?_#36G#40@#28I#34K}}{{wo#35G!8?O#101_#30O#34ooo_#13GO!106?A!4?_#73CY?B#62@!5?C#41@???CW#40_#33ACC!6?_#29?O?O#53G#76_#144__OO?OGGG?G?G!6?G??O__#61@@?CMG@#25??A#49BF?@#38?_#47G#142???G#14!130?_$#74!89?A#43A#29D#33Q@?A#44@AC#57O#23_!8?G#33_#40G#56G#17C#26O#23!108?@#17AAA?K#107?C?K!9?_#38@G!6?@!9?O#96G#74O??G#106___!4?_O??O_O??__#141@?@#63AA?G??_#56???G#50GC#46A@B$#103!89?@#75O#90O#39@?@#42@#56AC#38G#17?O#12_#36!6?_#24!4?G#42!110?@?@#47@#69??@]?C!7?C#24CO_!9?_!7?CG#23??A?A#64@#146__?_!6?_#89A#65@!8?@!5?_#52??B$#108!89?C#79_#25!134?@#139!5?@?G#63bc~F`#103A?GO_#28@?JG!9?_#47G#72??@#31?C#56??C#75C!8?CCC?C#66?A!4?C#31C#85G#44G#60?P?O`?W_!6?@$#91!90?G#67!144?G???_O#31?G#77C?_#34?@BB@#61w?OGG???ACA?A?B?@@?@#79?@#28A#30A#33AaA_#25?@???A#32A#59?G#138O#64?C#42?_???@!7?C$#72!235?@?_#138??A?AG#130A@O#93_OG#30A#60CU`??CG!4?B?B?@#140?G#101???A#39??_?_#29@@#46A_#143C#62@@?@A??A??O$#43!247?OG#139???H??C#138O!8?C?A#24!7?@#38@#27@#26!5?A#127?C#135!4?O#107??A?Ko$#73!253?C?A?A???C#93!21?C#73!9?@K$#62!254?O??C?CA$#69!254?C!5?@$#79!254?A??@?@$#36!255?OA!7?C!6?@?@!7?@!4?A!6?_$#107!255?@@?@$#94!255?_#64??C$#97!258?_-#0!59~#4@#15y!8~^FFFBBBFBNN^!8~}wo#29@??O_!4?C!8?C#24!5?@?O#14K_!83?_?G?E??@?@AA?A!6?AA@@#147E#63@C???@B@?E[EkoCC??_??@#29OC_G!6?__?A???@#77?@#57@!8?@!9?@#48OOO#36@#62@!8?AD#108G#36G#11C#15w!86~NNFNFN^!11~NF@@!6?@!14~s#4@#0!59~$#5!59?A#14D!8?_WG?CCC?C?O_!8?@C#19C#44G!8?@!6?A#75C#90G_#27!4?AG_#12!86?_!4?A#17A!7?CC!5?A#6A#153?C#61@??I?K??G???GA?GO!4?@#28_GA!7?O?_#95C!7?@#65?@?@!4?A#34@!5?GOG_ccw_??ONB#79OA???E#12w#14F!86?oOG?G#12O#22_#14!11?OGEA@!4?@a!14?J#5A$#6!59?C#17!10?_?G!5?O_#12!10?A#22G#94A?O#87_#56!4?@#24A#14@AG_?J_#77A#76_#17!6?@CO#6!86?_#7OG#18C??A!4?CC!9?@#62?Q!7?O_#67?O??w#57@?@@E???@#33oGC??A?S?A??@@#63O?O!6?__?_A??C#30A?C#72_#33C?`G!5?_G?C#65_??O#18??A#19!88?_#16O#18O#17O#21_#16!12?_!7?@?G#6!15?C$#7!59?G#24!11?_O#20G#33___#13G#47_#35!11?@#42@#13_#33@CGO__?A!9?BG_??@#20?AG_#22!86?O#13G#163O#156O?G#158_?G?GgGG!6?_O?G#5G#60WOtEOCK???A!8?C#90_??@B!9?_#67G?KC?KKE]IC]UkYsKwgw__#74O#80@AE#130A#27@AC?_?C#94_G#25A#63O_#72G#104??@#24!89?_#26_#38_#17!14?O??A?@@#35?_#12S#7!15?G$#8!59?O#35!11?O#90_#39_#44GG#18G#40_#95!13?A#18O#30A!8?O!8?O#42!5?C#15@B^!83~^^FF@B@?@?@@B@!6B@@#162OG#65???C_?o??@!4?E@?C#38@!4?CA#34??ow{{{}AA@@@#72O???_#62?_#107_#60??`#23??@#77?@??A?C!5?C#23??A?O#88G!7?o#23!108?_#18GC!5?A#8!16?O$#9!59?_#101!13?O#87O#56O#42O#103O#75!14?C#37_#34BFN^^~{w_!8?F^~~}{o#13A#152!88?_#171_#164_o#150CK#166oO__Oo_oO!5?_oo#72?_!4?A?_#88!5?@!6?A#43WUC!7?Gw#83C#139G???O#39!12?@#88A#50@@??C?G??G#60??AAA!4?G@??O#31!108?O?C???A#47S#9!16?_$#43!93?C#28G#17!7?A#19G_???C#78_#97O#154!98?G#149C#165O#159O!8?_W#4G!5?A#139?AG?G??o#64@Bx??w?o_???A#87G#47@#39??C!6?D#94C?A???@#107!15?C#46AA!4?@AW#139?@@!8?A#87!109?_??C#57A#38A$#80!93?O#76_#20!7?C#15@F^~s#11G#79O#155!100?G#172_#157?G??O???GG!6?G#69??@C#107???_!4?@@???G#77C?G#42O#84!5?@#80@@#75@#97@#30G#87??O#24O#42a#64C?GMQQW?SY?GQCGoCO?O#24G?_#97C#47!5?C!5?G#64_W#24!112?G!5?G$#91!93?G#25!8?G#13C#22O???O#88G#16!103?A!8?CC#160G!5?_#73?B??_G#148A#78!6?A#31AC???G#98!5?A#101A#137???@#91??G#141?C#61G?A!4?_!11?G#141O#90I#87O#98_#26!5?G#61K@!6?A@_#34!109?_OGCK[$#23!103?O#36!4?@#91@#5!103?C#104C#8CC#168??O#11?C??C#151_C#152C#36!8?O#138!9?A#91AC#74@#88!17?Q?_??@#106!13?@#145@#144@#140A#29!6?O#38?O#40A??@#67?C#28!112?O#29G???_$#38!103?_#173!110?_#153G#174!6?_#169O#156_#104C#155G#75!20?G#96A!9?A#93!8?_#73A??_??@_??@#69!19?C#30_#39O#73_?C?_C@#46!110?_OG#48O$#167!215?O#175!7?_#170O#161G#108!21?O#82G__#108!17?_#56!30?@#24?OC@#49!115?_o_$#165!225?O#83!22?OO#13!169?@$#24!248?_#21!170?A-#0!58~N#9@#15!9~|_#24C#28@!5?@?C#14@A?W_!8?O#19O#90@?O#34E^~~}wo!7?@@xx~~wo#15@F^!75~^FF@@#155O!5?O!8?_G??C#158G#181!5?A_#65@!4?@@?__!7?A??G#29@?CCCKKAHE@IEAIECC??CCC?GG?GGG??GWW???@!4?GK#101CC#60C?C??_IA#15{!85~J!8?y!5~^FB#33_?GA@#49_}^F@#15_{~~N^NNN^!8~#9@#0N!58~$#1!58?o#10A#14!9?AO#38A#29E?_!4?A#47A#23C#15@FF^!7~{_#36G#82Co#28C#33H_??@C!9?AEE??C#14?AG#13_#14!75?_W?A#16A@#171_?@??@#159g!7?A!4?C!5?@O#60GE???[?_O[??__!5?G#90@A!8?@#47_#61?_??_?_!4?O??_??@!4?A#56C!6?_#27O#23_???A#64E?@#108G#88A#14_oA!85?C@!7?D!5?_GCB!8?_W???O???O#10!9?E#1o$#11!59?{#17!10?CO!8?@A??_!8?@C_#75O#29@H_#47!4?@#24CO???@!8?@CO#20_#18!77?_#7O#6GC#165_!5?k!9?OC#171_#151O#167_#61!6?A!5?_?PKBO{OW?_!5?_#42G!7?O?O?O??@GG???G!8?O??_#30G!9?@#65O?_GG_G#17O#8C#13@#17!85?_C@!5?H#13!7?O??@!9?A??_?OO#20_#11!9?w$#13!70?G_#78_#33H!8?_#13G!9?A#88@?_#94G_#30O#56!4?A#35AG???G!9?AG#13!79?G#170_o#149A@#152@#183_G#179?A#180U!5?OG!5?A#63!6?C_!6?EA?CAKE???_??_#88O???_#79_#103_!4?_!7?_!7?_??A_#69a#64@@#34OwW{{~^FB@#103_#63__G#72A?@#16??G#22!86?O#13A#24C@#49{}}#25@#18E!9?C#34oo[MB#27?_#53GA#10C#17@#19???_#21_#23_$#19!70?A#102@#80O#34E^~~~}{wo#16C#19O#22!10?G#74C#76G#43E#39A#16!6?@???A#107C#82AW_#29c#27!4?AG_#151!79?_#12C#161GC#164O{]v~#162?@#166}BkpIFA@!5?{BiSJw#72@?@#73B???A#64?@?B@B@N^{Sw_#72O#108G?O#74_???_!9?@#65_??O?O??`???_#39G!4?C?A#57???_#87O#90OA#24G#62O@?A?OC@#20!87?G#33_C#30@#40??A#11O#35!7?_??A!6?_G#44A$#35!70?@#107G#101G#90O#31!5?@#27?G#25O#24_#107!10?A#91@#98A#27!8?G_#20_#13_C_#98@?O#172!88?O#175?G#156GA#167!5?@#173{RMD!9?{Tjs#4?G#62O!6?G??G???_#67?AG?O#84?C#39A!5?G???C??C#69O#41G#88?P?O??@???@#79_!5?A!9?_!8?@#21!88?O#28O#46o??@#41G#12_#16!8?G!9?O!9?_$#93!73?_#44!8?G#93!12?A#17!10?CO??O!9?@CO#163!82?A#176!10?O?@!5?OA!4?C#69?Eo??_#141!9?O#148?@#76@AC#33??@?A?A?@#28C?C?@!5?CC!6?G???G!5?A!9?A#61GO@C#57!91?_#101G#34GA@#42?O#36!9?O??@#46_OC#45??O#42C@$#15!106?@FMw#108A#36_#39@?G#182!90?C#157!10?_?C!6?@#139!7?G???A!4?_#83!7?@?C#87?C?G#96GO???G???G!9?CCC???C#138?C#53???_#93@!8?W#138?@#73?o?A#103!91?A#87A#45???_#138!9?_??A#56@#48_O@$#14!106?AGP#18@#75?G#87AG#163!103?_#175_???@#172G#178@#79!9?{}L?{#73!11?C#91@A#107?__#24O#26O#27?O???O?GC?G#60_#31??P!6?A!6?_#102?C#33?C?B??GCA#95!5?_SD#85!95?C#23!10?G#24C#30C#50??G$#76!111?C#81_#30AO#181!102?G#161O#169C???_#107!11?@#36Q#82!14?A#95??O#30A???A#40O!7?@AA!5?G!6?C#89!6?_#107!12?O#101!108?O#43G$#88!111?O#91C#43C#170!105?O?A#177?B#34!31?@@B@@#43CA?I@?@@#87!4?A!6?C#43C!9?A!8?@$#152!219?GC#164o[#101!32?G#78!5?_!7?O#25??G!5?O#74??A!6?@#94?@$#156!220?_GA#25!38?O#93_#38??O!5?A??A!5?OOO??C$#160!220?@#147@#64!44?__!6?__#67_!7?@$#174!220?A#23!46?O??O#26??G!5?O???O?O$#63!273?_???_!4?@@A@$#77!273?@!6?A#27C#23_$#98!273?A#57G???A$#108!274?@#91A?A#107@$#93!275?@#102A#24O!4?A???C?A$#75!277?@-#0!58~#1~#12~#15!11~}ww#91@?GO#43O__#33?O?C#35A?O#19_!7?AO#91@C?_#26??__O??O#35O?G#82B[o#84_#43_#29?_??A#17@??_#14!19?_??O?O#22?_#14!42?OKB#16@#170CS|f_#164J~~z|w!9?k~{#147C#5_!4?G?C#72CCG!4?_!6?@#141?@?C??O??G?K???G???G#139?_?_???_#72???_!7?_!9?_#73OOgA#11?_?C#17@#14!50?_?_!31?_OE@!7?_B?__GC@#34ow[F@#56??_GA#14GA_GC@!7?_I#1!7?~#0!58~$#14!71?@E?o#30@ACG?OO???G#14AC?_!5?Dg#76@G#43@???_#27??_O#17@#14@AO_#76C_#94?O#28???_#40O??O#15BF!19~^~^N^N^^!42~nB#6GA#161I#175G?W#165F!5?@!8?A#187O#156??_#157O?O???C#80G#64B#60@?O_UC@E_?`A`UA?E_??@??_??_!6?_!6?OO!8?@!4?_?C??G!4?C??GC?A#12!86?_GA#30OA#49o{~^#21_#9C#35!4?_?C#30G?@#49_{}~NB!8?o{{{[#24O#12@!7?~$#17!72?@?G#76C#34@BFNNNn}wo_#16G#13O#78!7?@G#81C_#30@!7?G!7?G?_?O_#27O#21A#14@CW#13!21?_#17__?_#8!44?_#176_G#173_#184b#155A?W!6?_!7?@#169???B#65?_#74_!5?_#61A???`O?W???S??@{@Wo??_@Oo`?Oo_?O_Oo?oOo?KG???W???@!4?OA?o?_??_@G?B@??A#14GE#20!86?OC#25C#34WF@#46?_@#11G#16!4?O#17G?@#31@#46_?A@?OC@!6?O??A?A#29C#18M$#22!73?C?_!9?@#15@BN^!5~yO#22_#31O#94A?O#42!5?@#21A#13A?C#16O#24G#90@C#42!6?@#25C#19C#12G#19!25?_#11!45?O#17C#157C#180@#171!4?S!4?C!9?B?B#172G#173??FDA@@#108?O#73?A?[?@K?@OC?K???O!9?@#72_??_#65!9?_!13?@#47@#69A!8?G#139C#65O#104?O#15_ow!50~^~^!31~^N@!9?{~^^FB#33_?CA#48W#9!4?_#15_o{^FB!9?t!6~$#35!73?A#37CO#90CG#98_#29O?_?@#21@#42C?_#12!8?C#80A#84A#82Wo#34@F!4Nc_!7?FN^^NNc__#24_#159!72?O#166o#186O#156!4?_!4?A#180F_#173@pg#152A!6?O#76???_!5?O#36@!7?_!6?_#64BFUgXMC@?MECENJ@DCAKKAeA@|GWDH\HoWdDLCKo?OGoo?AA@#8!4?G#23!88?_G#33_#50_?A#40??C#19B#23!5?O#12A#77C#87A#47!6?O#18GA#16@??G!8?O$#95!73?@#80A#94A#24O#140_#26!6?A#17?C#108!10?C#95_#29?A?O??O?A#25C#19C#15DG#75@#78_#97AG#22!8?A#35O#183!82?CA#151?G#176G?E!9?EO!5?@#139??C_@???_?C?`?G_#63!4?_??o@AOOHA?O_O??LA@HW@E?oc?O_o?a?wOgOKGgO???[D???oGC#43!89?_G#56@#48G#42???G#22O#102!5?_#24G#103A#45!8?C@#13??O?A#20A@!4?@$#108!75?G#107_#23!9?G#90!13?C?_#23!6?G#22G#11a#107C#95O#47!10?G#172!86?O#166F}GC!8?@FGIDEA#79???OM!5?O?G#67!8?GEE?A!7?CEAB?@AC???AFBAEAEMCYAAA#56?@??@#23C#62GO??A?_???@#93!88?O#35@#106C#45!4?O#90!7?O#86!9?O#12C#19??_?C#33G??@@#53_#35_$#40!86?O#98!13?G#33EGOO??GO_!5?B?O!4?GW#157!88?O#168??O!9?G#105?O#95O???G!8?A#79!19?@#36@#42!27?AA#24AA#26@#107@?_#79_OKFB@#88!90?A#53!5?A#25!22?_?C#26A???@A$#28!101?G#31!8?_#44@#108A#158!102?A!8?G#81???_!4?_#107?@#65A?A???BA?A?K?G!8?A#53!31?@#36CC#29@#31C#57A#22!7?O#21!119?O#34_?CA#85???G$#181!215?@#170{|V#179O#181!8?G??A#69!5?G_Q@?GO?O_O#27!43?@#96??A#77C#27!128?O#48_G??A$#154!216?@#175Ag#182C#75!9?O?o??_#62???G!5?G???A??G@!4?K???GO??G?GO???@!6?_#88!15?G#31!128?G#22@#38?@$#185!219?g#82!9?_o#23!10?C$#88!230?G??G!7?G-#0!58~o#11{#15!15~}wg_#82C?_#34FN^~~}{wo_#14KWo_?BK#75@CO#91A#43B#28@_#27!4?@C?_#74@#82@N_#29G_#27!5?@#42A?O#11?_#14!11?_?A#11A#22@#18@!6?o#14Y!29?_?_??C?@@#171CG?O_!7?AG_#187??AO!9?A#177@#189@#75@!5?H!5?G?_#64O#62?_?O#64!8?@#65?w???@AAG!5?B?A???@???GEC!4?!4G!5?AA#166_!6?o#13C??G!6?_#14!38?S@!5?@@?CW_!21?_WE@!9?WBKC@#33GC??AB!8?_O??@#127???O#10_#23C#18A#11@!8?[#0o!58~$#1!58?N#12B#14!15?@EO#13O#87@C#29CO#33O_??@#27A#23A#15@BBFN^~{oo#82BFKo#29M#33L_!5?O!5?A?_!5?C?_#13!14?G#75_G#29o#34O{!4}#27W#13@#12!33?_#11_OC#165G!4?_G?_!4?@G_#183@GA#147?A#157C_#173Jo#170Zm_#171W#154!4?G_#102G#83eA!4?GK#61@??O?`?LGsG_Z??`BGB@!5?OEYBB!4?COA?G]AA???NKAG??A?@@bNC`??CA#178OAq#8C??G!6?_#11_#17!40?s?@?@???C!25?_G#16A#47A!6?_#18O???G?@#30?@#29?@#48CA#42?_!6?G!6?_#25G#56A#114C#10!9?_#1N$#17!76?@??_!9?@!8?AG#32_#81Go#87C#90O#34A^~~~}w_!6?N^!4~}wo#15@FN!11~^F@@!9?d!29~^~^^^BB#175OO??_!6?@DK#180!6?@#151O#181O#161?C!7?A#74A#82AEXtd_w|uqwo_#73CGA!4?C??A??_???Aw?K?_o?_??Y?G@?GP??O?_wW??O?O?ooGW??g?CE#186_!6?_#170___#7O#161_#12O#131_#13!42?A@#42A?A#16@???G_#24!24?_G!8?C#15_[BB#28O?A#17!7?_GA?O??@!7?G#12!10?B$#78!77?A?O#43BG_#39_#31!4?@#40C#16A#42O_#18_#37!4?@#78C#107O#79_#94@#97G#98_#39O#35!5?@??c#75CO#91[#30DO#20!6?@#13@?G#17!13?O#16C#91o#36A#30CA#57@#38@#56@#42@A#17A#22!35?G???@#164CKGO!7?@F]v|{_!7?F~|}w_#76CO!8?C#63ACG!9?_???E!7?G?@!7?H@??@@@??@?B?E@?@!5?A!5?A#195w#149@!5?O#165_#190?_#18!43?G#24_!7?_#44!25?O?@#34_G@???G!5?_owMF!4?O!7?GCE@$#16!77?C#18G#91AG#30BG#20!7?C#13C#19O#88!6?A#108G#24!10?A#17A?W#37O#88_#94@#43o#18!8?A?O#19!13?_#37G#107C#90G#33g#21@#40!4?c#19C#18!35?_#189GC#156G???C!8?CO???@G!6?@_???C#142C#81@@?GYO?A@#24@??O#139@??P??G?A?GO!6?C?A!5?KC!5?C?A!7?_!8?C#64?@#159?_O!6?G#6G#163O#14C?G??OO??_#36!39?A#28C#46O?C#20A??O#35!26?C#29O?@#50?B#46?_!9?OGC?@???@!4?_OG?A???C$#88!77?@#75A#80G#93O#94O#38!8?G#35G#14!20?@#19G?_#97?A#47!9?C?_#88!14?O#102?C#138A#44!5?@#35G#4!36?_#161_??A#17@#6@#162C#166BCKO_!9?@Ns#184@#175CP#165K!5?O#80G_???E@#108???A#72@G?_??_!8?C?C!5?@@?_???@?__!7?@@A!4?A#79???_???OK??@@#5C@#192C#16BC#173O#17?G??O#57!45?C#29w#19@#49wwwo_#43!27?_#39_#33W?E#41???O#26A???_#57G?@#19!7?O!4?G#20C!7?O$#83!78?@#108?_#21!31?C#12A#79A#81A#40!11?G#14A?O#147!60?O#13A#155CoO!9?AO#159!6?G#194??G#155??O#37!6?O#93!5?C#65!4?C@?@?C#69O??BH?C?KO_??@A}?K???O???C!4?_??G?O!4?___o!5?_?OG@#4GC#191_C#196G#160G#15BBFBFF!4N^^^!37~j!8?BBF^!21~^F@#101O#90G#28CE#12!5?_C#13_#16O?A#101C#38!8?G#15_OKEB@!9?o{~~^~^~~~$#107!78?C#25!33?G#18@#95?G#16!12?C#8!64?A#179G?O#159A#167@#158CGO_#168O#176!9?A#185!5?A#138!12?A#60!5?A?AOAAfBoScOnQGOwE?@@?OC@CGoB??PW_??S_C{E???OKOCFCFEeWO??@???@#180??O#93!52?G#33?C#50C#31A#59C#11A#45G#102!28?C#38@#49_w{~^F!8?_oww{~NB!6?_oow~~NB@$#22!113?C#35!14?G#184!64?_#190A#170_!6?@EIo_#178!6?_#107!25?A?KG!6?@!6?o??_!6?G_!4?OE??_#37???_#141???@#36!4?O!7?A#147O!7?O#138!48?O#34?g#47???C#53O#87!29?A#48O#19!5?G@#44?_?C#54!9?C#21C!5?A#27A#48GC$#47!113?O#10!81?@#172A#181?A#180GO_#152A?O!7?C???A_#79!22?C!15?K??O!6?oC?A??_G#62!21?O#100G#197O#169_???_#77!54?A#89???G#53!37?@#17A#23??O?A#26!9?A!5?C$#185!199?O#173BAG#193G#62!56?_???C!9?_!6?o?@#174!14?G#190G#13!115?O?@#47_#24O??@!7?@$#187!199?_#188?@#186C?_#95!63?gC#64?A#14!144?gQHC!9?oGA??_?_$#23!268?O#141??C#22!144?C#11_#12??A$#23!416?@-#0!59~#6_#15B?@???@@BFFN^!6~{{o#74C?_#34@BFNNFbow{wo_#14CG??g#36G#81C?_#91O#34!4Bbr{w!5opz~~~N~~}{wo_#14?G?_!4?z#36C#76^#91~#33OA@#26!4?N#17C#15I!16~^~NNFFBB@@?@#167C!9?G#175@?A??O!5?@A#173O_!7?G#175@AK_#156?_#183?C#7?@#76@?G!9?@!4?C??G???O???_#72?O!4?G#64?__!4?_!4?_?_o!8?GCC#173_OGKA!7?K#170oN@#187_!9?_#13@???A???C#7G??O?_#12!21?@#11O#24O!7?C#15@N!13~^^^FF@@#34_oo{}}{w{___oow{}~`!4?G???_owKA#33???_O#35_??@???C#29?C#42A???O#12C#6_#0!59~$#7!59?O#20O!4?A#47C???_#12!9?A#13G#76A?O#81_#33C???wSJC!4?_!8?@?CCcSKA?G???GIC???o#27??@#26A???_#17?_#37!6?p#93_#39?_@#27!5?_#18Q#14t!16?_?OOGGCCAA@#5?A#160???@!7?O#152G!9?C#156@#183??A#172??A#154?_#186AG_#155AG_!5?G#81ACO_!7?AA?CC??G?O?O?_?_#95O??_#36?@!4?A#141_?__?_o#139!6?@!4?A@A#160_#176_G?C!9?@#188_A#175OA#156C#199OB!7?OO!8?_#150O#22G#161_#162_#14!22?U#18G#23_#33C_!5?_#14Ao!13?__?G?A#42GC!9?A!4?@!7?O#21G?O?CA!8?G!7?A#40?AA#33c#25G#13_#7O$#8!59?G#12C#14@?@@@?A??GO_!6?B??_#43@?C?O?_#27?G?A??G#16C#24O#12?O??O#82BKW#43??gO!8?BC#21!9?@#12@???O#107!7?I#28??A#34{}!4~#40O#19H#12!19?_#8_!7?A??@#158???@@#181A!4?_#165@??@AC!6?O_??CO!4?@?O???@#163A#88A#82@BN\nz\~vn|[k{gWGOOo_o__?_#65@#79!10?@@#65???CC!5?G!8?S#4?G#166oWgSA@!6?{B#152G#171?_G@#191??@???A???C???G??O#155O?_#22!25?_#28@#34BU#49|||]{#19@_#17!16?_#22O#13GCA#29OCE!6?CO???C#50???G!5?A!6?C!5?C!7?_O#48??G#31?C#8?G$#9!59?E#19g#21C?A!5?O#17O_#35!8?@#19C#16O#18_#29A?G?O!8?O!8?@EcOGWG??C!4?AC#44!9?ACGO_#29!10?C#20!7?_#159!21?_!5?C!6?@??@ACG??_!9?_!6?CO#161@C#152O#185?C#187?@#177???C#189C#37G#23O#80_#84AOCa?GO?_O?O?O#63AA?C?BA!5?G!6?O!7?Go_?_???@!8?A#159C!9?G#182!4?O#185A#204!4?A?C?K???G?O?O#171???_#149O#44!25?C#87A#29O#50@AA#48A#51_#27@O#12C#103!17?_#44OGC#16@#23@!8?C#10A#14B@#19@#48!4?@#51??GA@!9?OGCA@#15OGEF@@#9!8?C$#10!59?@#22?A!6?C#16G#24!11?@#36G#75G#30@A?G#28_#39O#26??C!6?_#21_#17_C?_#90@??O#30?_!8?G@#25!10?CG#38O#30!12?G#17!29?O?G!6?@#161!7?@!4?O!6?@A#157O!7?AG#158_#75!11?C#200_#83!8?A#108?@#75A!6?G???OO??_#107!14?@!6?@??A?A???@#167O#5??A#186_O?@#164O!9?OE!7?_#11@???A???C!5?O?_#47!21?@#93G#30G#46G#52???@#89A#21A#17W#77!18?_#28_O???@#46?@?C?A!8?U#27???_???_??CA#28@#101!4?_#39G#19OC!8?@@?a#10?@$#77!61?O???C#11A#42G#40O#27_#107!11?A#98@#79O#91GO_#42!6?@A#20AC??O#68?A#73A#83@#75G#74_#28A?G#42!5?@#24A#31C#26G#77C#28C#13!12?ACG!7?C#43???@#176!30?_?O?G?C??AA!5?C#170@AC@ISg!6?@C?_!7?ALoO#172!7?O#73!12?@#61@?@@?FB!5?G??CW@OwSo?OOOGNGGAM]?O??g??OOOWGC#157!4?A@#161GA!7?@#201?G#196!5?@???A#190A#14?@!4?A??C??G?O??_#103!19?A#101C#43_#25!6?G#138!20?O#33_??GA@!4?O?O!4?@#78!6?_#17OO#16G??@#46O?@!5?A!8?GG?G#206??A$#75!61?_#18A!4?C#44G#82!14?C#87CG#90O_#35!6?@#13@#15@BBFN[O_#79O#76O#94Cg#35!7?@#78@#60C#87H#15!13?@BFF^^!4~#6!35?O?G?C!5?@#163!6?A#150C#155@?CGC_O_!4?AG#180!4?@#80!28?A??C!8?O??_#72!21?O#47!4?_#25_#36@#193!6?_!9?C#178!8?@!6?C???G#36!62?A#39G#87@#49???@BB@!9?}~~VD!7?w}~nVJ@!8?_oooO$#93!61?G#38C#30G??G#94!16?A#38!11?C#86A#23G#206G#103??@#80@#88C#97?A#104!10?A#88A#98A#166!59?__OoGWgScKQiQIsGoO_!7?@??G!7?@FSo#64!25?@!5?EC#141C#73@A?@?@@_A!4?CA@??AA???B?CA?CdB`???A@??@#168??C!7?O#205!10?A???c???G#43!64?G#102A#48!4?A#40??G#20G@#17CA#18!10?_C#14CB#15@#23?@#57!6?O#13__#14SH?AA@@!4?@B$#43!62?G#33OgG#93!37?A!13?@#157!60?O?G!4?A#184!10?AC???O_!5?GO#88!34?A!5?G#69??AC?A@A!5?B??CA??@!4?C?B??G?k?@A#93_#180!7?_!5?C#200!12?@???A#15?@@@B@BBBFFFNN^~^!18~g#27!42?@#22C#25G#30G?A#56!8?C#12G?CA#48?_#38!7?C#12G#11A?G#34_oWGCCC$#90!62?O#87CC#173!115?_?o_OgWokSksGo?_#147G#180O!4?@A#169CG#151O#150_!7?G#94!28?_?_#139@???!4GC?O!4?@?CA#150!29?OC?@#203A#178O#167_A#131!12?@???A??CC??G#45!71?G#31?CA#24!10?A#11A#40OG#31!10?A#20_#17O???A?@??@?W$#91!62?_#29_#34Oooo_#10!111?C#16?A#13?@#171!14?@BCC!7?GO?@!7?AK!6?_#60!15?A!6?@?@AKA?E]kEBGDGGKF???@?@GN?C?qW?CKC!4?B#165!4?_?A!8?@#18!8?@#203G#17??A???C???G#29!83?@#19@#44G#130!11?@#24?_???C!4?A$#36!64?A#201!134?A?G#177G#158@ACG#90!38?_#102G#79C#62??@??C???K!5?_?G??_!4OC@?O?OGE???G???OA@??@#169??G!4?O#163!19?C???G#47!86?_#30!16?_#22C#26G$#154!199?_#164?A?G!5?BFL~}w_!5?@B]~z}wo#91!18?_#154!48?OD#198G@!8?_{~}{}w{OCGwow_o_o_#47!102?O#27O$#179!203?O_?_#155!90?G!7?_C#44!123?G$#187!204?O_#199!91?_#207C!9?G$#185!204?G#182O???C#202!87?A#195_{B#190B$#168!205?AC#204!91?O#191?G$#200!300?_$#208!300?C-#0!59~#1_#12_???_#33@ACGO_A?G#13A!4?_#78!4?@#73A#76ACG?_#34BFN^^!4~}}!6{}}!14~^@??BF!5~}{woo_!6?@FNN^^}o#13@?_#6!8?_??G??A#158A#186!13?_?O!6?GO#159?@?AC??O_??@A!6?@CG_#186C?_#183???@??O#7@#36A#82@@FN]j|^v|#91C!7?@@#82!6@#81@#76@#97A???A#95@#90G???C?C#87C!5?A#57@!5?@#161?_O?A@!7?OC#155GA!6?_O??C#156A#199!11?AG#185@#170@CGCGO_#14CGOo!13?J#17G!9?C!6G!6?A#46__!4?_G!18?@???o??A?@!8?_?G?C?A@??CA@#12??_#1_#0!59~$#2!59?W#13OCGO#29@ACGO_@#31@#18@#14@@???O!4?BCGo_#43@A???_#42!7?@#21@??@#138@#33A?@!14?_}~~{w!7?CG??_!5?A??O__@G#11A#14C!7?_?W??A??@#161!15?_???G!5?_!6?G??@#165O!6?@_?_???@A?O#185_#75!4?@#23C#81CGO_#97!5?w#29_IOAQC_KO?cCSC_GO?g?g?GSg?KGO?gOO?GC!5?O@#166_{FA@!7?oKB#165_?C!8?GCA@#207!11?@#164B}#165A#175Qswo_#17C#18GO_#11!15?O#42G!7?AG!4?O!9?A#48_?OOO#33!20?G@???@???O???A`?OG???@#53??_#47_#10_#14_OGC?E#2W$#3!59?C#14M?O#78G#69O#87CG#94O_#57???A#27_o?_#16O#17_!6?CG#82AC?O_#11!11?@#25AA#90?@#30@#27!26?@!6?_??_#90_??_#21!4?@?_#18!10?O!5?@#175!15?_?O??_!8?@?C?G?A??A!9?C?CO#163!6?C#76A#83A#74O_#84@SA_GAB#28S@cH_GQ?cGO?_GOC_G?OCOc??S_?cG??G?C#26A#79@#30A???G#6_#180G@!4?C#207_GA!7?_??_!8?C@#183!11?C#171?o#155g#152A??GO_#147_#16!16?_#24O#30@!4?G#46B#47@#12A#15FBFFFBFBB@B@@#36A#19@#47@#49___#50G#28!20?C?C!6?_#90_!4?@#23@?A#40A#135!5?O#45O#24GCA@#13?W#3C$#4!59?A#11@#15w__#83C#65_#34@BFN]{{O?_#12G!7?A#75@#81@??O#29CGO!7?@?A???A#23!29?@#17@#14@AE?@G#75@#91@G#30CG?_!4?_#15B^!6~^^FNF@B@#176C??@#170!14?__oOOoo_!4?@AA!4?_?@!9?BBIw__#154!6?G#88G#200_#43!8?GsJsKqKoIuGwGoKoKsSkOkO_SgOoGoOKCK#93A#139@#42??@???G#176OA#173WDA@!7?oK@#152@#185?@!8?O#205O!13?O#177?G#163@#162@!4?O#18!17?C#31_#29A?WGW#48A#56?C#13@#14?C???C?CCA#18C#13A?@#56??@#29@#42!23?E!9?_#31_!4?C#44?@#15!8?_ow}@#4A$#5!59?@#73?@A#93A#88G#80O#75_#82_#25!5?C#38G#15BFNN^~~~{wo#79C#95G?_#33@?G??_#26!7?A#22@#29!31?A#47A#18A!4?A#79G#76O#43Ag#28_O#40!4?C#35C#12G!8?_#157?_#166__OWgSeIVhQDY_JsATgRCI@E?DA@EHVISgWO__#187@?A?G!6?O!8?C#190!7?O#90!9?B???@??A#94!4?AAA!8?A#101A??AA#72@#24A??A#38!5?@#27@#101_#56C#21A#168??G#151O??@#198OC@!7?_w]^NFFB@?_w}!10~{_#179?C#35??@#6A#15@BBFNN!13~s#20A#87@#43Wo?o#27!6?O#26O#25O?O!4?G#79C#57C#87C#23!28?@#41A#49Ow{[MMFB@!5?_ooww{]NFB@#5!4?@$#35!61?A#76@#98@#90A#91G#84O#35!6?A#44C#20G#21O#47_#19!9?O#98A???_#24!45?C#35C???@#80A#81G#39@#98O#87O#57!5?A#38O#18O#17!11?G#13C#173__OgWsgUlyd^sJ|iVKZDM@FADE@EGSgO__#155@AC???_??O!9?C?G!5?A#87!17?@!4?AA#75!5?@#24@#80@#88@#139@??@#62@?@@#138A#60@@#33_O?GC?A?oD#90A#105C#209??_#169_??A#195_WMB#181GA#184_#170GA@!7?_O#175G#150!19?A#13@#16A#169G#103!18?@#93C#28cG_#35!12?GG#28O!6?A#36!26?A#65@#48_??A?@???@!6?G?C#9!5?G?A$#142!62?C#108C#22!11?C#91!11?@#18_#94CGO#15!47?@@FM#26__#82C#83O#47!8?G#159!12?O#167O#8C#160C??@#152!15?O???GG!5?@??C???_!4?CG???G#161?@#173GO#184O#98!26?@!8?AA?AAA?A#79@#102?A#141@!7?@#103!7?O#186!4?C?A#164WC@!7?OK??__OG??A#161!20?C#149C#101!20?A#34?ACCCc{oo!10_oooWW[[MNFv~NFB^!13~}rwwKEA@`??GC???__OGCEAB@??G$#26!75?O#108!12?O#28@#30AC?O_#25!44?G#20G???O#84A#94C#169!23?G#11A#168!19?G!7?_#157A??G#180G??_!5?GO#162?_??O#168A#193?G#77!47?A#23!4?A#64@@#152!13?G#147O#171O!9?_G?@!5?GC?G#33!43?C??_O?G?O!8?O??G?C?A!5?ow{_#50!18?GC??O???A!5?O$#84!89?G#40!50?O#13G#16O#11C#88C#155!48?_#169!6?@#176C!8?@!8?C#181O#39!54?_#88A#34_oow{{}MA#174!6?G#172G#175C!7?OC#199?OC@??G??A@#50!44?@A#38!12?O#94O#103G#16C#96?G#24!32?@#39???_???C#25_C?G???@$#31!141?O#22?O#193!49?G#163!8?A#147C!7?A#172G??O#150?A#166@BKo_#185!69?_#187_#156?A#208_#149_#159OC@#168?A#182O#177?A!5?O#201?_#49!46?@BB@#31!12?G#43!40?OOGCA#24A#19A#17A!9?O?C?@$#181!204?O#179@!5?_?A#157!4?A#193!73?C#163??@#196?O#190GA#187!6?A!7?_@#29!102?G!6?C$#185!206?ACG!5?_#191!83?C@#183!9?O??C#56!106?O#101G#65GK#35C#16@@$#184!206?GOO#156?C???CGOC!7?C!4?@???_#167!63?_#179!11?_#108!109?O#47O#27O$#150!206?O#164A?C??o`BFMG_!6?BBM^~m{o_$#171!207?@CGOW!6?O!5?@?GO_???GO$#151!211?C???_$#169!212?C$#182!212?@-#0!60~#3_#14g!4?C?O_#28@?C#29K#34EN^}}{wwwoo___#30_!7?@!4?A#33!14?___oww{!8~}w_#48!6?_?G_C_GOcGS_GcSs#27A_#15@FBB?@#174??@#152!13?_???CA#155GO_?OK_WdOK_K_O#166@?BCHBKsKgO__!8?@ACKo_#168_#183!4?C???_#83@@CC#74_#97BO#43RKrKrKrKrKrKrKrKrKB?B?B?@?@#28!10?O#23O#6G!9?G#173_oGE@#201C#171S?G?C??@_???@#199!16?{#156_#175OR~y^zn[wo#14@A?G_!8?O#11C#24C#29WOA#30@#46??_G??AA??@@@!7?_#33__w{}!6~}{{wwoo!8_???AD?C!9?O#17_#14_O!9?g#3_#0}!59~$#5!60?O#15V~~~}ww_#76C?O_#56@#30@#57???@#20@???C!9?G#90@?A?C#46!41?OG?C??!7@?A#49Go#17@OO!4?@#170!16?_OOGWsIX@??@@!4?BMC[Goo_!4?@???G??_!7?@ADKGo_#179?_#187_?G??_#82BBNO_#33!17?_G??CCC?AA?@@!5?_OGE#158OCP#152OG!8?_?C#155?`?o?gOCI?@?@#207!17?B#171^#155n#170k?D?COaCGo_#6O#8_#12!10?@#31B#43DI@#49!4?_ow{[{m}]m}}m}v^NFFB#46!26?@??_?OG???A?@??G#9O#15_ow{}!5~V#5O#1@$#7!60?G#16!4?@A#17CGO_#33@@?Go_@??C!4?O#19G??O#79@A#82@?ACKG#34`ppw|!13~^^^NFFB!8?@F^!6~NFFBB@!7?@@B#33C#23C#14AGC?B#173_WoKyTi^iTze\qNytYNA@?@#184?@?_gOKaWcOKo?a#173?B?BE[QGoO_#163C!6?A#151C#162G#157A#173@A?GO#196@#189!7?@ACGO_#84?G#29@CPC`GQC`GQC`GQC`GACE?@?AA@?@!8?_#75_#101G#24C@#155_??CA#165@!6?_?_!5?C?_PG#198ow{!16~#165!6?_#152??@ACG#15@FF^!8~n#16A#42O#28aD#34o}~~^FFB!4@#50!9?_OGC@#29!27?A@??A#51???_??O#19_#24O#42G#18G#13G#22C#12A#11@!6?@$#8!60?C#78!5?@#75@A#97@#91A#98CG#84_#39O#40!4?A?C!4?O#21?O#56_#57_#76@???G#47_#33@??GDA#50!38?ooWw]umYui]uYg?G#24G#13C??C#104C#8A#193!17?O#175_gocAcE]FbQCAJbR?OG_o#188??O#158?@!6?_#156C??C?A!8?AC?O??@AC?O_#31O#91?C#28AGaGQC`GQC`GQC`GQC@?@?A?@#159!14?_#30@#166GE@!6?_OGE@#175G!7?_?ECA#205CA#174!26?@#18@#17AC#16G#13O#17!10?W#57G#39?_C#48!4?OGC??A!4?!4@#34!4?OWEB@!6?@BBFFNN!8^~~}|wWWKCEAB@@#56?_#41???C#35C#44A#21@#7!7?G$#10!60?A#107!6?A#82@ACGO#96A#11!6?@?A#27G#17C??G?@Q??_O_#138O#28AO#42!57?@#35A#16G?G#166_oWcKqDiT_TiCXaLoDID?DA@#152!14?@???G??_!4?A!8?G#153??O?_#193C#80!10?@ACG#90???K_#39!16?O??C#40!18?A#180_!4?@!5?A#158@#170O?A!4?OgWSGA@#169!29?A#180C#149G#150_#18!11?_#93_#33??G#51!8?_?O?_O??O?G_OG#30!31?C!4?@#85!9?A#53@#8!8?C$#11!60?@#79!7?CGO_#87AO_#31!5?A#14@?A??C?HI??OO#29?@??AME#41!56?O#60_#159_!4?A#161!17?G???@#186!13?@A?CG??_#159@???G??_!5?@#176?CG#167O#185!12?G#81A?GO_#34!19?o!5w{{{}}}!5~^NF@#176_#150OG#168A!9?O#161G?@#185?O?G!6?CA#163!30?O#94!70?A#48_??GC??A#10!16?A$#93!70?G#43A#35!9?A#15@@BBBFECKG#75A#97@???C#43G#160!61?O!4?@#186!17?CA#161!17?C#176!5?A#160A???O!9?G#186@!4?_#105!55?A#171?_?GC#198OG#195CA#176GC#156??_G?@!4?A#187???G#25!103?A??@$#24!82?C!4?O#16???C??_#36O#27?_#176!62?_O???A#172!43?@!6?@#169@#179?_#4!4?O#169!62?G?A#205_??A#204@@#190@#182??O?A#184_?o#43!110?@#49_oow{[{}NNFFB@$#42!84?G#23G!5?__?G#98A#102?O#6!63?G#181G#157C#147!46?A#155@A?CGOA_??C!6?ACGO_#163!54?CA#209@#207?GC#210_#167O#160C#186???A#199!4?@@#101!110?A#57@$#13!85?C!5?OG#81AC#157!115?C#180C??O???A???C#5?_#164!63?ooWEB@!6?OGMFEAB@??o#102!105?@$#107!92?C#74C#181!117?G#185@???G#157!71?C#208??_#149_O#196C#178A$#167!212?O#175C!4?@!7?@A?O#186!58?@#191???OG$#154!213?G!5?C??W$#165!213?A???O???O$#187!213?@???G$#164!214?@AECG@O_!7?@BFNZ^}sWo$#171!214?A???OW_?_!8?G$#161!215?O!4?@??_?A?GO$#177!215?@#150_!4?A-#0!60~{#4_#14O!7?@ACG??_#33@?AA?@E??O?__?`?Oo?@!4?@?@BBAFEFNMNN!4^v~fnnNN^^^!6~}o#50??o}nZtj]jsZvkZtmZt^_#34K#6b#166~@UgTASiPISaTgDI@A#175O?OyBjRHC?gS`?E_y?EGAT?HcAGaRG_EWOW_!9?C!6?G???@A???O#187G??G#74@#81@A#43@ELITiTiTiTiTiT__?_?_#77!8?_!4?A#104A!8?C#158G#152_OG?@#155GP??_??B#185C#165A@#199???@?G_!15?\#185E!6?_#152!5?@?G#14@CO!5?D#11O#23_#28A#30_#34!4~#50O!9?O?G??A@#101_??_???WG#39?O#46!10?__?O?GGCCA?@#85??_#20_#19O!5?@#114@#14!11?O#4_#0w!60~$#1!60?A#7O#15n!7~}{woo_#29@???C?AGO!7?__#46!8?_O?___#30!10?G?G???O??_#34!6?@N~~@#48@OcIS`SJcGRcIPcI_W#53`#7[#173?}hVi|jTmtj\iVYDE@@#184???_OGeOjSj?]`Y@EHATgJsAXeOKo?O_#166@A@EHEYcWgO__!5?@BAC???O?_#179G??@#190@A#88AC#23G#28@ASgPaCGPaCg@i??_?_#69!10?_#4_O#42C#159GC!4?_#5_#113o?_#167C#166M@A@#171!6?_!4?A@#198???@F^!15~#205A#165w!5?A#161!7?A#17@?O!7?L#24O#29D#33^#46!4?_!8?_?O??C#29_?O??G?_!5?G?G#48!9?_?OO!4?A?@!5?A#18C#23A#10A#7!14?O#1E$#2!60?@#9G#17!9?@A!4?_#34!4@?@FNN^^^~]]MM}}!4~}^M[[\Wxwopoo!4_#29??OOOo_#43_#39_#46!11?M#49!17?F#127O#186!15?_O??A@#165!15?OO_#186!11?@?AC?G?O#159@?AC?G!9?G#169O#167G#162_#185A!5?_#154??CG#83C#91GO_#39!11?C??O#30O?_?O!5?O???A#187_??GC?A#183C#191CA@#180_#168O#175_Ooa@?en]]L#187OG#205!6?A#207!17?_#179@!4?g#164_#162!7?@#149AG#22G!7?_#42K#43w#48!5?N#49~~v|nz|}VNJEBB@#30O?G?CA?_C???S??C?C#49!7?___oww{[}}nVNB@?@#24?@#9!14?G#2@$#11!61?C#79!10?@#93@!6?_#30A??G??_#27!4?@??@#48!7?__#135!52?A#170!16?_owKun@KC#179!12?_#170O!7?@?BFGCK_Oo_!4?@!5?O?_!4?@ACCG??_#183?C#97???@#90A#103O#44_#29@ACGPaCGPAS?ZOO??OOO!5?_???C?@#164_ooWMKB@#92G#189A@#210@#173E@#161A#164!7?__o{{}~~}wo#170!18?p_!5?CpI_GQCo#13A#15Bn!5~y#19A#93B#51!9?GAOCA@G?C@#48C#43??_??G?_!7?G#51!18?_??OG?CA@#40A#11!16?C$#13!61?B#107!11?AC#98@??C???O?_#40!8?@@#188!80?GC#155_G?CO?cOjSB?]`WDCh?DgAsAXcPK_CW_?_#161???O!6?C!4?a!6?C#158??_#155G??_#172!5?O#161_#34!14?E!5NFfvr~^NNFB@@#60C#171OG??O@#196G#123GC#111O#181A#186?GC#184!8?AC#207!7?C#175!19?AFFF?@NZMt^vkw#154C#18C#7_#34!25?__OGGCA@@!7?!4_!4o!4w[[[MMFFBB@@!6?C??@#12!15?A$#11!73?C#82@??CG?Oo__#152!125?A??G??_??@?A??G!7?OA??G?O#33!24?H!5?gGGK?_O?GCA#62G#175O#172G???_#178O#207?A#134o#211G#170???_K[umXO@@??B@#184!25?CGOg???_#151!7?O#33!28?_ooWKMEFBDFFbfFFBVFJNJ!4FBBB@@#27!10?O#12_???C#13!15?@$#76!74?A???O#84G#97G#43C#28C#173!126?@?@E@EGCW_?_#171@@?CAG?G__?_???AC??O?_CGO_#101!29?O#23O#24G#180O#93?@#157A!8?O#177!11?O#156G#155!27?GOgO??O#163!8?_#90!34?O???G???OG#28!26?G#15__ow{{}!11~n$#12!74?G#78G??_#167!136?@!5?O#156A??G?O!6?@!7?A#87!34?G#154_#185_#6??@!4?O#199?@#156!46?VC#42!44?__?OO#56??OG#11!28?O#14OG??A$#13!75?O#80G??_#188!136?_#157@??C?O!5?A#186?@!7?_#151!43?C#147@#165A#155@#177!51?G#94!45?O???G#47!31?G#17G$#75!75?C#81CG?O#158!137?A#176C!4?_???@!4?G?O#188?O#156!46?C#179_#187!52?O#93!46?O??O#42!32?C$#83!75?A#35O#90A?C?GGO?_#150!133?A!4?O#154???C#174?G#168A??G#169!48?A#202@#28!100?G$#91!76?A#95O#39A!6?O_#168!132?O#169G#201A#147G??@#177!4?O#184@?_$#185!220?@#164??BACCGOO?_???@@BFFNYSwo_$#165!223?C@?OC_!7?C??O$#180!223?O?_?A#181??C#173?C$#160!224?_?@??C???O?_$#155!224?G-#0!61~{#14@_#13!14?@!5?G#91@!4?C#36_#43BCACO#30??_#46??FO_!4?@??A?ECCC?GGG?W?OO#43@A??G?GO#46???Fo#48@IS`IS`KPiCPiCPIC#6_#166_]@UGRCPADWdQC#175owaRANPGc@_QGAh?SAOD_Y?IO???@?K?ACOGc@G`PHT?gKG[Oo__#181?@???C#165@?A??@??I!4?T!7?GO#23G!5?_!4?__!6?C#154C_#180_!7?A???@#131_??A#170@VLAE#185???@#198!14?@F^!12~#156_!8?G#161W!6?D#149@O#14S!5?BG_#29@#34@BN^~}}{}}}EF@???_oww[KEEE!6B@B!4@#96!12?_#130O?C#19CA#12A@#14!18?g@#0{!61~$#2!61?B#3_#15^!14~}{{wwoo___#28@??@?@C#33BCGO#50!4?K^}jvNykU{gOwWwW_oo__o!4_#28?C?GO#50!5?N}tj]tj]rmTzmTzmtB#25C#149@#173_}HVKzaDIFYlB@#155?CGo?cRGUHcPSCj_LaWD_V_??@A?B?LOJcBWaUGe?iOAo?_#173@@ACAKGWOG___#179A??C!8?O#178?C#162__#102@!7?O#33GKKGAA?DC?A?@#164OWWGCEEBB@@#158?A??A#9O#134YS#14O#150O#196_#164__oww{}}!13~wo!13?C!6?yk#191_#193c_!5?y#173o#17C!7?A?_#33AK?_!7?ww}~~FP??CAA?@@#51!5?_??_??_?O?_G?OC_GCA@??A@#22C#3!21?_#2B$#6!62?O#14!16?A?C?G?OO?_#29@@A?A@GGO_#48!4?B?@SGoCQg?Sg?_?_O??O#30??@?A?@???@C#34!21?O#31G#157A#168@!8?_??G#170GEXCD#185!18?@?_A??G#170!13?FDAE?KGWOo__??@?A#154C??G???OA!4?A???A#65A#28@?A?A?A?A?P#34CCCEABB@@#152O!5?@!7?_?o!4?A#155?AK??A#183!16?C#199G_#187!12?A!6?DO#215O#152AO#174!6?C#12A!7?C#19G#43A#28GO_#46??@?A???@#30!4?_?G!4?@#21!27?_O#15_ow{}!18~V#6O$#8!62?G#19!16?@#12A!8?_#20_#87?C#97G??_#33!25?@@@AA?CGHRb?B^}_#46!18?G#35O#158C#161??_!4?O_#152!4?C@#184?_GoIcRgUHehQSJoLaYDgS_??@A?BALOJsB[`UGu?gO?o?_!6?_#163??A#180C??G#185C#172@!9?A#35??@#7?C#43BCF?N?B?B?@?O??GGCCA#156_???A#176_???G!5?K#22C#212D_#111_#151_#165?O??C#207!17?A#177!14?W!5?G#176!11?G#8G#15J!5~{o#22O#87C#39C?O#49???!4@#50@#29!5?WA@#38A#93@#48_!6?!4C???!4A??@#16!10?_OG#9!22?G$#11!62?C#107!17?@!6?G?O#84!4?O#94O#34BFN^~~w_!6?@@@B@BBB!4FMEMKK[Woo_!4?@^w#59!17?_#53@#167W#188???_#186_?KgO???oA#179!23?C!7?_#152!12?@!7?O#159@?A?C??G!4?@???___!8?_#42G??O!9?O!6?@#61@#157_!5?C??@#161O!5?C#171??O??C??@#207!28?@#175[\WCR!6?V~i^w#180?A#13@#16_#44!6?@?O#31_#90_#28!15?C#87A@A?@#46G??CC!4?A?A???@@@#53!11?G#20G??@#11!20?C$#12!62?A#80!18?@!8?O#29!35?@?@EACOeg_#56!21?A#171!38?BK?K??O#194!15?@!7?O??_#167?G#164@@?AA?CCC?GG?G@HGG#77?A#93C#29@?D?DCD?AAPHHG?C#7_#155_??_!9?@#186_#92???@#128_#120@#189@#156!4?G??A#155!31?b?a_k???E#169!9?_#23!8?C#93G#94O#43!16?G#90C#39C#25?@#49_oo!6wW{{[{{[{m}]v~nz^vz|]NF@#27A@#13!21?A$#35!81?A#93@!8?O#39!40?C#201!63?G#165@??C??O?_#161!13?A?C!6?`!4?C??G!4?@!5?A#189!4?G#4?_#214_??_#40O#90O?O#87??O!6?A#24A#185C#172A#160@#147O#167_!4?C#119!4?G#121?G#213C#170!40?ADJ!5?@Ng?T_F#42!11?A#50!23?O?G$#22!82?C#83@??A#90@?AA???G??_#187!99?AS?O#170C#166!17?@?A@BDAECKOWOOo__#160OO#113_#152A?O!6?B#61!5?O#21O#47O???__!6?O#69O#5O???A#179O#178O#159?O?G#162C??@#124???A#132?A#214G#184!40?_?O!4?@#56!18?@$#69!82?A#18C#82@A?ACCGGWO__#177!101?O#164gogo__#186!16?A?C?G#169??@?A!7?@???AAA??__??___O#94?G#92_#124_!9?_#15_#57?G#171_???C?G@C#175C!5?_!6?G?@@B@@#171!36?o?A$#88!83?A#75A?C!5?_#193!130?G#158???A#177???@#157G??O#175?G!4?O!5?C#39!8?G#30G!4?G!6?A?@#90@#187??G#201?G#166_OoWGSID#179!49?F?@$#65!84?C?G#81C?G???_#171!140?B??E??GK??@@!6O#31!10?__#101??O??G#35?G#161G#151!6?G#173_?_oGSI$#17!85?G?O#24G#155!145?C!9?CCC??CC#17!15?_#94!4?C#181!7?O#177?A#163A!9?G$#95!85?C#98@?A??C#176!142?O?_???@#5_#170@#187?G?G???G$#44!88?O#18!148?_#189O#156G??C?O#180_$#110!239?_#17_#147???A$#158!239?@#150O-#0!62~}#1_#15N!24~}~}{{wo_#91@???G#33A???O__#48@A?@E?LODiODiPIcPIcQHOG_!9?@IS`KPiCPiCPA#167_?A#175_WoXKH{!8?THq?hS?PcQGA@k?cA_IcQ@OG_O#164J^~V^v^u}W{owo_o_?_#177_#170!9?DBF??EEGK!6?g?GG?GKCKC[?KO_!9?!7_?oooOwG?CCEBDB#154C???@#179C#187O#199!22?@K#205!10?@!9?U#186?R!7?_#18@?_#35!7?@_#24_#29@a_#46!13?u#49}~^vz|m^zmvz\}nv|nz\]VNMB@B@#44CA@#16@#12!23?_#2o@$!62?@#3O#12_#14!24?@??A!4?_#87@???GO#98_#46@??C?GGOO__#34!15?@BE{o??@NW_!11?_GA#166oC@!7?@GO@AG#194_#201!24?@G#171O#183??g?G_G?_#184@?@A@CBCJCAXCZCy?kOGo?W_O?o#176@!7?@?@?@?@??@#180?@#169A#134@AEGaIaGA@#166OGKCCGAE@BD@A@#165_#156_?O?G#201C_#11_??G#185G#198!24?B!10~!9?og#168?K!6?W#209@#165G#17C!9?C#26A#28SG#34@!4~}_?_w}~~~@#48@#51?_GCAP_CPGCa@OGAOCa@G?@CA#14_#15_ow{}!23~J#5G#0}!62~$#5!63?G#14O#16!26?@#78@?AC#82@AEKWOo_#50??@BBEDFMH^QnyTnyTmtZmtZlunuWo!6?BN}tj]rmTzmTzMD@#19A#173G!8?ASgaDc#187!26?@C_??_!4?C?G??O??_#193!14?@#186@@#173@@!7?A??A??A#184_?_?A??O#121@?aO?S_KA#157G?A#173GGCKGECAE@A@@#164___oo!6?n!22~}_#199!10?E!8?K#191@#176?_#152o#188!4?_?O#171o#14@G_!5?be#42@#31W#43iS#50!14?G#48!20?_#46_??G??A#26A#12C#214A#14!24?S#9C$#8!63?C#108!29?@!5?_#30@??C#28??_#46!25?CG!6?CO#20!12?_#159OC#158@#170WeIE@EA[_!5?Fio#179!23?A_!6?@?A!4?G#156??O??_#152!15?A!7?C??CC!5?A?AC#189CO#11_!8?@#154@#168O!4?G#155??_?_??OGK??O#114O??O#183!25?O#164!10?_!6?Mm#183B#193!8?B?G#172C#7G#16O!8?W#57C#33?@]!4?@^~^F@#50!26?O??C??@#11!27?A$!63?A#17!29?A#12C#13GO#74G#18_#43@!4?O_#33!27?@AGp~}o_#26!13?O#23G#42@#161o#152C?@???@?G??G??G#185!25?C#165!8?@!6?G??O??_#188!15?A#9@???C#166B@AA?AA!5?@#211?@#15A#113C???@@#123??O#7C#186OO?O!4?G#188!4?A#171???O?G!5?B#207!35?W#155oCBEO@!4?k#161!6?[#166a#189?A#13A#15F^!5~[W#44A#90@#34!39?_O??C#13OG!27?@$!63?@#76!30?@#75A#81ACGO__#29?G?O!28?@BC#56!18?C#176G#168?A!6?@?a??GO#185!37?A?C#201C#8!24?A#114_??O#161K!9?A#209!4?G#163_#132H!5?@C@#176C??A#161O!6?C???@#200!4?A???A#165!37?K!4?E#187_P#173!10?D#154??O#27!51?_!5?@$#83!95?@#35GO#88O#94A#90AC#30!33?CG#186!22?@!6?C?@??B#175!38?B??A?C?CXCA?_HDRBbQG_EMgGC_!6?OwoowoOWOW_soG#206?G#92O!5?_#124G#110A#160A#158?A???@#189!13?@#128@?A#111_#185!37?B!4?G#155!15?_#23!52?_#17_#18O#20G$#80!96?C#97???C#34@@BFMM[Wwoo__#39!22?A#155!23?C?q??_!8?CHU?bkAHcPkQ@Y@[DOHcIdQI_!9?@??A@CBGJ?AXCzCq?kOK_CW_O?o?O#151O#132B?E#6A#150_#193?CC??C#155_?_??G?_#128???@!4?O#162_#163???@#147@#167A#193O!6?C#92!8?E#134pG#123C#170!38?gG_!6?A?MG?iw??E#30!54?O#24O#42G#38C$#39!101?AC?G#184!56?_?o??O!7?AChUGRkQHePkQdY`YdQHsIdS#171!12?C?G??O#172!21?_#92G#121AW#15_#209O#167??@?@?@?@@#212!7?SKsGU#180!7?@#159@#152O??G!4?A!5?G!4?_#175!37?OoOg!5?P?@v~TF#33!58?G$#193!165?A??A?_#189!66?C#110O#212W_#119G#168!8?A#186A#120!9?@??@#175!9?_???G?O[WI@?A!6?O#184!37?BCHF#171_P$#188!167?@CS#161O?O#200!64?G#120C#134d#113@#159@#124!47?G#212I_#179!43?O$#121!290?C#113@$#117!291?O$#132!291?C-#0!64~o#15Bn!30~}}{wo_#83@AKWG?__#87?O#30G#50??@@BELNYTnyTnyTmtZnM!8?BBADFE@B@#57C#159K@#173WDo#175AFX_SH?E?O!6?@CAGa@K_SAo@Q_PG?Dg?cOHCAg#164En~zn|\mvz^mz~lv~V}v^k}[}[w[w[w[{Wwowo!6?o_oo_oo_!6ow!7?wwwoowoww{w{{}}!7~w!5?Bv!23~_#198!8~B!8?w~B#154C!8?C#164w}#172AO#149_!7?O#29@??M??O?_#50!8?@??G?OO??O?O???C?A?@#9G#15Gw{}}!27~nB#0o!64~$#6!64?A_#14!31?@@ACGO_#91?@!5?O#43?O#46???ACG?__!11?P!7?@??C???C??@#25@#166{Fy#152@O!9?_??AG??_#185!23?@#187@O!4?A!11?@??A?A??C?C?C!4?G!9?O??O??O???G#201C#170BA!8?B@B?B?@#177???A#165@#177!9?A#154O???A#155_#199!24?B!8?W!7?_E#191?o#157_!7?_#155_??_#17A!4?o#104A@#5G#31G#28A?D!4?_#49!10?@FFLMNZ\NJMNFDAB@@#62G#78O#14oC??@!27?OC#2G$#10!64?@O#19!33?@#18A#81@!5?_#97C!4?_#48!5?@AODiODiODiPIcO#34_~C!7?CGGG??C#7_#65G#78A#176A#197_#186?G_!9?G???cKO#201!24?E#171G#183??COA_PGC_PC?QG?g?G_O?_?_?_?_?_?_#172???@_#113O?A#151@#170@A#165!12?CC#172G!5?G#182CC!7?A#200!13?C???C#165O#207!24?K!8?_!7?O@#205?K#162G#166~!5?oF#150G#171F#179@#189@G#14E???A#158oC#63C#57@#43CFA?WO#48!12?A!8?O??G#42_!4?C#105_#17C#19A#13A#16@#6!29?_A$#1!64?G#14CO#108!33?@#17CGO_#107_#90@??C?G??_#33!21?z~^^NFMKG???GG#93_#74O#190O#162_#161???A!9?@O???OA#179!26?o#171!20?@???AA!6?CCC?G!5?w!11G?GG#152@!6?OA#185?CCC#179C!4?A#213!12?@???G#183!25?O#205!8?C#155_a[AH@#163!5?@#152?@???K!6?_!8?_#88O?_#33@???O?__#46!10?O!4?_!4?G?C?A#25???@#10!31?O#3C$!64?C#13G#95!35?AC#79GO#28??@??C!4?_#30!22?_??G#28O!9?A#180O#188???C#170Lw_!5?BG!6?EY#179!46?@!4?A!8?C#152A#123C???_#131!16?@#92S???G#9A#209@#187??G??G???C#123!12?A#92O?@#11O#156!35?A!4?C#169!5?O#161?U!6?O#177??G#13@?_#10??G#22@#166O#64A#93E#91G?O#75_#34BFNN^^^!6~}{wo!5_?__oOOG?C?A@#12!33?G#9@$#82!102?@ADAESwWO__#29!26?_O???OOO!4?C#191_#155!7?CJ?uGpK_!7?@s@[aPI`KAk@McRiOFWBcQhSO#185!23?@!4?AA!7?C#200G???O#149!16?A#111G#212]pE@#200C#171!4?G??CC???A?@!7?C!5?KG#165!33?O???OA#172!5?A#188?G???_#168I#158O#151B#182??O#7C#15@^~~@#157G#167G#67@#150?_#98G?O?_#51!15?A@?CA?C@??A@#24O!4?A$#76!102?ACGO_#94A??G#43!29?_O?O???O#87O#83??G#184!8?ASj?vGoE!5?@??Hu@[aTIpLQkPIcTiOfWJcQh#201!25?@!5?A#165A#9!6?A#132C?G#162E#154!16?S#113a#134`IO#114_#214@#175!4?A?@?@@@#163!12?_#132C?E#172@#177!35?@#175@@`C!9?PiN#193O#186D#176G#185???C#8?O#16???C#159C#168_#151O#7??_#90G???_#30!23?_#27?O!4?A$#39!108?@???G#90!28?_!8?G#89A#168!16?C??GA?_#155!50?@?@???@ABAAA!6?CC@?@A@??BCFCB!8?_??A?@?AAA??@#190!11?G#121@D#118o#211_#185!35?K#184[a[A#164OuN#194!5?_#170mToB#11!7?G#216!5?A#173_#140??O#39??C#30G#7!26?_#33G?C??@$#96!108?A#33@A?C??O?_#94!24?_#98_#97_#48@A?@A#173!18?AlQo@#193_G#175!51?@??@!5?@!7?A@CECCCEDC#190!6?_#132@#121?Ch#120C#156!7?C!6?@!8?@#111?_#212OG#179!40?_#171G@#82!31?_#158!28?_#160O?o$#29!109?A?C??O?_#91!28?_???O#166!18?@Qk#184!57?@?@?@?@?@!8?A@A@A@A?B?B#124!8?O#113!25?G#117A#187!42?_G#166!61?__$#34!110?@BBFNN][woo#84!25?_#82___O#188!19?@D?O#11!64?@#134B?O#190G#128!21?A!25?A#134g#26!105?G#21G?C??@$#154!238?O#111_#117@#92@#176!156?O$#120!239?G#121I#118_$#212!240?s#128C-#0!65~{_#15F^!35~}}w#20A#104C??C!4?G??O#94C!5?OO!7?G???Q??C#57_#20_#190_!7?@#149g#166|ATiCgO!8?ITiG#188O?O#184?@QTiHsA\aXaLpEgUHaTiPI#185A#164\~}n|vZn}vz\m~zmvz\~v\nz|nv|nz\nvz|^m_!5?znvz\~v|V~yn~~!8?^!21~|_!5?Nn!22~|#198@v!6~!7?w~~~#162_#157A!6?C#165_O!5?_#14BO#10I#157F#168oAA!5?O#75@#156C???G?G??O!4?O#37G#103G#207_#199___#180OC!4?G#160C#149@@#15o!29~^B#0_{!65~$#2!65?@O#10_#14!36?@?Cc#149G?C!4?G#90@!6?G!5?O!4?G!5?_g?C?G?CA#162C#21@#152@#195@#178A#159C#168A#173|iTyS__!7?PiPo_#196!25?@#171_#183?@OAGcO@GCaP?CPGCa?GaOCAOGAOCaOGCA_@!7?OGCa?GAg?CO#156??K!6?[#187_!21?A#155B#11C???_#171OO#183!22?A#199g#204G#171!6?C???G@@#178!4?K#180@!6?O#185O?C#147???@#17@??t#158w#173AKo[_W_O_#182C#7A!4?C#33@@@?@!7?@@@#24A#158G?@???A#172A#8G#13B#14!29?_K@#1A$!65?A#14@G_#11!36?@#13AW#5_#82@@??BBAA?C?G!6?!7_ooOWGCEAB!4@#164__oo{#198G#191O#167@#188!4?@#170@AGG_???F_!6?Byg#201!21?K#187A!35?O!6?C!9?@#165???O!6?B!23?C#123W???O#182_#205!24?U#179!7?_??_C#199?_C#190???O#167_!5?G#159G@#184_??G#7??AO#15N#166??LpK_W_O_#165C??G!4?OG!4?_??O#164_#82?CC#200O!9?A#14K#10!30?_#2O@$#4!66?G#13O#78!38?@#80@#6O#162C!8?O??_#98G!7?OOO!4?_!8?A??@#156C#171A#204E#196K#169A#193!5?A??_!5?G!4?AG#152!62?@!4?O#171!14?B#11O!4?B#179_#177!23?O#124@#121C?G#152@#177!33?W#155P?G@#207?OA#196???B#176O!5?O#163_#172C#156@?@#149???C#9G#22_#188!5?A??G#37@#74@???A!6?C#163G!7?O#154O!5?O#157G#191O?S#11_#13!31?O#5G$#8!66?C#93!41?@#166o?_??_#76CC??G??O???_#48@???@#30???A#84_??G?C?AA??@#154G?A#205?o#208_#180O#175!6?@CADgBOG!8?DEgASAH_?CaOAGP?gUGACaOo#185!37?C!4?A#201!14?_#123A!4?_#179!24?G#190_#132G?C#154C#187!33?B#175MmF#164o}M@#158!4?G#166|???o?@#174A#171EA#154!4?G#12C#155!7?@A?E???C_?_O??g#34@?!7@#90AA#81C#28@#169OO#195!4_o#192_#20O#9!32?C$#12!66?A#107!41?A#161G???O#24C???G#91C!4?O!8?O!4?O??@!6?@#187?G#186!10?GO!6?A??CC?A#177!62?W#14@??G#163_#213!15?C!4?[#214!25?A#120_#118BO#172A#184!34?_O#165OA#169!7?C#173A#161_??C!5?G??A!7?@??@??A#190A#83@#44A!5?C#43A!8?A#31G#101?A#74C#5A#208_!5?G#131C#12!32?A$#74!109?A#152G!5?O_?_#34@@@AA???C??AABB@#97G?C?G!4?A#19O#24G?A#184!11?CADGRSh#152?O???@KOC#201!61?A#214A??C#165C#200!15?@#132E??_#128!27?O#117{#111@#200G#170!35?@#188!11?G!7?_#151!5?_#186!7?C#185?A!5?G!4?OO#28A#75?C!7?GG#139G#93@#166KM@FC@$#173!110?o?_#88C#169G#97@?A!7?O#39!11?@#101!4?_#93??O#155_#172O#179OG#155!12?@OQCgEo!9?ODg@sA\aXCLoEgV@_TgPKd#10!39?C#113_#118@#11A#175G#206!15?G#134OJO^!27?B#113?A#186!48?V?_GA#181A#179?G#164@#189!15?@#193G#98???@!4?A#88?C!8?G#198???_#205_#18@#189O#173E?B#196G$#75!110?A#83@@!4?C!5?O#74_#28G!7?G!4?@_??G?C?C#42?C#161!20?C???A?`g#123!62?W#117@#129}#92_#179@#214!15?_#121gcA#129!30?_#175!49?FB???G?G?_Ow!8?@!6?g_?OO?_???_#201_!4?O#167!6?A#176?G#178O$#168!111?O?_?_#29?@@AA?CC???!5G?CC?A???O?O?G#168!24?@c#194???@C#154!63?_#128O#110?O#128!17?@#212Ol#170!80?gK@!6?PnC!9?A?C?GGOO???_#8C!7?G#187_$#193!111?G??O#170O#108?G#87A!9?O!8?C??@???O?GA#132!92?G#213?@#155!100?O!7?A#184!20?O_???_?_#190?GG#83C#102C$#81!111?AA#154G#150???O#184_#33@??A??CCC?!4C???@#134!103?E#193!103?O!6?O#87!20?@!4?A#149!5?G$#163!112?G!7?_#43C??G?G!5?G??CCAa@?O?W?C#152!199?A!4?OcE??O!9?C???o??C!6?G!7?O$#186!112?O???_#79???O#30A??C#46!6A#160!215?_#29!26?@@!5?!7A$#36!112?C#31!8?O#6_#50!4@?@@@?@#168!213?D#179!26?G!6?OOOo$#84!121?G#36?_#75?_#24!247?A!6?C$#172!373?C#76A!7?C$#200!374?C#91A!9?CCC$#171!375?G!8?_$#30!375?@-#0!67~{#15@N!37~#13|#6|#166|QcJShQdIsAkOkGOGG#161O???G!9?A#151?@#187C!7?C#195@#162@#166@zChUGRcY?o!5?DItt#147C#177!25?@#164Y~~l}vz^l}vz\m~z\nuz~\v}Z|nuz~V|m~Znv!5?F~~t^yn|vZ}zm|!8?!24~!6?^!23~#199~#198!7~!5?w!4~#172_#159K!5?C@#175A?OGTKF@??O#4O#166oMqDY_IT#170_c@FdBE?A#187G?C?A??@#183???@#199@@!4?w#191???K#9C#15w!29~F@#6@$#2!67?AO#14!38?A#160A#176A#173lZsjUlYtI{OmOoGO?G#88@#80@#75@@#6A!9?@#183??_?OGCA_A#198O#178C#159W#168C!6?@!7?a#167??G#150@#179!25?E#171_#183??Q@GC_Q@GCaP?CaOHC?aG@cAOHC?gAP?cOG!8?I_DOAGc@CP#187A#152m#14A#134iC@N#11G#156N#163!24?A!4?C#171_#177!31?_??@#199_C#178!4?O#158O???O#176C@#170GC`guIB#164_o{w#8AA#158@#168@!6?C#175W}wW{W?@@@#201?@#205!10?@#198@?A#196!4?o#17o#13@#11!29?_C$#6!67?@G#168!49?@?A???C???G#151A#76??@@@#77@#102@#101@#93@#42@#171_GCCA@#207!6?@#203C#190A#169C#173?zUhvkZc{G_!4?WtI#176A#151A#185!25?o#187D#156!36?O!4?O#155!13?@#110w#212ShUO#12o#165O#152!24?C#92@??C#123_#156!32?K#155LA#164CM@#191!4?E#180B#166nQ_GA#151GA#193??@#155?_O#156OC#185?A_#18@#167K#173oLyd^tiB#186@#155??A?@I?A?@#203!13?@?C#18!4?G#14E!29?W#0_{!67~$#11!68?C_#186!49?@???C_CO??G#7???AAA#162??A#147A#156O??A#204!9?I#191o#176A#152!8?@!5?G!5?`#196!24?G#165!37?G!4?_#163!13?O#124C#121@Qg#118_#213F#185_#154!24?@#11O#132_?W#155@#179!32?R#175_@#187g@#183A#196!4?G#181_#173OlO?@#160A#182!6?_#165GA#163?@#9@#64G#176A#152!7?OA#165!4?_?C?A#207!14?A#195@B~~~B#131A#2!31?OA$#13!68?AO#152!50?@??A?_E??O??G#155?_???oOG???@#205!8?_#196G#180_#188!8?A#170BEW_fo!5?]KNA#172!59?@???@#171G#211!14?@#156!30?_#22G#134W#129~#110A#170A#184!33?Q#171oQ#207O#205!5?@#161???D!4?G?C@#171???GBC#172G#105C#161!8?G#164!6?__oww{{}~}~~~!5}{{#190!5?@#7!31?G$#1!68?_#170!52?@@??A?_ccSsScS{K[CCC#164owww]~nvz|^w#193!12?C?_??C#159!4?_#163!63?A#123_??G#165!46?O#111A#212F#114?@#172G#165!34?C#186!11?G???__KA#189!7?C#159_#171!15?OW?CAA?@#13!49?A$#188!122?A!7?G#185!8?_#31@#35@#175!24?@CQG!7?ooLQ?HcO@sAPI?cAOaPC`CaP!38?C#11G#118EG#12_#201!46?G#124C#117??_#200O#185!34?G#152!11?A??OS@O#184!25?D#179?C???@$#163!123?@???A#150A!11?A#165C#184!24?GAH!8?B?OcHuOJsIPm?tIPmPKb[aTG#177!38?_#92@#129@o#119O#213!47?_#184!51?@?_??A$!124?A!9?G??_#186!28?O???@#169!4?G#110!64?A#132_A#190A#168!99?C???O$#154!124?@#149@!4?AA!4?AA#155!29?@CO!9?_Hu?JcI@k?tIPk@KaWAXGe#114!39?C#134OC#200C#153!99?_#150_$#175!126?_???_?_O??o?GG??A#161!27?A#180!4?O#213!64?O#117G#120@#188!100?A#157O$#104!126?@#193CO!4?G#216!210?G$#36!127?@-#0!69~{_#9_#14!35?N#6B#166B}`KpEWaTIOiD#175_OkOmw||NAPGBGD#201?@#183??OGC`OGA_HCO?G#195k#162@#166@tYdGTiPE]PeGO??@iV#159D#147_#155AS_EpSAhEHcQiOFGu@cRGUHc#201N#164M~mz|]v~Z|mv^|mvz^|mvz\}nvz\}nv|]nz~m}!5?N^}vz\m~v\m~n!8?!24~{!6?!23~#198O|!6~???o!4~^#154O#159C?G?A#186AOC#155_OGCA?G?W#187?A#159_@#152!7?O#175}j^i~B#164w!20~{#195F~~_#17A#15F!26~^F#0_{!69~$#2!69?AO#12O#8!36?G#158O#173@]rMxf\itnTA@#155!5?A?_GeOCA?B#199!15?B#203O#178G#176C!9?G#158G!7?_#150G#171!25?_#183?PCA`G?cAPG_APGC_APGCa@OGCa@OGA`OC?O!8?@GCaP?GaP#187?O#152B!6?G#177!24?@#172G!4?O#199!23?B!7?_??G#178!5?G#158o??G#168G!8?_O#184G#151???G#166_ZcY`ItG#161K#170@S_T#155?[#156@#183!20?@#198o#191??K#18G#13_#14!26?_G@#7@$#6!69?@#15@J!35~o#16_#159_#186!11?w?@#184!7?sGeWCA#207!16?S#204A#190E#159_#173AdYviTmx`a@C?_?ITg#160I#151O#187!25?P!35?@!7?_#155!11?[!6?B#187!24?A#190A!4?_#205!23?k#204A#191!6?O!7?_A#169G#166Bap?OCA#175OI@B!4?_#163??C#157A#168!7?@#165!5?_#171E#207!20?A#203G#196??B#21C#14W#13!27?OA$#5!70?G#14C#17!36?O#176K#170!12?WmRnPF?AO@?@?@#164_ow}}nvz]nv|^uzn~_#208@#191_#167A#168G#181!8?CO#170@BMO!5?sH#171!60?@!5?O#201!12?_#15@#121MOA#129F#14A#172_#123!25?@#120A??O#156@#171!31?C?@#207C#180!5?_#189@#167GC#181A?@#152`??@??_??o#178???A#158C#186!7?A#208!31?O#45@#9!28?_#2OA$#10!70?C#104!37?C#168!13?A#165!11?_#171_?CE@#196!15?O#169G#157!11?o_!5?O#163C#200!63?@???O#179_#22!13?O#212PeL#136w#92O#178O#152!25?O#124O??C#161G#187!31?G?A#196!6?C#202A#157O#173@?ogG?@!5?_!6?cZd]tIv#193_#131!32?O#5!29?G$#13!70?A#193!51?C#185!12?O#187OG??@#180!15?O#188!11?A??@_#152!67?G#111C?A#119_#124!14?A#134_H#117o#110?C#184C#154!25?C#132@??_#165A#177!31?@#155A#164{B#205!5?@#197?C#160O?@#188CA#170oKCE?@O#180!5?O#167O#200!40?_#10!29?C$#156!136?G#152!32?C?C!4?@#154!63?A#11_#117_O#172C#211!14?K#111!4?G#155!26?_#22_#117\#129B#12A#175C#215!31?A#156O#216!9?_#153?C#176C#161??GA#156__?CA#201!5?@#176G$#168!169?G??S#163!67?C#113A#129]_#175@#214!14?_#113!4?_#111!27?G#118A#136{#92G#165!33?C#194!15?@#182??OG#185O???C$#175!170?@!6?H_IPGAhCO_QH?DgO@GQGc@_Qo#165!38?_#124G#136@#14@#178A#114!19?@#113!27?C#212_#119?@#179!33?_#164!19?OGCAB@NL$#161!170?O_#201!68?O#128@#120?C#190G#185!84?G#165!19?_?@?C??_$#184!171?G!6?ATgEhSQhUHcTiOfGuHcRgUH#214!40?O#132?G#201!85?@#171!21?GD?AoO$#193!171?A!4?A-#0!71~{o#15B^!33~{_#166@EzCjOiDiPe#161T#175Ta[bSfFAOHA#164W{~nvz\}nz\nvz}v^yn|}#198{#191@#149@#166BmPk@IDGRkPFuGpaLYk#180A#184ADYaLoEhUHcRkPiDWeHu?ZcTg#171@#164d~z}nz\nv|]nz~l}vz^l}vz^l}vZn}zn|v]z|{!5?]~\vz~l}vz~v!8?F!24~w!6?n!20~@!9?A#187@#178!4?OA#166gSIDA@??__OkODA!4?_{BiSHEGQLO#175AFF\Vw_#165_#183!19?G#198F???K#7A#15AN!22~NF#0o{!71~$#4!71?@?_#14!34?BW#158AO#173CzSnTyTmX#188i#170i\b[jOg\#184he@#187_A#183?OGCa@OCaOGC@G_DOA@#204B#195{#151C#169O#173@mR}DYVkRmwGo?@ADO#170?@o#175dOAHO?gQGCAcOi@W?Hc?G_T#179]#187Y#183?C@OCaOGA`OC?Q@GC_Q@GC_Q@GcO@COAG`CA!8?aGC?Q@GC#187?G_#123W!5?O#171W#179!24?A!5?A#187O#199!20?o!9?W{#191!4?G@#173OgsID!5?_OmYD!6?{TjUXVLQg#170LWwag#155FO#199!20?O#195w~~^#178@#10@#14@o!22?o?@#1A$!71?A#14@C_#12!34?C_#160_#155!15?GO_EOG@#171@#208!20?A#162A#176K#168O???O#194?_#160!4?@A?O_#176?@_#150O#155I?LoEhU@cRgPIDOe@u?ZcRIA#196_#177!37?B#123B???_#179_#156!11?M!7?_#165!24?@#11@???C#163_#207!21?K!9?CA#151!4?_#157OC#153@#167@#152_OC!6?@??C?_?@!5?__??@#168_#164!5?BN^!18~F#203???_#191A#154W#11G#4!24?_?@$#2!72?G#9O#6!35?@#17O#167G#185!20?OA#178!22?O#180_#152!4?__#167!6?C?C??A#161?C#178!63?O???E#187@#165!11?@#200_!5?G#177!25?C#123C???A#171@#183!21?A#198!9~_?!4~B#159G#4@#181A#168?O!5?OG??_G@#162??OA#186!4?_!5?O_#171!4?CO_#204!18?_#201!4?_#159_#17O#9!24?O#2G$#7!72?C#13G#18!35?A#104G#197@#179!20?_#177C#190!22?G#158!13?@A#159G???K#163G#190!63?C???G#177!12?O#213C!5?_#154!26?O#92O??_#184O#179!31?@#205!5?C#176_#64A#161???G?@??G!4?OA??G#157K@#188!6?__C#156!6?G#215!25?O#189C#86C#13!24?G#7C$#11!72?A#159!37?C#201!21?C#196!23?_#181!14?G??_#169??_#172!63?G#92G?C#165@#10!13?A#121AD??C#172@#178!26?_#110_??O#185C#190!38?C#175!4?_W?D!8?E#160?_#176OA#161!8?A#105!34?_#11!25?A$#153!171?C#157?O??P#201!64?_#132A?G#200O#11!13?@#128o???@#190E!26?G#120G#129NM#114G#201G#170!44?_wWKCA???_wX#171?B$#134!242?@?O#132!15?G???A#213!27?A#132C#118o#117@#200@#186!45?A?_#165A?@!6?GC$#114!242?_#129x_#134!15?Da#117@?G#134!28?B#136?o#155!47?CA@A?@!4?_V$#120!242?C#136E#15A#212!16?W#118]?O#184!78?A#188?O#182@#193CA$#124!242?O#214?@#129!17?___$#136!263?^-#0!73~}w_#9_#14_!28?_OGCB#168_!9?O#175V?|A\POi@d#171{#164z~]v|Zn|v]nz|^uz~lv}N#195_~O#149A#166FyDi!5?@?ALQdU[oA#159B#147G#155AW`UG`[AUGe@XcZ?qHcIcbHG#164m~z}v\nzv]nz|]vz]nz~l}vz^l}vz\m~zmvz\~w!5?N^t~mz\}v}~~!8?v!24~_!5?B!19~B#198}!10~}}NB#166_KD`?OOgSISaK@A@!4?_[bKpE#188A!7?@??A#184CA???G#179!5?!7_?O#198A@A?_#147C?A#191A#190@#11@#15@!18~^FA#6A#1@$!73?@#2CO#13O#17!30?_OGC#173OOgtlR}Tit_#170g~A|aKjT#184S#177?A#187C#183?`GAcOAG`OCA_HC?QG@O#198N?_#162G#167G#173DyT_!5?B@QLYg_#157A?K#172C#184XA[`UI`\_VGuAXcZCqHtISa#165S#171P#183?C@GaOCG`OCA`GC`OC?Q@GC_Q@GCaP?CPGCa!9?I?PCa@G@#152??O!6?O#187G#155!24?@!5?_#205!19?_@#199!10?@#204@#196_G#162C#173oyE`__Ogsj\r]D!6?_[rMx@#155???EQ_!5?G???AOOO??O#185!7?_!7?@#154@??_#13G#14!18?_G@#11@$#5!74?A#15BN^!28~^NFB#149G#166GnTIQk@iTI#161D#155!5?aC?iY#179@#207!20?_#203O#191?@#169o#176o#161???A!7?_!7?A_#175dAG`SA_H_PGcA?cHCQ?PGS_#171!38?C!5?O#187_#155!10?_!6?G#156!25?C!5?G#183!19?C#205!13?O#159_G@#188?G?H#161C!6?_?C@??C#157G@#184!4?G#171!4?C?GG??OO??_??a_?_G??OO???O??g_CO#162?A?@#208C#18A#14E#9!19?_#0_w}!73~$#11!74?@#7G#180!32?_O#161O#6B#197?A#188!7?A#204!34?G#180D#168!4?O!8?_#176?@?G#216G#180?O#185!23?@#165!38?@!4?@#156_#154!11?A#92C!4?_#156@#165!25?A!5?O#199!19?O#191!14?C@#157A#193?O#186O???A!5?GA??_#159OE#156!10?G!7?OO?@A?C_G_?O!7?_#155O???G#163C#204G#131??C#16O#13!19?O#2OC$#12!75?C#152!33?__#159C#194!9?G#208!34?E#186!5?G???@??C#153!4?@??__#196!23?A#177!38?A#154G???G#163!12?G#111G!4?O#178_#177!25?G#154_???@#171C#207!19?G#190!14?O#163A#168???A?GC?@#175!4?_?s[B!8?G[~x_?ACC!4?G!5?OO#207!8?C??C#195@@@_oo{W#178O#17_#8!20?G$#152!162?@??@??C#193G#158!4?A?C#163!65?O???C#172!12?C#124O!4?G#185A#187!25?O#190O???A#181!36?O#152???K??A#184@!5?O#152???O!7?C!8?A???C??G#196!14?_!9?A#200G#12!21?C$#188!162?C!4?A#160!7?COO#200!64?C???O#190!12?@#113A#121_#129AAB#11@#201C#11!26?A#117B?C#12O#170!41?EB@!7?wIbKB!6?ovb??@B?A?CD@HBICCGG#205!11?C???A?P???_$#170!163?{bMEKWo!8?@C#10!62?@#117@?O#175A#120!13?@#134F#117{#136|{#12A#123!27?G#128W?@#119G#147!55?G#167OA#164!10?GOOoo!4_?_`@BFFF!4N^^^JHH?C?A#187?G#215C$#175!163?BWowo_#167!6?@`#123!65?A#132C?G#213_#211!13?_#212W#118@#114??C#211!27?@#92_#129oO#124_#160!55?_#154@#182!11?C??G#193A!4?C#183!15?A#199C??_#152C$#155!164?C!77?_#110O#129|_#213!47?C#134C#136N_#200C#177!68?O#165C??GG???@O?C?G??O!8?O?GO?A$#113!243?G#136A#12@#115!50?G#161!70?@??@?A?C#185O#203!17?A#170O#150G#209G$#134!243?A#92?C#121!50?A#179!70?_#187_#168@$#211!243?_#124?A-#0!76~}w_#14!25?gB#152_?P#166_ShQLOdIt?TiPgO_#188_#155??@_IA#156O#164v~v|Znv|]nzv]zN\mFAH#175_#156C#205_G#149G#166o^A[P!9?ATiPdUW#150@#154O#175CRgAGcP?Q?i@GcOADGcOBU#171B#164D~z]nv|zn|v]zn|vz\m~zmvz^|mvz\}vz\m~v~w!5?N~v\mvz]~~l!8?^!24~^!6?N!16~^@#191!11?_GA#173Ogsi\ivKzd]TAB!8?sjUh^pO#155C?AA?CC?ACKKGGG#196?O#182G#207!6?C??A!4?@_#195@@`_ow{#172@#17@#15Y!14~^F@#0w}!76~$#3!76?@C#15@F!24~V#7_A#170kKB#173iUlqnYtI~iTmS_#175@EH][I?t#165_#187G#183?GAcOGA`OCG`CoAPGD#198CBA@#177@#159o#167H#197_?_#186GW!7?A#188O#157!4?@?O#163G#184BgEXcRGuHiDWePIdYcPJS_#177C#187y#183?C`OGACOAG`COAGCaP?CPGC_APGCa@GCaP?G!9?GaPGC`#187??Q#156G#123o!5?_#171_#187!24?_#156G!5?_#199!16?_C#162!12?_#166_gUITaTGrCY`ID!6?C?{JShU_M_#175B`@?@@B?C#201G#199!15?CC??ACC??_AO#155C?A#175@#18?u#14d!14?_WE@$#10!77?AG_#8!25?O#163OA#193_C#188@#152!11?@C#186O#184!5?tG#171@#207!15?_??GA#199C#184O#179A#191c#169C#176E#173?\BE_!7?@LiTmYg_#159G#180_#201!21?@#179w#165!38?@!5?_!10?A#10C#121CO??G#154A#171!26?_!5?O#198!17?w!11~^F@#159A#169@#168@#161!8?_?C???_GA!9?_#171CS??G??P??O??CC!4?_??gHG!4?oOOC??G#182?C#191C?{#35G#10!16?_GA$#14!77?@EW#12!25?C#16@#175OA#168G!12?AG#179!8?M#185!17?_??G#195@#201G#196A#174A#168??_?_#152@!8?_!7?C#179!61?A#154G???C#156O#177!10?o#11A#132W???C#172@#155!26?A#123O???O#207!18?A#178!12?O#157OC#175!11?_?cwYE@#157_O@#152!6?BO#165Q??C??@?A!5?GG#183!9?A?A#165??GGG?CC?A#1!21?_#3C@$#4!78?O#17!26?G#154G#149@#186?O#170!13?BMXu`aT#187!20?O#155_?OGC!9?OD_HcO!9?A?CPcRGeHcTOePIdW_RIcgG#187!39?C#200C???G#185!11?C#22@#128_#117I??O#190K#163!26?@#200_???G#163!32?C#172@#152!11?O?A#158!4?O_#210A#186!6?G#182?G#156_C!6?A!6?OW!8?G!9?A#4!22?O$#169!106?C#171!44?O#165O?_#215O#208O#189@#193!5?A!7?C#4!6?A#160C#201!63?_???@!11?@#213G#134B#118@??_#200O#177!26?O#111@??@#119_#180!32?G#186!12?G#170oWFdXE@!9?G??@AA?E#185G???C!6?O?O!7?GG???G#196G!4?AA$#170!152?_#161!10?C#170yi???@Ew!8?@w#11!61?@#92G#129Lo#123O#212!14?c#136N~#113@#201!27?C#11C#118A?O#152@#184!46?G#168@???o#167G#153C#188!7?C#164??Gwwoo__ppbbB`bfffFFFEFAa@`?`@`cOOO?G$#175!164?DD_YCQG#64!7?@#167_#163!63?O#110O#118O#111A#178A#129!15?o#116?A#211!28?A#129@__#154C#160!53?I#177!14?G!5?O!8?GG???G#198C???A?@A_??G$#184!166?YDqG`#181!7?C#169A#213!63?A#128C#136A#114@#214_#213!46?G#117K#136^#113A#172A#169!53?@#179!15?G!5?SOO???GW?!6OWO!4?C!4?@$#134!244?B#212_#120C#128!48?_#116?C#187!70?O!5?_A!4?_?_`?_@_?`!7?A$#211!244?_#121?G#132!48?O#134?G#205!91?CCA!7?@$#203!393?A!4?@$#204!394?A???O-#0!79~{w_#8_#14!21?~#6C#152N!9?C!4?C??G??OA?C_?G#164BBFFDFA!4B@???__owC#195OE#160GP_#173EwOoN#170@SO!5?Dk!8?A_#165!19?C#164~~\vZ~u\nuz~\v}Zl~z]lvz}nv|^uz|n}vZ~u\z{!5?F~z|^t~|~~~!8?!26~!6?!14~N@#205@#163!10?_#159OC@#168!8?_??@!5?O!6?A!4?@#165??@CO#207!13?A!4?C!4?@#178!12?_#12O#15|!11~^F@#1G#4@$#3!79?@CO#13O#7!22?X#161o#170v!6?_wGwC{CwOgO_o@`aaCC??G!6?G???_OOI?@B#198_G#154_#157_#5G#166FxFgLo#152]!9?O#157!5?AS#172O#185!20?A#183??aGc?HaOHC?aG@cQ?C`QGC@OGA_HCAO@Gc?Ha!9?CA_I?A#152???_#92G#121@#115C?A#11C#155_#152!26?C!4?C#198!14?_w}!10~N@#166OkRcJShQDY?B!7?_{@?{Bk??@EISiO_#156AG_#199!13?A!4?CC???P@#191!10?@#17E#14A!11?_WE@$#1!79?A#14@E#8!23?_#175?G!8?oCw?w?gO_O???@?A!4?G!8?c??A?@#199??O#204_@#159O#153C#158O#168??@A#193?_#175jmOcAO_QO!9?AGd?SG?PGaOACOI_CaOH#196@#187!38?C#177A#165_!4?O#154!10?C#110_#134U#129Z?D#12G#156F#154!26?@!4?O#207!14?OA#191!11?O#196C@#167A#170!9?_WKWA?w?@!7?CO_???@EW_#205!15?A#198AAA??C?G??`@#192!8?O#19@#8!13?_#0_o}!79~$#10!80?A#15@N!21~#149?A#186??]!6?C?A!5?C??@??C!4?_OO!4?Oo???GG#205!6?CG#178A#176_#167G#186??C#184!4?@cJOlQG?_!7?CXdQSj?vGeXaLoJcTiPLa#201w#185!39?@#123A???G#171G#163!10?O#111O#212g#118_#136~w#114O#165W#155!26?G#111A??C#165@#199!15?C#154!12?G#173_OkZsjUlyd^KB#152A@???YA!6?_AG!5?G_#164BN!12~!4|xxxwxoo_#196!11?M#22_#3!14?OC$#9!81?G#188!26?`#173iTj{jE@A@?@@A@BDACGG?GO??O?_?___???O#193G??G?AA#177??@#196O#190C#180A#197???A#155!5?JOlAL_!9?@CQGj?vGePCLoJcPIPKaS#172!41?G???A!11?G#113C#119!4?A#156!27?O#11G#121_?_#172G#157!28?_#181G#161!10?O#186C!6?GB!5?K@AG_?@CO#171@CO#183!16?C!4?H??_#45!11?G#9!14?G#10A$#166!109?TiSBS@A@?@??@A?ADACCG??OO???_#183A?@!5?@#187@!5?G#207A#191?@#161!16?A!7?@G#155!61?O#120C?A#152@_#190!10?A#120A#123!4?@#163!27?A#92@#129M@#12@#175A#205!28?A#189A#175!11?_oe|~FD!8?w_#193O???A#204!22?AA#203AA??O$#168!114?G!5?A!5?GA??OG???_!5?_?O?O#185A#165_?O#168!23?@#166EXATiRg#154A#180_#190!61?C#134@_C#12O#200!11?@#128@#124!4?_#177!27?_#123_#136@}#14A#200_#194!47?_#188C!8?C#187!28?@#195?CEMM]}!8~$#193!114?O#161???A!9?O!9?O!4?_??O?C#173!25?@e|iTk#159@G#213!62?@#10_#129Bw#15_#211!46?C#117O#113?G#167!51?E?A#173{R@??@TiSg$#194!119?A#188??C??G???O???_???O?OO!8?C#186!24?G#176!5?C_#111!63?G#117G#110@#200C#213!46?O#128??O#176!51?G?@#161??O$#155!127?_??@?AC???!5G??C?CA?@GC?@#197!26?O#128!64?A#118C#157!103?_O$#184!131?@??G!8?G#152g??C??C#211!94?O#212O#158!103?O_$#165!132?@?C#156C!6?CC!4?`??GCA#153!197?A$#193!132?g#171@!6?CC???A!4?O#160!200?D$#185!133?A#216!217?G-#0!82~}w_#3_#14!18?AO#7O#175@!6?eWfWfWfWfWeXaJIRYjAI_UcUCCE???@!7?_#159_GB???O#168???k#161V#175QCY_QHC?cSW#161O!6?@G#184dItGeTIoFgUHqKdQhS#164?@n~v|^u~Z|nuz^|vz\m~vz\m~zmvz\}nz]v|Zn|n{!5?@~|nzmz~~~!8?!25~}}!6?!9~^FB#191!12?G@#166WfHu?TiT_DA!8?_kZA?{fG!8?BCY_#165@CO#203!24?@!7?G#8O#14m!9?oGA#2C@$!82?@C#15@FN!17~|_#8_#170Mw!5?WfWfWfWfWfXe\_Tk`CD@FHI@IA?BB@#183?_?O?G#198_GA#152G???AO#176A_#194???_#184??`UHcQlQG#152C_#173VLyTs#157AO#150O#175?CIPG_DGO@gQ@QGcA`^#187AO#183?GA_H?cAOHC_AGCaP?GCaP?CPGCa@OC`GAcOAO!8?AOCPC#152???@!6?E#187!25?@#171@!5?@#207!9?_??@#196!11?OA#162@#173WuH~iTi^YD@!7?OOC@??Wv!7?@?JCW_#156AG_#199!23?Aw#191!6?_#18_#15O!9~NF@#7A$!83?A#14AGo#13!18?G#149G#152OE_???s#155@!12?S_?CO_SG_?G?G?C???@#207!4?OC@#154O#216O!4?G#188!15?@#166BGqDiJw#180C_#171!19?C#177!39?@_#111C??O#156C!9?_#10O#121O_#136~~#92K#163w#155!27?O!4?O#198!10?_w}!11~F#159OA#161!8?_G#152C@??O???B!7?W!5?A??_?A?O#164B^!23~{#195B!6~F#123C#12@#3!10?_#0_w}!82~$#13!83?@#1O#10O#16!19?C#150@#209_#161@!4?A#184!18?W_O?O??@@#156?C?A?@#204!4?_GA#163A???G#158C#193!15?A#168C_#159!4?@G#177!20?W#187!39?A#165O!4?G!9?G#11G#134l#117W#110??@#200!28?@???@#156A#199!10?OC#163!12?_#154C#168_#175!9?_gs]NB@A@!9?s__sc__!4?AG_#198!25?C#208!6?O#131G#10!11?O#1O#13@$#5!84?G#17!20?A#154A#186??C???@#152!22?@?@#164__oww{[}m~vNB#191OC@#188_!8?G#186!11?G#176!5?C_#179!20?_#172!40?C???@#154_#155!9?A!6?@#152!27?G#92C??_#161_#183!10?G#169!14?_#181C#184!9?O!7?O#188C#168A!6?B!5?@?C??CO#171@C#190!33?A#5!12?G$#11!84?C#18!20?@#162C#168??J_#193??G#165!23?_O#179O#185G#201??A#203!7?O#195C@#174C#166GA??@^sHQ#172!19?A#123!61?@_#129@[#200A#161O#185!9?O#14@#212A#118F#111??A#154!28?A#110G??O#185C#180!25?G#170!11?OI`oK}|M!8?_J^TJZSWo??@C#192!35?@#12!12?C$#188!109?O#166VgT#171!25?_??G?C?A?@#181!6?_#173O!5?Ju@#155!81?G#120A??_#171A#201!9?C#114A#113!4?O#165!28?_#114O??G#201G#186!37?A!7?G@!6?C!6?A?O`$#173!110?GVi#187!27?O#157!14?C??A#180_#190!85?A#10O#117M#113@#213C!11?_#120!4?_#172!28?C#120A#129gC#11C#193!42?_??_#181??_#176C?@#155!5?I??G#161C!4?G_$#170!156?o[_!6?lY!7?B_!8?EO#128!60?@#132_#118_#214G!11?C#132!34?@#136Vw#119A#4!49?OK#167A#188!10?G$#167!156?@#155_!8?`CHcQhQH_!9?@Ip?ePIoFgU@cK`QHSI_#211!42?G#134O#121A#214!47?_#115?A#153!50?_Q$#186!156?C#150@#147C#117!139?@#160!50?G`$#193!156?G#160?@-#0!86~{w_#6_#14!15?@K#149A_#184@#161C_?@#175@]`]`]`]`]`]`s_U?dQ@#164_ow}~^nz|^u~ZLA#155_G!4?OCO#184O_!9?@ACDA@C?G!8?DaKRgFojSIPmOJchUP#185A#164^z^uz~lvz\~mz\~u^z|nuz^|u^zl~v\m~zl~v\v~M_!5?\}~t~~~}o!7?!26~zo!5?Fn~~^NFB@#205!13?_@#167_@#173}Tivi\RE!9?_YD@!4?F#155o@#175a~^tj\vzkWo_??A#164@F^~~~N^!13~^NB#203!6?_#8_#12A!9?A#8@$#2!86?A?O#13O#17!16?@#7C#170B]o???}`]`]`]`]`]`]@^_#184lQHCA@#183???_OCA_H?CA@#156O???_#165G_#163???A#176?G#173BkZsGOwO_o?o???_Pmti\iO#154AO#155KbgFoISI`k?JcPU@k#196@#171_#183C_HC?QGCa?PCa?H_CAOHC_AH_CQ?GaP?CQ?GaG?O!7?@?I#177!4?M#200_#128{???@#154N#187!26?C#171K!5?WO#187??_#198_ow{!13~^#154O#158G#166}@iTGTaK@#186@???O!4?G!7?o@#165[#152!9?@?G?@C#156AG_#199???__#207!13?_?C#123!7?O#14W!6?_GC@$#8!86?@#14@CG#22!16?A#8G#152C_G#166AL#188E#155!13?I?HQGcI@#185A@#207!9?_?c@#152O!4?@???C!7?A!5?G???C#157!6?AS#175A@OCOGD?`SAPcOI?gA_#187!40?`!6?A!6?@#213?@#132B???{#190o#156!27?@!5?_#183!4?O#207G#199CA#162!14?_#159E#168!7?_!4?_!5?O@!6?G#161A#182_#168!10?@@?OO#155G_#183!4?O#195!15?_o{!5~^#178C#15_!6~^FB#1G$#5!87?C#15BF!15~}o#11O#162O#173?@@Qo#187!19?_OGC@#203!9?_OC@#175A?O?GaIdO!8?@?@A?CA@#159!8?@G#180_#170O#201!16?[#156!41?G!4?A!8?@#121??a#129~_#116A#179!28?A#154_???A#178!23?G#176O#170!8?_WrrLEp{ktB!8?G?\?_ISaGCReKO_??O#199!21?O?@#190!6?G#17@#7!7?_#0_o{!86~$#12!87?A#10G#13!18?_#163G#186?A??G#156!19?O#171GCA#199!10?O#198GAA#160_C!7?A_#161???@!6?G???O!8?@G#155!59?C!4?C#134!11?O#136?^#200!30?G???K#191!23?A#189@#188!8?GA???G?A#184@#193?C!8?C#201A#188!10?A#166AEG#165@?O#198!21?GA#192!6?A#22C#13!7?O#2O?A$#154!107?@#168???[_#165!21?C#205!14?G#172_?@!7?_#188!5?A?C??G!5?A#176!6?C#167_#172!60?A#123O???_!9?]#212?L#10!32?@#117A?G#119_#196!23?C#152!9?OC??O?A#166!4?cyA???~#186!14?CHc#173_#182C#196!30?@#10!9?G#5C$#196!150?C#147G!7?@O#186!5?_!8?O#177!70?O#154_???O#131!44?C#121C?C#123O#175!34?_KKA@??QI#157???G??M#161!16?OAG#193_$#159!151?O!8?K#168!6?c?_G???O??@#190!68?@#213G??A#171@_#190!43?O#129@?O#163@#161!38?_G@#160!5?O@@$#161!151?C??C#170EXdIG!6?@@A?C???GGN#22!70?C#92_?G#163G#213!44?A#120_#136~#113@#194!40?C#176!6?CO#217O$#215!151?@#157G!7?@#193!8?C!7?O#111!69?A#117@P_#128!46?O#115?_#153!47?_#158Gmo$#167!152?O#166C@!6?F{RcISG?GO?o?___?mPITaTg#113!62?@#212A_#132O!46?G#116?A#216!48?E?@$#180!152?A#158A!7?O#155!7?A?@A@CA#121!72?G#129M#200@#167!97?_$#186!152?_#153@#171o#128!93?O#211?C$#188!153?G#194A#134!93?C-#0!89~}{o_#6_#14!13?P#8@#147AO#193@??A#155C!11?dG@IA#187C#183??OCa@G_CPGC#171_G!4?_C??A_#170???C!6?ADCACAIIGCK#159!8?@G_#155@U?XDYDOGbGY`[DPO#164]~nz]v|Zn|v]zlv^|u^z|nuz^|v]zn|uZn}zn|]v~w!5?N~m!5~!7?!27~^!6?BB@#178!18?o#169o#166NOdIt?L???_O!7?t}??n??B#185?wN#175SKNju\jul~BBEC_???O#187?O#207?CO!8?C??@#123!9?_#13O!4?O?A#11@$#1!89?@?G#15BN^!12~m#17U#64G#175@!5?B{B{B{B{BsIOAS?@#205!11?_???@#161G?O!9?@!6?C_?G?@??OA#167!9?C_#184DChUaWdYdQKrCY`Yc#165G#171_#183?OC`GAcOAG`CQG_AH_CAOHC_AG`COAHcO@COA`G!9?P#190!5?_!5?~#187!27?_#155w!4?A#198_W{}!17~#191B#180N#168o_!4?_!4?kA!5?gA!6?W#165?B#179o#184g?_#171!7?_G_!5?AG_#199?A??A?C!6?A#190!10?O#14_???o?C@$#3!90?A#14@C?_#18!13?g#157_#170A]_??J{B{B{B{B{JtItiC#164_w~~nz\}v^zmVBP?A!4?w~~{#175@KADG!5?@@?AC?!4CG#157!9?A#154AO#175I?hCa?_IdOC`CA_I#179B#187@#155!40?A_???A#156O#120!8?_#121c??~#152!29?C#92@??O#165@#205G?A@#196!17?K#173??NYtI~QB!4?C@!5?G@??O??C#182?C#155??o#165!8?CC?_!5?_#204??@?A#183???C#191!16?B#9@#15~~~NNB#1G?@$#11!90?@#13A?O#160!15?C#152C_G?@_#156!14?_#171WA#198!11?OKB#152_???C!8?A_!4?A??@@G!4?O?G#158!8?O#170Ao#196!15?C#152!41?@#12A???_#128!9?^#134R#117_#136~#163!30?A#111A??G#175C#204O_#170!28?osHE@_wQB?N!9?w??BBOSHaSHQ???@I[o@C_#195!4?A[{wwwoooww{}!9~#192G#17C#6!4?_#0_o{}!89~$#7!91?C#2O#163!16?@#159_#184?O??O#165!14?O#179C@#199!11?gAC#170O#184C@??@!4?AtGo#159B_#168C??C?G?_o?o?__??G#161!7?@#163G#201!16?_#177!41?C#172O???C#212!10?G#118]#172!31?@#124G??C#178w#207C#199C#161!28?G@?G!7?A!8?@#156!12?W?O#152@?GAG#198!5?H_?C??GGG?C#196!11?C#18A#3!5?O?A$#10!92?G#216!16?O#161G#166?@E_#184!15?@#155!15?_GA?_!5?ApGq!8?A??A#184G??A#180!11?C#124!60?@??A#200G!8?^#129??@#110!32?C#129W@#12@#186!31?C??O?G!5?O???O???_#164!14?o!6?@FN~w_@@BBBFFFBB@#214!11?G#10!5?G#7C$#173!112?APO#156!31?O!8?@G#154!4?G#166BxmOGO?O!7?`AtIPiCXbW#9!61?C#113G?C#165@_#211!43?O#136F}#114A#152!32?AO?AOC???o!9?C#155!14?GO???AG#203!4?C$#188!112?C_#186C#185!32?C#182@???A#163!8?O#158K#197A#173PkoGO???o?_@@?pAtmTze[_#190!61?G#121A_#11@#213O#214!44?_#115_#120?_#175!32?GE@???k{}#193?@!8?A#161!16?A??O$#168!113?GG#167!33?_OC@#169!9?C#160O#186??B?_?GO???P??AC#111!70?O#118Eo#188!82?_??@A!4?C???_#181?_#186!19?@?K_$#150!148?O#163A#158A#165G!4?C#194!12?_#193?@!5?O#126!71?C#117G#132G#184!87?@#155?@#157!4?CA?@s#168!19?FO#182@CO$#166!149?_#173G#177O!4?O#188!16?@#211!75?_#129@#158!95?A??]J#193!20?_$#174!149?C#180@#168A#212!97?@#134O#176!95?@K$#181!149?G#159!196?g#216@$#160!346?O-#0!93~}{w_#11O#8!10?i#158D?O#170B]o?E^_^_^_^_^gRI#156O@#183?GaOHCA?hGA#182_!5?_?@#183??OH_#177?o#184DyCq#152O!6?C??G@@??AO#186O#197_#159!7?@K_#155DAG`UgRCZ?UHcAx#179@_#183?COA`GC?QGCa@OCa?S@GaS?HC_QG@COAHcO@COI#171?A!5?C#190!6?B!5?F#187_#207!21?_??G#205GCG!4?_#204cG#163!19?K#158B#166z@kPe??Ok@!8?zC???Eo#160L#161A#155g??CaD!6?@!4?@AG_??C#182O#205??@G#123!18?_#9A!4?@$#1!93?@??O#18!11?T#150_#166W???BG#175@_^_^_^_^_VcC#165C#164}~v\nuz|~UF`WE@!4?_{~~nu^~#179G#175?DG!7?A???E?AAGG#194A#158!9?AO#175@AWdQG@Cg?cH_QHCW#164L^zn|]vz~lvz\}nz\~j}v\j~uz^lv}zn|uZn}znt~{!5?B!6~!7?^!21~^^NFFB#191CO???O#172!21?_#169C#168CC!5?G?O!7?_!5?W#167C#216A#175?Q??BWOy^tj}N!7?CO?@W#207???A#195F!18~#178O#12G_??A$#2!94?A??_#176!11?Y#173_!4?C#155!11?Gp@#199!10?oK#198A#155oGA??O!9?y?rK!8?A???C#193??O#147!11?AO#170w#156!14?A#187A_#165!39?@O#92C??_#155O#128!7?{???O#154W#198!23?__oowo_!4?Zv!19~#178B#176O#173?yRmXF__QMB!7?Cz???@#156!4?m?_!7?_C!4?A!5?@G#183???_#191!18?E#14_?GE#4C$#9!94?@#15@BFN!8~i#167??C_#152C_??G#171!12?_#207!11?OD#156G!5?G#185!7?C#170???@_!4?@@BDF@EGKECK#163!10?@G#201!15?C#177O#156!41?_!4?G#121!8?S#117u#136~#116_#163_#199!24?O#156???A#119C??_#163G#190!21?O#181_#188!5?O?C!9?A!5?_??@#171?OB#165W!7?OA!5?C!5?_#199???O#192!18?G#18C#15^F@@$#12!95?A??_#160!11?@#161@???@#182!13?G#205!13?@#165C!4?C!7?B#159!4?@w#166]`kOgG!7?_?a@YdGTi@l[#176_#184A?dQK`UgRcZ_UHsA_#163!42?G!4?_#134!8?j#118H#132?N#165!29?@#123GO#132_A#190E#210!22?G#186!5?gO??_C!6?[#157??oA???o#196??@#164{!9?o}~~}w_!5?B^~{#203!19?@#22O#2?_??A$#4!95?C#14CGO!8?T#217??A#147AO#193@G#168O_#201!12?A#171!14?A!4?OA#154!11?C#157C#173@MAkOoO??O_??@@@EDYviT}Q_#152?C#119!58?@???G#11!43?B#192_??@#170!29?C!4?kQP?CC!5?{!4?D!5?D_IS@!7?@BK_E_#131!24?@#10?O#0_w{}!93~$#7!96?G#159!14?K_#188?C_O#170!27?_C@?GA#169!12?A#167@#176_#197O#168O???GOo_O_`?_CW_#154!67?C???A#113!44?G#22O#110G#152!30?I???O!5?x!5?A???o#184!4?Di#177!6?G#171@??@CW_!4?C_#1!25?O??@$#163!112?G#194??A#175!28?O#161O???@#209!12?G#181A#186??@?C#188??_???@#123!73?A_#118@O#200C!7?{#117!36?E#120G#116@#161!30?@!4?@#176!8?B???J#185!21?O???A#7!27?G$#168!145?_?B#161!19?A??_G???OO??G#121!70?@OC#114O#129!44?@#136F#121C#175!31?B??_?lm~zA#158???K#147C#186!25?@#152@g$#184!145?G#166OC@#184!24?G#110!76?G#212MG#152@#131!46?O#193!32?@?G!6?@!4?@???K!20?A#161GO$#185!145?@#173gGA#128!101?A#126_#120A!9?B#155!73?O#150!9?o#168!26?C$#188!146?COC#214!101?O#111?@#184!83?A#151!9?G#184!26?O$#152!146?A_#117!104?_#159!93?@#188!26?A-#0!98~}wo_#13O#10!5?_#150E!4?G#155!12?AB#164}~~lvz~U|XKB!6?_}~nz]v|v~[#175@?i@C!7?l}]KKKGW?_#197A#159!9?@K#155@Ch?\_UHEoJSBgEO_#179_#183?AOC`QGC@OGA`OCG`COGA`OC?QHC_PC?QHC_AH#187G@#171O!4?A_???~#132?_#117?}#136~#113o#154@#187S#183!11?_!4?C#204!11?A_???E_#154!19?F#158y#173_XviT}TiB!8?oHS!5?G#171??iAE!6?G@!8?AG???@g#183??O#195!14~^N#0_ow}!98~$#4!98?@???_#17!5?O#209@???@#152C`GB_#165!9?K#171@!8?_??@!5?W!9?b#155}T?y`#154A#158B#166]qL!9?@?B?\QdY`IsBTW#163@G#175I?U_AH_OHC?gCPiO#187G#155!40?C!4?G#120!10?G#163w#205!13?_!4?C#178!11?B??C#172!21?w#176D#166\eGTi@iT!9?Huj!4?C#158@#156??S!7?_C#187?C#185!7?@CO??C#198???A#203!14?_O@#1GC#4@$#14!99?@A?G_???D#18B#151O#170W???B]o?KRkRkRmPm@#182O#183???QGC?hAA#156OC!7?@#183?OC`GAG#184???iTCYo!5?_??_?o?_#168??CLA#158!8?A#147A#184E@UhA\_UhEojSRgC#164@F~|nz]lvz}nv|]nzv]znv|]nz~luz^mz~luz^|uv}_!4?@^~~~!8?j!11~^^NNFBB@@#191!9?K??`#168!23?A#170!7?_{TMI?D[!6?I[??G!5?wL?A#155!12?AG?Io#199???K#176!15?_#190G#8@#7A$#1!99?C#9C!7?K#154_#175_!7?BkRkRkPmP{#185_#207!9?CA#175oWA???GA#161!15?G#159[#168@??A#152@!4?@??ACO#176!10?C#157O#154O#170o#165!13?@G!41?G!4?C#172!11?E#198!14?_ooww{}}!7~|O???@^!19~#186!10?C!7?oA!6?G#176E#177??@#164|w!6?o}z!7~{o!4?V~~_#191!16?A#9A#13@$#7!99?A#15@FF^~~~y#172?G#184C_!6?O#155!20?_?C@!4?@#169!15?@#180_#176_#173Lq`!7?@?@??_lYd]tJ{i_#167_#180_#171!15?AO#172!40?A!4?O#207!26?O#199?G??A?@#205!9?_??w#152!30?OA#175_pt~yb!6?D!4?i???A?q~|F@!10?@Co`#156A#203???@#196!16?C#61C$#2!100?GO#188!8?A!6?[#199!21?@#165GA!4?O#209!16?C#186!4?[!9?A?o#177!27?C#190!41?@???@#200_!5?~#111!37?AC#190O#188!31?G#193@!6?@#168C!6?Bo#179!4?@#165W!4?OA#170!11?BNS#165G#218!20?O$#11!101?G#168!8?@G!4?A#170!24?_KB?oC!22?]Q@@BAAUCw#161G#119!71?G??C#214!44?CO#192G#155!33?I#161!5?E!4?w?@#188O#184!6?@A#177!16?_$#161!111?O!4?C_#152!24?OC?GA#155!26?o?o?_#120!73?@??_#128!44?@#196_A#194!39?G#157???@#150@#155o_???T???CD???G$#173!111?F_#169AO#161!27?_?_#182?_C#188!27?@#123!75?O??A#163!44?O#11@#159!44?A#163E#152?A_?v#185!4?_$#186!112?O???@!26?G?C@#111!104?A#128O@!8?^???F#213!32?G#14A#160!44?{$#166!112?K_#157_#168!28?oQ#154!106?_#121AO!9?S#120!36?G$#160!112?@#158O#166!30?C@#211!105?C#134La!9?i$#181!112?A#159K#173!30?GA#113!106?_#212G!9?@@$#193!144?@#118!108?C#92O$#114!254?G-#0!103~}wo_#12OC#169C!5?C#209G#184?Gg#155!7?_!8?_?MO!4?o@#183??_GAP_Ca@#179Ow#175oI@GCO!7?BCIOAhS?CO#166@aKpEWaTiXF#159BG#184@AKpEWjCqKRcY`Mo#164@J^|nzV}lv}Znv|]nzv]zn|v]z|mv~Zmv|Znu~Y~zo!5?N^~!8?}!5~^FB@@#204!20?@?A@#164!18?_w#150C#64_#168O!6?@!8?{!8?B_#175B!4?EfF@#199!4?C[#156!8?AO?D#198???E!9?O#0_ow{}!102~$#6!103?@#14@AC#3_#13O#149o#175AS_???@C??RkRkPmP^!9?w?@F??_E#156G#185!10?_#177E#155MoEoJc!8?@cHSAgRg#152@#173A\rMxf\iTE#160o?O#170Eo#165!13?@#171A#187C_#183AOCg@QG@cOGA`OCG`COAG`CAPG?cPGAcOH?d?C#171G!5?o_?^!7?@#199!5?_!4?@#205!21?@#199!19?OC#152_#153O#166jSjS`UG#170opF?G??_{!8?A_?[!5?OW#195!5?_#183A#165!8?@G?o#199???w#195!8~^NFB@#193@#219@$#1!104?C#15@BN#16_#151@#170Lj[o??AyoFkRkRmPm#156?B#164~~]v|m~F@!8?{~^v|m^z\}B#165?@#184DwFoJ!8?APehSBkOA#168K#181!8?_G#153K#150A_#175DQG`COHCOC?cQ?EW_#155!39?A#200G???O!4?i#117??~#129G#92y#152w#198!7?ow{}}!19~}~{}!18~N@#163O#157G#173CjSj]hV#161E#175Mw~vF@?B#166?@S~#159@#161}???GA#179?G??@???o#198!4?W#205_#171!8?C#155Co#179A#204???@#203!8?_#217_#191GC#201A#220A$#8!104?A#2GO#17?@#162I#152o?A???C@?O#179!8?{#183??`GAP#165?O#171A!7?oB!9?K@#161!6?W!5?G#188_#186!9?o#167!10?_#161@O#155G`EWbCqHbgZ@KpG_#156O#163!39?@#152_???C#177???_#131O#136???v#113D#163E#183!7?GCA#205!44?A#172J#158F#186!6?_!9?B!8?CW#187??OC!5?_@??A???C??C?A!4?I#218!13?OG#221C$#10!105?C#11G?A#188???@#161G_???A#187!17?G#185C@#161?O??A#182?C#186!18?_!4?A#170F[w!6?Bk#176!11?O#157_#152G#185!15?C#179!40?C#172O???G#178!10?@#184!64?G#171!4?_w!9?W!4?A??K!4?Q#175!14?BJ#185G$#22!108?G#166!4?@SO_#193?D#170!19?O_G@_WH#201A#162!18?@#153W#166]peG_#202!22?C#12!58?A#134BU!8?a#155!73?GAO!9?K!7?GG_E#177!15?_#170C$#168!113?EG_#184!22?_M#168__@#172!21?A#157A#168@??O#110!82?@#132C!8?w#182!74?O?A!7?OA!4?_#177??A!4?@$#158!114?@#150@O#152!24?AOC#209!20?C#158@#176_#173MXcO#213!81?C#14_?C#156@!81?C!9?C!4?D#184???@$#167!114?A#147A#166!25?WA#159!22?C#152???@#92!83?O?G#165A!82?L!7?N@_!7?o$#176!115?G#186!25?C#173C@#160!21?_#120!87?G?O#173!86?}j#160?{#163@#185_!5?O!6?G$#188!142?G#121!111?G!8?X#216!81?A#164??_!5?nzo!4?K}~~@@~~z~~z~|w???t~~$#212!254?_!8?C#152!85?@O?_$#126!254?@#128_!6?F#201!86?O#188?C$#123!255?@_!4?D#193!89?@$#11!255?A-#0!108~}{wo_#168??@COO#155!8?a!6?_U_G@!4?G#183!4?gAP?S#155?_W??W?lQg#152G!8?@A???GG#186?G#160!6?_?CB???G#175@GdOAS?`WDOGd?aSGAOO#187?O#183I?CPG?QC_GAP_CG`COAH_CQ?Gc@OGAOCa@G#177C!7?@#132?D#117?}#129o#111E#152^#199!4?_A!43?_?C@#152??A!7?g#170f\E!4?i}#173TjS#159?|#152}!6?O#201!5?O#187C#183??O???G#171!8?@o?i#183??S#195~~~^NF@#166A#210@$#3!108?@#2A#152@CG?@CO@?_#156!8?O!6?@!8?A!9?OA#179k@#184dY?lQF#166wo?VkPIUkGw?o?O_OgCXaTiGFPwEj[#180B#184ADQItGb[DqGfWaTIcOg#179G#187!37?_!5?@#118!5?@#136N#161?_#207!4?W@#164!44?_w{}~#161@!7?C#175W_PB???O#168?i#166Sj~#160A#161@!6?_#207!9?_#198C?B_#165!9?A^#199???j#152???_#1_#0_o{}!108~$#5!109?@#222CGO_#186?AGa#161G#185!9?_!5?G#184gSQ#161Oa?@#165?O@!10?c#185@E#161!5?O#173C???RmthAC?o?o_O_Sze\iTF???wS_#181_#147_#155AGdGb[AaGfOA\G`Rk?_#201!37?@!4?A#171o?]#198!12?{!43~^NB#147???_#157Ga_#186!4?@#155?AgC??o@#164!8?uw!4?F~~o??o}~N???F!8~}??T~~#184!5?O#219OG$#188!110?A#170BBS}g_?DF~?~?~@}D!9?_E?OUB#182_#196!12?A#175oAdQ?D#188_#186B#157@A!6?_#155@A??A@#176!7?O???@?@#154?O#170o#185!15?@#164@FNNt~zmv~lz^v|m^zv]zn|u^zl~vZ}nv|nz\}vW!5?M~_!7?!4~F#183!45?O?A@#150?O#160CO#166MwDQk#188A#165???G@?G!8?o!5?A!4?og#199!4?B??O#155!9?@#185_#223!9?GC$#175!112?CJ?O??AW?~?~?}@W!6?O?JD??_g{F#201!13?G#176!7?K!6?OO#193C!4?G#159!8?G!5?C#161C#171!17?A?O_#156!35?A#123C!7?B#163!61?C#4B#153G#173@FylR#193O#156???OA?@#184C@#171!7?HC!4?W??K??J@#195??o~{#156!10?C#203!11?A@$#173!116?@C_#179!9?N#164n~v~]B!9?_~~~V|m~j~F#158!10?A@#168G!7?G!5?B!7?_#167C??A#163?G#165!17?C?_#190!37?G???O???_#172!61?G#158O#181@#167O#171!8?_C`#185E!8?N??O??@#204!10?G#179!12?G$#166!116?AG#187!11?O#183?G?@#171C!9?[!9?G@O#153!9?W#167_#170!5?@ACC???F#153!7?O#157I!4?O#163!58?_???C#210!66?_#216C#164!10?w]#156!11?A!4?_??@$#177!133?_#168!4?_SA#160!25?c#184!8?@AD?E#158!8?_#172!64?O???G#196!91?@#155M??C!4?GE$#193!138?G#152@G#175!35?@AD#11!74?@#92O?G!9?x#175!83?@!7?E@$#173!139?G@#161!35?G#213!76?A#134BD!7?x#179!86?_!6?A$#186!140?C#120!113?G!7?_#170!88?{?G!4?@$#128!254?C?_!5?Y#188!88?@G$#110!254?_#121I!7?C#193!87?AP$#212!255?o!7?A#184!88?_$#15!256?C#200_???[#186!90?E$#113!256?O#185@$#119!256?@$#214!256?A-#0!114~{wwo_#188P#161C_#155!5?So???_c@G@!4?cA!9?OC@?_K_jCd#168_A!4?E#167!6?CO_!9?A!6?A#159M#184ADIOlA\_ZChUaXDiSRcJgO_#183?@?@CA?S@GAOH?cAPG_AG`COAG`CQ?cPG#187_#165C!4?K??_#113@???@#172@#156_#183???A?_#207!35?_#164__owg{~~^N^#155G!9?_o@???r!8?G???@W_[#177!4?_#199???B!4?A#156!9?@_#171OG#187C#195@@#152@#219@$#222!114?@ACGO_#173_#175DiTiTi@!5?OGa???wqZ@#164g~~mz^l}NB#184OC??pMOjQB#166WcZ??xItAlSp@ITiShQdYCbwKv@UgQlS#180@#170@o#165!18?@ACG#179?O#185_#187?_??_#201!25?A!7?O#92A#212UGg#120A#200G#207!4?@G#205O!36?O#183O??CA#171??_O?@#158_??G?_#152??F!8?@!5?B!6?A#182@#203!8?_!4?G#177!9?A#225?_O#199A#163C#1CA$#219!114?AC#152?@K??O#165!6?E???OB!8?C!8?_GA_@#201C#152!5?K#173cZc???tI|Qg?ADITjUlYDB??oG}hVlQ_#152?C#175GtAO`AC_Y?GCaO@gCOC?gG_?_#155!31?@!4?o#154??B!5?C#198!5?FN!35~^NNFB@#179!4?_#161?O!8?O!8?W!5?S#165B!4?_?A!4?F#198???K#195!4~o#0!12?_oww{}!113~$#170!115?@BE??BITiTiTi!5?GEOC?wFL#179?O#177@#183??PC_Q@#170?_?A#185aA#161!5?O#186@#157??CO!5?@IG!9?CC!8?_#161G#155A?lA[_ZC`U`XCiSBgJoU?O?O#185!32?G!4?A#163??C!5?A#199!45?GCA@#150!5?_#160O??CO#193???G!8?C!5?G#201C???A#170F[_#204!8?O#205!4?C_#207!12?@#203A#161A$#168!118?@EO#185!7?@#171[?_C!9?_E!7?OC#175gW?WAPCOG#160!4?B!7?C!9?O??@!7?O#163O#171!20?@ACG??_#156!29?o#213o??A!9?_#187!47?O!5?_#156A#159K#166GK_B\qL#175?NN!4?CL!7?o#164@~[o???B~~o?N~~!5?@^!8~{^NF#224G$#186!118?A?G#201!7?G#164B~^B#184?oDA#168K@#185???G#187O#179!9?@?[@#181!9?O!6?A?_!7?_??@#197!7?G#209?_#170!20?C?O_#11!33?K??G!4?_#118??C#132C#185!55?C#180B#173O???ALQ#170?O!5?Gq#184A#166iTg^_#186_#171?M?B!4?K??K?o$#194!119?G#177!9?_#156??G#152???G_C#158!27?GM#153!7?O_!8?GA#164!31?@BEN]Z\~j]v|nu~Z|mv^|v]zn|v]zl~Zmv^!6?}~!7?L~~~{o#167!50?EAO#157G#156!6?K??O#188??_#173DiV_#158K#156??o!6?o??@$#186!136?_?A#176!27?_@!16?G!9?@#124!59?@!7?G#129??B#168!58?_O#153A_#171!6?o@?n#168???O#159???B#207!4?_#179G!6?AO$#188!136?O#161Q#216!29?_#159!16?_#158O#214!68?A??o!4?O#176!61?@@#216@#181C#165!6?A#164}~#181!8?O#185!5?C!7?G$#193!137?@#121!117?ZM!6?GbOo#186!58?_!4?__#188!21?@$#134!255?Co!6?`S?G$#132!255?_#128@#123@!9?O$#119!257?C#171@@!8?Q$#152!261?G#111C-#0!120~}{wo_#1_#225_#155ACo_CWdA?_?OaC!7?_WEoHs?_Y@sHC#173_Qmti???DJe\iTySgOw!4?_[rmTi|jUlqO#159K?_#184ZcQl?vGtAkRgDYdGv?mPKqDY_kOGO__O?_?_#201_#165!8?!6_?_?O!4?C?__!6?@#156?_#205!5?@#183G?GO??O!4?_!5?_!5?O??G???A???@#156_??Gc#185?C@#180_GB#170oK!7?F!6?^!5?_?B_!5?DG#179G#187?O?C?a!7?A#198_#171_#194_#1_#0_ow{}!120~$#218!120?@#221ACGO#173O#156???BG@#193??_OA#179???_C!5?_#156?C#185!4?E#186!6?O@#167???B#160D!9?@@?A_C@#168?@#181!9?A?_#155ACQh?uGtAgRGDqd?u?mPKa@Y`LOK_g?W?_?_??_!13?_?_!5?b_??G!6?O#198!7?@BBFFFNNN!12^!4NFFBB@@#207@#155!4?_?_WC?_?A#186OC???O???BO#155L!4?M_!8?[!4?BIC#183!6?P#195!6~}o#226???O#175G#218GC#222A@$#166!121?@#152@#175@CJCHO??GdA???KhD@!9?_JCA?M@cI?A#152A#166kPIT{?^icXaTiDiSkC{C@o]aKPiTAShQLk_#170BG#172_#156!24?@!5?G!7?___!12?L!4?O#171??@O!6?G?___#199???A?C?GG?OO!4?!4_!4?OO??G?CCA#171!6?O???Q@?@#166_GA??j??@BG_!8?uHq@@#185??@!4?C??O#203!12?@G#221!4?O#165C#182A#156@$#194!122?A#170ABCZsj!5?[@WrEGA#165@!6?O!5?P@#193!5?C#153!5?I#176_!9?A?@#197G#4A#157!12?@#147A#150G#175D?HCQH?AGT?cQG?Y@GP?aPK_CQA_S?W?_O#185?O???_???___#161!9?_#214_??B!5?G#212CpN#12@#11O#207!20?_!5?_#187!8?_!9?A??_?CA#158OC#152Ga!7?G#175o!4?@#193?o!6?G#164?Bo@]o??BJn}y~C!7?@N^^NFB@$#161!123?C#186G#165!4?K#185OA!8?OA#183??_HCA#201!6?g#223!6?G#216!5?o#197?O#168O#181!5?@#167A???P?G#154!12?@#152C#161O#164!26?@@BBEEFLNJLM^ZL^VL]^Z\]V^Z^X!6?V^]!7?F!4^~~}s{ogoo!5_!8?!5_oooWw{{}~~}^NLFB@wWC#177@#184_?O#173OSG???C#201?A#171NG?N#165o!9?A_!4?O@_#205!13?CO$#164!130?F#184oAW@!4?O!9?_WCqH?OcY@ux@#158!17?@???C@#176!11?O#160O#165!27?A?C!4?O#179!20?A#213N??O#185G#123???@#154_!4?C_#165!44?O??GA?G#167??@???C#5CC#159G_#156??o#164v~o#161??H!4?G#171???C???G_?Cs?@@$#152!135?_C#201???G#171G@!5?G?@#157!28?A??_A#177!42?A!7?O#10!19?O#121W@!7?GIOo#111_#175!45?_O#179???A#150OC#161??@??_#64A#8g#176CO#188!8?E???O#152O?C#156?W??@#175??O$#161!135?A#186@#164!4?_}~^uz\FB#216!31?A#153W#179!45?C#183@??A?CA@?CA?GA@?CA@G?C?A#132??Ao!6?@???G#152A#168!56?k#160?@G#10O#6O#168!10?H!4?Y#183???GA#207_!9?G$#188!135?K#187!5?O!88?@!5?O??O??O!9?C!6?G#163??C#110C_#92??C#200G#188!56?@#181?A#153@#157A#12_#173!11?uLAAc#199???FC#184???_$#171!231?G???O#134!19?dM!7?BC_#124A#194!57?A#162??O#186!15?kC@o#198???o#201???A$#131!257?_!5?O#128O#163!63?_#205!22?G$#11!257?C#190!4?A#113A$#119!257?G#201!4?_-#0!127~}{wo__#165!5?OA???_??A#152!12?G@#176!6?_C#197A#181!8?O!4?G#168!4?G#186ACG?_#176!4?@??C#172A?C!4?__#152!16?o!5?C??AA!8?@_`@?@@!9?AA!6?@CGG?G??O!5?_#185!12?O#193??_#188_!4?G#152_OGA!7?AG???G???@@!4?@!6?@O!4?AO?O#177A!7?O#187C#199???_#205?_???C#222C#167A#166@$#222!127?@!4?O#196!5?_#171W@!5?@#193!12?O#166oJsBShV?qlQiChUgA|GBoKW`aLYd_?BCIHqDiTiEkO_O_#168_#154G?O#161!19?O?G???OO?O?Q#194O#209?A#161!7?_#194_#149_C#134oM#92A!8?AG_#132@#128CO#155@@@???AA?KG??O??G!4?Oo!7?O!7?_OGC?@!5?_IB!5?e#179_!8?A_#168?C??W??Bo#165?G!4?@CK#176!8?_#219_!5?@$#152!128?@???GO@#164!4?_y~~^FB#209!13?_#173GsJ{jUg??OlTzUhV|AF??o_O@ADYP@?BTULyTiSwOgW_#155@?E?E@KPKaIcOEpI`KdOEpCI?Be@_?B?@?@?@#181!11?O#157A???A#170!5@!8?AE!6?OOOGG?!6_?_?_?!4_??O??G!4?GE@???O!4?o{B??Gk#172?C#8G@?G#184CO!4?_!5?C#161?A#171?@O!5?`#152!9?O#0_ow{}}!126~$#218!128?A!4?_#179!5?C#187C#177???G#156C#161!14?A#157!6?C#167H#4!9?_??@#167C?C#188???E#152SG?_#160!6?@#180AACC?G#150O#162O#170!21?_???_?_?___??__``#111!7?_A#121@!9?@CS#123?@#114G#6O#158_#175EMEMGKC???O?GW!4Oo???_#173!6?_?_O?G#175_O??AB!6?C!4?_OQW!5?A#185G??@O#173?Be\_??CG#185?C!6?O#196!10?G#218G$#170!129?@AE?C]B?`#168!22?C#158!6?G#160!11?O??B??o#170???G__#147!8?@#184@@?A@?fGEPMpKpIdWEtIpIdWExDiK@EDA?B?@?@?@@@??_@??@#7??G#211@_#18G#176C!6?C#17C#134@AyG_#14_#165??@!4?A???C!4?G!5?OOO?OOO???G??C?A?@!5?C??OC!4?_#189??@#13@A?O#151G#164BN~o#155K!9?BO!5?@#179A_#221!9?O$#193!129?A!4?_#159!32?A#158!11?_G??A#194!5?_#175O#193O#175!11?@?A@?@GA?APCPI`G?SAOI`GA?D???A@_?_???__#150!11?@#113C#120O@#167G#186A#193??A#168C#161@#206@?O#124??A#154A#147G#168??O?_???_#171?A???C!4?GG!9?G!5?A?@!5?G?@#162@!9?A!5?_#187O#170??B]_???vw_@K#164?K_@F[o?Nz~~|$#221!129?CG#155@CG?G_]G@!4?OGOl?zCqODj?uGV#160!8?@#176!11?C@#157A??O?_#152!16?A??_#186!22?O?O?O!7?QOO#214!9?O#132G#114?C#189@#202!6?A#149G#212AHCo#189C#186!4?O!5?_#201C#193_#185C#156!18?G??C???_!4?_??_o!9?O!6?_#188!4?G??A?@GC#183??A??G!7?A$#175!130?@?AA?sB?D!5?_OcAPC?HDi?SH#181!10?O#216!11?G#153C??CG#209!18?G#161OS?G_O?_#168!18?O?G??CC!6?AOA??AA?A#160!4?O#181!8?O#159_#182!8?@#179@!8?C#161!17?O!4?C!9?O!5?@OC#201??G#10C#15Cw_#182@#171C??K#166???Xa#186D!4?_#156?_???AG$#188!130?C#1O#184@@??[?A!6?_GOm?zCiOSj?vgF#159!20?A#174!23?G#165!26?_#188O?C?O?O?A!8?_!9?AA#161!15?OO!4?_#166!20?_?_O#184???C#182OG!7?G#188@?O#173_G@#22???A#12_#6A?O#175!10?G!4?_!5?A$#166!131?G#209!99?G#166?G?G?G!5CGCGCGSGEWM!4?o[cWcGsW_!6?Oo?_?_#164!5@!4B!6F!9N!4FBB@@?_oo{[FB#174C#158A#176@#184??C#194A#131!5?O#14W#11C?_#199!16?@#198@CO!8?W$#173!234?G?GC!5GCISYUGSWC!6?_WcWoG_!9?_???__#156A!8?GG#168!13?O??OG!7?_???A??_K@#190!4?_#169?@#150A#204!18?EG!9?C$#193!238?A#169!17?_#184!20?C?G?CO?GO#186!20?_??C!7?_!8?A#180!6?C#207!18?OA?_$#177!277?@???A#187!31?G?A#166?OGC!4?OE#205!26?G#203__!8?A!6?A@$#185!313?CA#179@#150?G#195!35?O!9?@~^^NFB@-#0!134~}{{wo__#193O#184CGsAXiDgUdQItGrC@#181!15?O?A#13G#159o!7?@#158@#181A?_#170BDMUSGWOo__!8?@#186?C??G??O??_#170!7?B?A!7?@??A?@@??BEB!5?AA?AAAEEK??G!7?GW?OO_!4OoO!7oOoOOOGGG!5?@!6?A???_A\F!5?@`#196C#187C!7?@#152O??A??@O?A?`G_#198@G@?G#187P!4?@#218?@$#221!134?@???G??_#186_#152!14?W#173_ZtI~iTj{iUxVlYDJ@!4?oIze\cWoO@??O???@A@FLANYtjUkqmWoGoO_?o??_#156!11?@@???A!4?C!5?G!5?O??O!9?__#213O!4?O#173BEDEDADEDADACAKAK?C?CACE@AB?@#185!7?_#184O#201C#188CA@!4?OA#179???w??b#178@O#14D?@?Go_#155?`?O???@EG_?A#164@K_?@BMNFB@#173A$#1!135?A#170@!5?O_A#186!12?_#166OcIt?TiSBThEgQdyCE@??{JtCXaZcG`ACW?_?@?@E?ALODIShQKOcKoGgOo?__#185!13?A@C?GA???_?C#163??_#172OG#134_O#17@#128C@#161C@??@!5?A?CO#121@?CO#111G_#163O#175?O__?!4_#168?@H??G?IAI???@C??A?@#155!13?G?A??_!4?[#199?O#8A!6?O#170??DC_??G@CO?CO#207AO#199?A#196_!6?@$#173!135?@#227A#155B???CGS?XcDoU@YGdGbKA#168G#5!15?_#12_?_#6M#176@!6?ACGS#152@??P_?C?G?O?_!8?B??C??G??O??_#177!10?A#171AECGGCS??_?_W!7?OO?O!5?___#169A!6?G#152??G?G#161G?G!9?@?@??G!5?A?@!7?G?@!7?G#185??F??O#207g#17@O!4?G#164AEO_#186@!6?@O#171?AO!8?A$#222!137?C??O#193!17?A#7!16?O#160C@!9?AC??_#175A??go__#188!4?_!6?A#155??@A?BC@E?DAWDOhCIc@Xa[?o@_@?_a!5?CC@!5?GG!6?!5O!8?__??O!5?_!7?_?__???O?G?C#175!12?O{!4?C??eA#189??C!5?C#172G#175??AG???E_#182_!4?_#195F}{O#222_???C$#165!138?C???@#223!15?C#180!16?G#14O#16C#64@#167A#197C#186!8?_A!8?G#168!7?@#161@!4?C??G??O??_#165!12?@???QA!4?CC!6?_!7?OO#173@@#166@!7?@B?@A@ADA@ADACACACAC?CACA?A@?@#171OOG??A!4?A@??GA!5?_e#184?W#190???G#15A~}{o#171@?HK#168??EH!4?AC_#156C#183!4?C#0?__ow{{}!134~$#171!138?BC#175GGABHcAOI@g?dOAS?@#15!18?_#22O#153!11?GO#188C#193G#194?@??C???O#184!7?@??BA@ECBKHEWTAydQhSGs?[_W?o?_#179?O??c#187G#22???_#132_OG?@#123C@#152?@!6?A#176?@!7?C#184???G#188??G?G!5?@@@?@?@H#165?_!5?G??A@!8?_C@!4?G!5?A!6?A??AG_!4?GO??@G!8?C$#164!139?BB@#18!34?G#104A#167!12?G?_#155?G#161A#168A#175!18?@??A?HAGD?ICQ`OIc??cGGPO_@@@BAABB!8?CKMKKKGG?KK#9@C!4?C#186!10?GGG?H!4?G?G?@??C#164!4_ow{{}~NF`ow{E@!6?W#191!5?_#12G#13??A#7A??_#173???@OW!4?G#225!8?O#1O#221G??A$#188!139?G#182CA#164!96?CCGGWWOwW!7?!10_#162!4?G!6?_#193!22?G??C??A#152!15?_G@OA#35!7?_#18??@#16C#161!6?G?A_?@#226!11?G$#219!139?O#11!109?OG#120_#92Q#190O#110A#147A#193??@#182O#185O#186@#168@???A!8?C#182!23?_??O???C!5?G?GCA@!7?@#163!10?@#184!6?O#193_!4?GA$#162!250?C#124C#121K#211G#150G#188!5?@#132!7?@?O@?O#175!25?OO?GCCAB#186!14?__?@#166!19?E_#201?O$#189!251?A#131_#212A#110!14?AG#134IQ?_#156!26?_!8?@?O_O???O!6?O!4?G!8?C??O#188???C???C?O$#209!251?@#128!17?C??C#180A#152!28?C#166!19?OA$#212!269?ADgg#168!49?KD$#190!269?_#92_#7?@$#15!272?A-#0!142~}}{wwo__#188O#210_#173??_OoKyTi^itj[juLIDB!7?AXvKzDEDRmXcW_!6?_!8?@?BA@FADBMHNAMYTIS[ugogWo_Wg_O_?_???_?_O#11C#110A#170!4G?!4_???CkcCS?IK#9A#11_!4?O#164Em}}}{}}}{}{}{!5}[]]!4^~~~^^^NNN!4F!7?o}v#184?hC#156W#207w#195w#7@#18G!9?O_#152A?OG??A?A#171@#187@#163@$#1!142?@??C#175@?E@?CGC@#166_KqDiT_TISbSHqTI?@!6?xeGrCY@AKPeW_?@?C#175?@@HBIAXaSCc?Woog?_?O?_#161!6?@???A???C!5?GG!7?G?_!4?O?O#185??AA!5?@!8?@O!4?@@#165@@@?!4@?@#156?@@#207_!4?_#165!4?_!8?C!5?_#179C??y??c#198?A#123A#14@?_??@?CGo_#175A!6?C#173C#218??A$#222!143?@??C??O#168!4?OG?@#176!11?_!8?o#168C!4?_!5?A?O?@ACGO#184???W_O?o??_#168!6?G!4?O??@?_??C?C!4?G!9?O_O?OO?O#156A!4?A!6?@#161??AO#92G???C#14_#177@#187O???A#201!4?@#182!4?@?@#183?_#199?__?_#163!6?_#162__?O#154O#151O#177G!7?G??C#185??B#203?C#178G#13AO#22!4?@#189@A#156AAGO_??AK#1O??C$#152!144?@!4?G???OGC@#157!13?O!7?B#161!6?O!4?@A#216@???O#155?A?CO_CG_?W_!4?_??_#184!10?@@?@AB?B?EDCAD@AA!5?G!4?gKGG[WSgO!4?O#172?@#114@#134BAC#117_#126G#152A#156??@@@#187!14?A#198???_#201!9?O??GG!7?O@#187?G!4?F#208@#190C#60O#17_!7?G#155@!4?A!4?@$#221!144?A??G??_#161!5?A#105!13?_#18O!5?G#158C#186!6?GG!4?C#152?OO@A?GO_!6?A??C???G??OO???__??@???A!4?C#186??G!9?_o??O?O#171A!8?@@???_!7?G@!7?A?A?A!6?@@!7?_??O!6?@!7?@?@#191!5?_#63_#15N^~~}}wo#151C#154G#170@CG`C?@$#186!145?A!7?_#159!17?G?@#8???o#194!7?_#170_!7?_?@ECEWdDADII?[CGGOWOo?_?___#209!6?A???C!4?G#157???O#172C?A?@#14@#132B#134@#152CA__#165!8?AA???_@O_#120C#121sHQ#129O#154C#178!34?_???O#155W@!4?G!4?Cq#196???O#86C#11!6?A#165@??CGO?@@#0__oww{}}!142~$#170!145?@A#155@?CJOBGA@#13!15?_#181A!5?G#193!7?O_!4?KG?_#161!9?@!5?C#155!17?@?@?A?A@?AD?A?@!8?@?C??C?CgSg?AA?IO#162?G#124O#212HqK#131@#200G#172!34?O#179G#204_#196_#180_#182A!6?A#10!17?C#171@#179C#172O#186@@C?O?GA$#184!147?A@ACJSBCA#167!15?C#17G?@??C#158!13?@A#157C???_#194!8?@#166@?@?@A?DAC@E?L@DISIAGSGS_GW_OO_OoO___?o#7A#113A#147?C#92@#188_!4?O#189!13?C#132???@#136_#191!36?_#170???_oLB?_E#150!22?_#161CA#188O??@@?A$#193!147?C!4?_#14!19?O?A@S#10A#159!15?AC#176O_?_#188!9?A!6?G???O!4?_#175!8?@?@?A@?A?@?@!6?CCC??C??_???OWwiCC!8?_!39?MA!8?QH#168!17?AG#222_?O??C$#197!148?G#15!23?_w{}j#11@#188!15?_#167A?GO#186!11?A??C!7?O!4?_#193!8?C#178!6?C#6O#180O#114C#131G!4?A#123@#182A#179A??AAA!9?_#211!6?A#161!42?OC??@#221!26?_??G!4?@$#219!148?O#5!23?C#7A#181!20?G#160G#202!45?G#8G#189C#158O#111C#18C#124C#164!5?!11@#168!54?_Ga#152W#201!27?GA$#22!173?C#211!69?G#214G#165@#162G#151G#166!71?OK@#182!27?C$#190!245?A#173!73?_OA$#193!320?@#186C-#0!152~}}{wwoo__#5!4?_???@#6!7?w#167B#197S!4?@#186S???AC??_???_???GO_???_#152!10?_?O???O!4?@!5?_!8?@#185!4C#196C#156!5C#165???AA!4?@#187A??@#156?@#190G!5?S#199CCC_???AQ???G?@CC!4?@#177A#196??C??A??@!5?A#163@#166C\A#152C#155Y!5?DG!6?_#7_!6?C#12A#4A#181@#218@$#221!152?@!4?G?O?_#176???O#6O#7G!9?A#173??bYtiSA!4?ADI\eWo@???A?GOo#147!13?_?_!7?_#171O??G?GG?G__!4?G!9?!4C???A???@@??C#192_!5?g#228G#205?_!4?O???G!8?O??G??C??A#198??@#190!4?@#169A#168A?G#161B#165_!4?g?o#204?_?g#162S?G#15B^NFFBB@#18?@#219A$#227!153?@#166@@?ADAKQXa|?DA@!9?{jWdITg!5?@ADAW_!4?FCKO_?_#169!13?_#161OOO!7?@??@!6?@?@#207o#199__#205_#198!12_!8?OO!8?__?C??O??AI???C@@aA#191O#178C#201G?A#161???@#208!9?C#178G#154_#173AD#170w#175D!5?gE#198?O#195~O#160@#6@#14@C?OGGC?A@$#218!154?A?C#167C?G#16!6?_#15_ow{!5~#104?D#168??C???Ah???@?GO!5?A!5?G#162!17?_#154__!7?_#186@??@@?@#182??C?C#193@#187G!7?G#204!5?_!9?O#123A#113O???A#172A!8?_???_???OG??G???A!5?@#189!7?C#157G#181_#186o#164??o~~v???_#199B#208?F#189a#152w#8C!5?G#2G?C$#1!155?C#173@@ADBLE\ANA@#14GCA!5?~#170!9?|VA[Wo???BE]kW__`AEC???_!5?@?@A@A@?@B?@B?A?A??AA!4?AAA?A?BB@B@B@A@@???@#185A!4?@#205?O#177??A#163O#128A???C#152@#204!4?CC!5?A!5?@??@!6?C#191!10?O#159O#171!4?K!6?]#205K#200??G#169A#11A#22G#9_?O#0_ooww{{}!152~$#181!155?A#168A#222?G?O?_#181???G#12O??@#175!15?Ag{__!7?O_O!4?@M?SAGOGCI?FI?C@IFECUMCECECEE??!4A???A!4?A?A??@??@@@#184?@?@#183O#195!7_!7?O!4WG!4KCCEEAAA??___ooowww{{{}}}~~~w_#193@#156!4?A???E#184Q@#209!5?C#172O#206O#3?_?O#13?C$#157!167?C#8C#17A#161!18?@#152???_???@!4?O???G#151!19?___??__#164!5OWOWWwwWGOOG!6WO!4W!4[MMKMMMNG!7?!6B!6@?G???C??AA?@@#163??A#182!16?@#187??G#179@??@#1!9?_$#159!168?A#193!25?@?G!4?@!6?_#165!17?O??GG!6?_C?CC?C#203!17?___#207O#199OO#200!4?C#132D???G#183!5?_!7?@!6?A#215??G#185!25?O$#188!195?C???C???C#184@A?TIHsJcjOew_SGGC?G?G??@#193?@#182G#188@!5?@?@?@#183_???O#196!13?A#213!9?@#92_#134[#129}_#111@#171!6?_???O!4?G???C!6?@$#158!198?@#167@#161G?A???O#185!20?O#179OO#187?O#156G?G???_#120!33?G#212b#118@#136^#121o#203!6?O#207A!6?@??_!5?@$#176!199?A#155???@??@I@SBcROdW?SHQC?G?G@?GG!5?!4C?C!5?A!6?A?!4A???@?@#179!20?_#155_#154?_#162_???O???G???C???A??@$#200!228?_#172!4?_#163_#194@#201_#156!51?O???_???O$#151!289?_???O???G!5?A??@$#165!289?O???G???C!6?@$#161!290?O#150O!7?C?C$#175!291?_???O#203`@?O??G!5?A??@$#187!293?_#152G!7?C-#0!162~}}}{{wwoo__#22O#5_#12_#6?~#166NtITgDi#186I!8?@ACGO_?_!7?G@!5?@#176_A#171_ABBAB@@@B??CCC!4?GG??OO??_?@!5?I!7?OOo!6C!8?@C!4?A#161@#207?AA@?A#204@@#203!4@???A?C?O#191!10?@#190AG_#156w!6?A#1O!5?A?@$#219!162?@#221@#229@#4A?C#13?C?G#7O#176!5?o#173AtiVyTT#170~U!4?B?KGO_?@CLphYC[GO_#188?@???@#159??O@#155_SCgS??o?o?WgH_o___O??A?@#156_!9?G#203@@#195!13@#149G#110W#134_??C#7A#155C??@!5?@#199??AB?A!6?A??C#198?G#196!12?CO#170B!6?@#182H#199E#222G?C#169@#161@#188@$#9!165?@#15@@@BFFNN^N#197???G#168!5?_#175?hFPH`CeO!5?BAEUco_o_#180!9?_A#156G!9?@#152???@!8?@!6?C!7?GK#175C#179??CCOO#196O#156OOO!8?C!8?@#205???@!8?A!4?_#159!11?@#150A#152G!5?O#0___oww{{{}}!162~$#10!166?A?C#3G#123G#2O?_#155!14?gASIOG_O_!5?G??G#194A#157!11?C#165O@!4?CCC???@@_G!8?__!8?A??G!4?CC#154!5?G!9?A!6?@#183!10?A?AA#162!19?C#175C???_#225_?O#207?@#195BB#208B#229A$#14!167?AA!6?O~#184!11?OkaSgO?_#173@ACG!7?@A@UGUkWe[A#160G#150C#177C#184_W?_?_?oGo???O?O!4?_!9?A#198?@@@#147!14?G!8?A#162A??@@#195!14?!4@BBFN!10~}w#186@#154O#185F#171@_?S??C#226G#204C#196C$#152!193?@ACGO`A?O???A!4?_!8?G#185O#175G_S?_WGG?G_?O???OO?_?_#183!7?O!8?A?A!8?A#163a#111f#212^#118~#136~#117A#131C#165??@#152?A#185A#179A#208!34?_#164??}^^J#221_?O!5?A?@$#166!195?@ADGO_!5?@A@EGUgQeWa[@#164!4?@??A???A?AAEECEC?KGKK[[Owoxgwwwo_qa!4o_??!4_a!8?xww{{w{{}}{|!4{}}{}!5{wwo#155!20?GM$#168!198?A?O!4?@??C??_!5?@#170!6?GW!6?O!6?_#154@!4?A??A!4?C#162C!8?!5G?G#178?@#121!4?X#190w#187!5?C#165!42?B$#188!199?C#161G!6?CG???@?@#187!10?A?A?A?A!4?A??C?C!4?G?G!6?O?O!5?_!6?_#132!5?_#200@#201!48?C$#193!204?@!4?O???@#182!14?C#179DC!6?G?A?O!4?@C#207??@!7?A$#185!235?GG???Q!4?@??C#199??@@!7?A?!7A$#161!235?@#172@!7?A!4?A??C!4?C??GG!9?S!9?A@$#151!237?@@!7?AA!7?CC!5?G$#177!238?A!7?C@!7?A$#162!239?@#201?A!7?C??A!4?G$#150!241?@#163@A!6?AC!6?C-#0!175~}}}{{wwwooo___#152O??_!6?@??_!5?_?_A#188S#157???O@#156CA#165!13?@???A??C??@@?_@??AA#154!19?_!5?@#187E#195!28?@F^NNNFFFBBB@#151A#152@#226@#222@$#1!175?@#3@#4@#221A??C?GG?O??_#161!10?A?OA?GO!4?@G!4?O#171G@#185!14?@!7?@!5?@A??A#163!18?F!5?W#199!29?CO#188_#218_??O??G#196C#210C#178?@$#167!178?@#166@@A@AE#168C#170FD!6?_???BEWW?@BEGOP?BS!7?_#179!15?@???@@!4?O???@???AC#172!16?W!5?E#156!29?_#203G?O??G??C#219G$#218!179?A#210A#173@?@@@#193G#175IGDOG@cOGa@SGC_!4?AGAX?G!6?AO`GCQ`OGDA_PBI?aY??C_WWO#182!5?A#92!23?w???O#184_#198!29?A#175_#221??_!7?C?A$#219!180?C#186?A??A??O??_!9?C@!4?_!4?A#159???_C#172@#177!17?@?@#187?A?A!6?C???G???G#111!14?F???_#191!34?O??G!4?A$#222!182?C!4?O??_#168!9?@AGO!8?@#181???G#165__C#171!17?@?A??CAIAQ_??@CCCAEKCC#134!14?G#117~#136~#113G#0!35?___oooww{{}}}!175~$#230!183?C?G#155??AGBEWBCTGi?o!5?@C@CAS!8?GSbgCIdQgTIcS`YC_UgOC_?gOo@@#212!26?v#120??C#193!35?O#1?O!9?@$#184!188?DAKPEWJaTSg?_!5?@EGcG!9?ISRhSIdQgTIgScWCgO_??_?_?_#128!30?A#223!38?G$#166!201?@AKo?_!6?~?lE#160A#179O#170!18?CGW#164?A?CCKK[{w{xxpxxrrz!12~!7?x!28~W$#173!202?C_Go!6?_?~Q@#162G#196!20?A!8?A#132!28?@$#193!204?C!6?_#156!29?D?G-#0!192~}}}{{{!4w!4o!5_#167O#181??OA#175c?G?U?Hc?A`O@COId?GAgD_SAPG`AhT?Q_O?O#187G??@#171!13?o!6?__???_!4?O!4?G???C???A#1C#188A#222A#156@#219A??@$#221!192?@??A???C??GG???O???__#3?_#154O#156W@#170q#184UhHu?ZdWbKpItAWfgTaSJpK`UhSGOkO?o?_#123!17?_#92@#134[?_#11G#175A!6?_!4?O#0!4_!4o!4w!4{}}}!191~$#222!193?@!6?C!4?G???O#227O#159!4?C#172A#155WDh?u?ZcWEKqId?WfOTAWJ_KaUGSA_l?K_G_#154!18?@#113E#212bi#129K#14A#155@#187H#182!4?_#188?_!4?O#227O#165?G???C#231???A#226???@#225@$#197!194?@!6?C#173?CDADAEKBMHF#176?@#202@#152_#170!28?AA??G#171C???_#190!15?M#120G#117?DO#110@#156O#225!8?_#218_!4?O???G???C!6?@$#193!195?@#218A!7?G???O!4?_#180?G#165?C#156!30?@#164@BAFE^]}!13~N!7?U~~~!5^!5N!4F!4B!4@$#170!196?@@??AAB#168B!5?@#194!5?_#171?A#185!31?A???O#200!17?O#128o#121?O#136B#114C#165C#221!10?_!4?O???G!6?A$#176!197?A#1C!7?O#165!43?C???_#119!21?O#185G#156!10?O#1_#193!4?G???C$#186!198?A???C#166?ADALHAK@UWN#183!36?@?@?@#213!20?_#224!18?G???C$#155!198?@??@#230!4?G#179!45?G$#152!199?A#175@$#184!199?@-#0!216~!6}!7{!14w!13o!15w!8{!6}!215~$#221!216?@@!4?AAA!6?CCC#218C#181??C#225C#176CC#173C#186CCC#188!10C!5?C#173?C#225C#227C#222C#218CC#221CC!7?A!5?@#1@$#218!218?@#181@!6?A#175@?@!4?@??@??@?A!5?@?A@??@?A#164@@!6B!5@#175A!7?@#226@#224@#222@#218@$#224!220?@#186@!7?A#155@?@??@A?B?A@?@B?B?A@?A@A?@#156@#165A#171A!6?AA???@#123@#132@#121@@#5A#189@#231@#219A$#161!222?@!7?A#193A#219!11?G#1!12G#194??C??C#156!5?AA#165A#1C#159A#214A#11A#8A#128@#220A$#152!223?@!8?A#170!21?AA#232???C$#170!224?@@@!6?AA#186!20?CCC$#222!225?A!9?CC$#225!227?A#184@??@?@?A@A?B?A@A?B?B?A@?A@$#197!228?A#1CC-#0!500~-!500~-!500~-!500~-!500~-!500~-!500~-!500~-!500~-!500B\ \ No newline at end of file diff --git a/src/tests/fixtures/terminal_emulator_startup_response b/src/tests/fixtures/terminal_emulator_startup_response new file mode 100644 index 00000000..9d79ff52 --- /dev/null +++ b/src/tests/fixtures/terminal_emulator_startup_response @@ -0,0 +1 @@ +]11;rgb:0000/0000/0000\]10;rgb:ffff/ffff/ffff\]4;0;rgb:0000/0000/0000\]4;1;rgb:cdcd/0000/0000\]4;2;rgb:0000/cdcd/0000\]4;3;rgb:cdcd/cdcd/0000\]4;4;rgb:0000/0000/eeee\]4;5;rgb:cdcd/0000/cdcd\]4;6;rgb:0000/cdcd/cdcd\]4;7;rgb:e5e5/e5e5/e5e5\]4;8;rgb:7f7f/7f7f/7f7f\]4;9;rgb:ffff/0000/0000\]4;10;rgb:0000/ffff/0000\]4;11;rgb:ffff/ffff/0000\]4;12;rgb:5c5c/5c5c/ffff\]4;13;rgb:ffff/0000/ffff\]4;14;rgb:0000/ffff/ffff\]4;15;rgb:ffff/ffff/ffff\]4;16;rgb:0000/0000/0000\]4;17;rgb:0000/0000/5f5f\]4;18;rgb:0000/0000/8787\]4;19;rgb:0000/0000/afaf\]4;20;rgb:0000/0000/d7d7\]4;21;rgb:0000/0000/ffff\]4;22;rgb:0000/5f5f/0000\]4;23;rgb:0000/5f5f/5f5f\]4;24;rgb:0000/5f5f/8787\]4;25;rgb:0000/5f5f/afaf\]4;26;rgb:0000/5f5f/d7d7\]4;27;rgb:0000/5f5f/ffff\]4;28;rgb:0000/8787/0000\]4;29;rgb:0000/8787/5f5f\]4;30;rgb:0000/8787/8787\]4;31;rgb:0000/8787/afaf\]4;32;rgb:0000/8787/d7d7\]4;33;rgb:0000/8787/ffff\]4;34;rgb:0000/afaf/0000\]4;35;rgb:0000/afaf/5f5f\]4;36;rgb:0000/afaf/8787\]4;37;rgb:0000/afaf/afaf\]4;38;rgb:0000/afaf/d7d7\]4;39;rgb:0000/afaf/ffff\]4;40;rgb:0000/d7d7/0000\]4;41;rgb:0000/d7d7/5f5f\]4;42;rgb:0000/d7d7/8787\]4;43;rgb:0000/d7d7/afaf\]4;44;rgb:0000/d7d7/d7d7\]4;45;rgb:0000/d7d7/ffff\]4;46;rgb:0000/ffff/0000\]4;47;rgb:0000/ffff/5f5f\]4;48;rgb:0000/ffff/8787\]4;49;rgb:0000/ffff/afaf\]4;50;rgb:0000/ffff/d7d7\]4;51;rgb:0000/ffff/ffff\]4;52;rgb:5f5f/0000/0000\]4;53;rgb:5f5f/0000/5f5f\]4;54;rgb:5f5f/0000/8787\]4;55;rgb:5f5f/0000/afaf\]4;56;rgb:5f5f/0000/d7d7\]4;57;rgb:5f5f/0000/ffff\]4;58;rgb:5f5f/5f5f/0000\]4;59;rgb:5f5f/5f5f/5f5f\]4;60;rgb:5f5f/5f5f/8787\]4;61;rgb:5f5f/5f5f/afaf\]4;62;rgb:5f5f/5f5f/d7d7\]4;63;rgb:5f5f/5f5f/ffff\]4;64;rgb:5f5f/8787/0000\]4;65;rgb:5f5f/8787/5f5f\]4;66;rgb:5f5f/8787/8787\]4;67;rgb:5f5f/8787/afaf\]4;68;rgb:5f5f/8787/d7d7\]4;69;rgb:5f5f/8787/ffff\]4;70;rgb:5f5f/afaf/0000\]4;71;rgb:5f5f/afaf/5f5f\]4;72;rgb:5f5f/afaf/8787\]4;73;rgb:5f5f/afaf/afaf\]4;74;rgb:5f5f/afaf/d7d7\]4;75;rgb:5f5f/afaf/ffff\]4;76;rgb:5f5f/d7d7/0000\]4;77;rgb:5f5f/d7d7/5f5f\]4;78;rgb:5f5f/d7d7/8787\]4;79;rgb:5f5f/d7d7/afaf\]4;80;rgb:5f5f/d7d7/d7d7\]4;81;rgb:5f5f/d7d7/ffff\]4;82;rgb:5f5f/ffff/0000\]4;83;rgb:5f5f/ffff/5f5f\]4;84;rgb:5f5f/ffff/8787\]4;85;rgb:5f5f/ffff/afaf\]4;86;rgb:5f5f/ffff/d7d7\]4;87;rgb:5f5f/ffff/ffff\]4;88;rgb:8787/0000/0000\]4;89;rgb:8787/0000/5f5f\]4;90;rgb:8787/0000/8787\]4;91;rgb:8787/0000/afaf\]4;92;rgb:8787/0000/d7d7\]4;93;rgb:8787/0000/ffff\]4;94;rgb:8787/5f5f/0000\]4;95;rgb:8787/5f5f/5f5f\]4;96;rgb:8787/5f5f/8787\]4;97;rgb:8787/5f5f/afaf\]4;98;rgb:8787/5f5f/d7d7\]4;99;rgb:8787/5f5f/ffff\]4;100;rgb:8787/8787/0000\]4;101;rgb:8787/8787/5f5f\]4;102;rgb:8787/8787/8787\]4;103;rgb:8787/8787/afaf\]4;104;rgb:8787/8787/d7d7\]4;105;rgb:8787/8787/ffff\]4;106;rgb:8787/afaf/0000\]4;107;rgb:8787/afaf/5f5f\]4;108;rgb:8787/afaf/8787\]4;109;rgb:8787/afaf/afaf\]4;110;rgb:8787/afaf/d7d7\]4;111;rgb:8787/afaf/ffff\]4;112;rgb:8787/d7d7/0000\]4;113;rgb:8787/d7d7/5f5f\]4;114;rgb:8787/d7d7/8787\]4;115;rgb:8787/d7d7/afaf\]4;116;rgb:8787/d7d7/d7d7\]4;117;rgb:8787/d7d7/ffff\]4;118;rgb:8787/ffff/0000\]4;119;rgb:8787/ffff/5f5f\]4;120;rgb:8787/ffff/8787\]4;121;rgb:8787/ffff/afaf\]4;122;rgb:8787/ffff/d7d7\]4;123;rgb:8787/ffff/ffff\]4;124;rgb:afaf/0000/0000\]4;125;rgb:afaf/0000/5f5f\]4;126;rgb:afaf/0000/8787\]4;127;rgb:afaf/0000/afaf\]4;128;rgb:afaf/0000/d7d7\]4;129;rgb:afaf/0000/ffff\]4;130;rgb:afaf/5f5f/0000\]4;131;rgb:afaf/5f5f/5f5f\]4;132;rgb:afaf/5f5f/8787\]4;133;rgb:afaf/5f5f/afaf\]4;134;rgb:afaf/5f5f/d7d7\]4;135;rgb:afaf/5f5f/ffff\]4;136;rgb:afaf/8787/0000\]4;137;rgb:afaf/8787/5f5f\]4;138;rgb:afaf/8787/8787\]4;139;rgb:afaf/8787/afaf\]4;140;rgb:afaf/8787/d7d7\]4;141;rgb:afaf/8787/ffff\]4;142;rgb:afaf/afaf/0000\]4;143;rgb:afaf/afaf/5f5f\]4;144;rgb:afaf/afaf/8787\]4;145;rgb:afaf/afaf/afaf\]4;146;rgb:afaf/afaf/d7d7\]4;147;rgb:afaf/afaf/ffff\]4;148;rgb:afaf/d7d7/0000\]4;149;rgb:afaf/d7d7/5f5f\]4;150;rgb:afaf/d7d7/8787\]4;151;rgb:afaf/d7d7/afaf\]4;152;rgb:afaf/d7d7/d7d7\]4;153;rgb:afaf/d7d7/ffff\]4;154;rgb:afaf/ffff/0000\]4;155;rgb:afaf/ffff/5f5f\]4;156;rgb:afaf/ffff/8787\]4;157;rgb:afaf/ffff/afaf\]4;158;rgb:afaf/ffff/d7d7\]4;159;rgb:afaf/ffff/ffff\]4;160;rgb:d7d7/0000/0000\]4;161;rgb:d7d7/0000/5f5f\]4;162;rgb:d7d7/0000/8787\]4;163;rgb:d7d7/0000/afaf\]4;164;rgb:d7d7/0000/d7d7\]4;165;rgb:d7d7/0000/ffff\]4;166;rgb:d7d7/5f5f/0000\]4;167;rgb:d7d7/5f5f/5f5f\]4;168;rgb:d7d7/5f5f/8787\]4;169;rgb:d7d7/5f5f/afaf\]4;170;rgb:d7d7/5f5f/d7d7\]4;171;rgb:d7d7/5f5f/ffff\]4;172;rgb:d7d7/8787/0000\]4;173;rgb:d7d7/8787/5f5f\]4;174;rgb:d7d7/8787/8787\]4;175;rgb:d7d7/8787/afaf\]4;176;rgb:d7d7/8787/d7d7\]4;177;rgb:d7d7/8787/ffff\]4;178;rgb:d7d7/afaf/0000\]4;179;rgb:d7d7/afaf/5f5f\]4;180;rgb:d7d7/afaf/8787\]4;181;rgb:d7d7/afaf/afaf\]4;182;rgb:d7d7/afaf/d7d7\]4;183;rgb:d7d7/afaf/ffff\]4;184;rgb:d7d7/d7d7/0000\]4;185;rgb:d7d7/d7d7/5f5f\]4;186;rgb:d7d7/d7d7/8787\]4;187;rgb:d7d7/d7d7/afaf\]4;188;rgb:d7d7/d7d7/d7d7\]4;189;rgb:d7d7/d7d7/ffff\]4;190;rgb:d7d7/ffff/0000\]4;191;rgb:d7d7/ffff/5f5f\]4;192;rgb:d7d7/ffff/8787\]4;193;rgb:d7d7/ffff/afaf\]4;194;rgb:d7d7/ffff/d7d7\]4;195;rgb:d7d7/ffff/ffff\]4;196;rgb:ffff/0000/0000\]4;197;rgb:ffff/0000/5f5f\]4;198;rgb:ffff/0000/8787\]4;199;rgb:ffff/0000/afaf\]4;200;rgb:ffff/0000/d7d7\]4;201;rgb:ffff/0000/ffff\]4;202;rgb:ffff/5f5f/0000\]4;203;rgb:ffff/5f5f/5f5f\]4;204;rgb:ffff/5f5f/8787\]4;205;rgb:ffff/5f5f/afaf\]4;206;rgb:ffff/5f5f/d7d7\]4;207;rgb:ffff/5f5f/ffff\]4;208;rgb:ffff/8787/0000\]4;209;rgb:ffff/8787/5f5f\]4;210;rgb:ffff/8787/8787\]4;211;rgb:ffff/8787/afaf\]4;212;rgb:ffff/8787/d7d7\]4;213;rgb:ffff/8787/ffff\]4;214;rgb:ffff/afaf/0000\]4;215;rgb:ffff/afaf/5f5f\]4;216;rgb:ffff/afaf/8787\]4;217;rgb:ffff/afaf/afaf\]4;218;rgb:ffff/afaf/d7d7\]4;219;rgb:ffff/afaf/ffff\]4;220;rgb:ffff/d7d7/0000\]4;221;rgb:ffff/d7d7/5f5f\]4;222;rgb:ffff/d7d7/8787\]4;223;rgb:ffff/d7d7/afaf\]4;224;rgb:ffff/d7d7/d7d7\]4;225;rgb:ffff/d7d7/ffff\]4;226;rgb:ffff/ffff/0000\]4;227;rgb:ffff/ffff/5f5f\]4;228;rgb:ffff/ffff/8787\]4;229;rgb:ffff/ffff/afaf\]4;230;rgb:ffff/ffff/d7d7\]4;231;rgb:ffff/ffff/ffff\]4;232;rgb:0808/0808/0808\]4;233;rgb:1212/1212/1212\]4;234;rgb:1c1c/1c1c/1c1c\]4;235;rgb:2626/2626/2626\]4;236;rgb:3030/3030/3030\]4;237;rgb:3a3a/3a3a/3a3a\]4;238;rgb:4444/4444/4444\]4;239;rgb:4e4e/4e4e/4e4e\]4;240;rgb:5858/5858/5858\]4;241;rgb:6262/6262/6262\]4;242;rgb:6c6c/6c6c/6c6c\]4;243;rgb:7676/7676/7676\]4;244;rgb:8080/8080/8080\]4;245;rgb:8a8a/8a8a/8a8a\]4;246;rgb:9494/9494/9494\]4;247;rgb:9e9e/9e9e/9e9e\]4;248;rgb:a8a8/a8a8/a8a8\]4;249;rgb:b2b2/b2b2/b2b2\]4;250;rgb:bcbc/bcbc/bcbc\]4;251;rgb:c6c6/c6c6/c6c6\]4;252;rgb:d0d0/d0d0/d0d0\]4;253;rgb:dada/dada/dada\]4;254;rgb:e4e4/e4e4/e4e4\]4;255;rgb:eeee/eeee/eeee\ diff --git a/zellij-client/src/fake_client.rs b/zellij-client/src/fake_client.rs index 7338d633..88b41c9e 100644 --- a/zellij-client/src/fake_client.rs +++ b/zellij-client/src/fake_client.rs @@ -2,12 +2,13 @@ //! and dispatch actions, that are specified through the command line. //! Multiple actions at the same time can be dispatched. use log::debug; +use std::sync::{Arc, Mutex}; use std::{fs, path::PathBuf, thread}; use crate::{ command_is_executing::CommandIsExecuting, input_handler::input_actions, - os_input_output::ClientOsApi, stdin_handler::stdin_loop, ClientInfo, ClientInstruction, - InputInstruction, + os_input_output::ClientOsApi, stdin_ansi_parser::StdinAnsiParser, stdin_handler::stdin_loop, + ClientInfo, ClientInstruction, InputInstruction, }; use zellij_utils::{ channels::{self, ChannelWithContext, SenderWithContext}, @@ -82,12 +83,13 @@ pub fn start_fake_client( }) }); + let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new())); let _stdin_thread = thread::Builder::new() .name("stdin_handler".to_string()) .spawn({ let os_input = os_input.clone(); let send_input_instructions = send_input_instructions.clone(); - move || stdin_loop(os_input, send_input_instructions) + move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser) }); let clients: Vec; diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 3f92c537..5f8cc063 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -1,8 +1,7 @@ //! Main input logic. use crate::{ - os_input_output::ClientOsApi, - stdin_ansi_parser::{AnsiStdinInstructionOrKeys, StdinAnsiParser}, - ClientId, ClientInstruction, CommandIsExecuting, InputInstruction, + os_input_output::ClientOsApi, stdin_ansi_parser::AnsiStdinInstruction, ClientId, + ClientInstruction, CommandIsExecuting, InputInstruction, }; use zellij_utils::{ channels::{Receiver, SenderWithContext, OPENCALLS}, @@ -69,19 +68,6 @@ impl InputHandler { if self.options.mouse_mode.unwrap_or(true) { self.os_input.enable_mouse(); } - // [14t => get text area size in pixels, - // [16t => get character cell size in pixels - // ]11;?\ => get background color - // ]10;?\ => get foreground color - let get_cell_pixel_info = - "\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}"; - let _ = self - .os_input - .get_stdout_writer() - .write(get_cell_pixel_info.as_bytes()) - .unwrap(); - let mut ansi_stdin_parser = StdinAnsiParser::new(); - ansi_stdin_parser.increment_expected_ansi_instructions(4); loop { if self.should_exit { break; @@ -91,13 +77,7 @@ impl InputHandler { match input_event { InputEvent::Key(key_event) => { let key = cast_termwiz_key(key_event, &raw_bytes); - if ansi_stdin_parser.expected_instructions() > 0 { - self.handle_possible_pixel_instruction( - ansi_stdin_parser.parse(key, raw_bytes), - ); - } else { - self.handle_key(&key, raw_bytes); - } + self.handle_key(&key, raw_bytes); }, InputEvent::Mouse(mouse_event) => { let mouse_event = @@ -126,13 +106,13 @@ impl InputHandler { Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => { self.mode = input_mode; }, - Ok((InputInstruction::PossiblePixelRatioChange, _error_context)) => { - let _ = self - .os_input - .get_stdout_writer() - .write(get_cell_pixel_info.as_bytes()) - .unwrap(); - ansi_stdin_parser.increment_expected_ansi_instructions(4); + Ok(( + InputInstruction::AnsiStdinInstructions(ansi_stdin_instructions), + _error_context, + )) => { + for ansi_instruction in ansi_stdin_instructions { + self.handle_stdin_ansi_instruction(ansi_instruction); + } }, Err(err) => panic!("Encountered read error: {:?}", err), } @@ -147,33 +127,28 @@ impl InputHandler { } } } - fn handle_possible_pixel_instruction( - &mut self, - pixel_instruction_or_keys: Option, - ) { - match pixel_instruction_or_keys { - Some(AnsiStdinInstructionOrKeys::PixelDimensions(pixel_dimensions)) => { + fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) { + match ansi_stdin_instructions { + AnsiStdinInstruction::PixelDimensions(pixel_dimensions) => { self.os_input .send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions)); }, - Some(AnsiStdinInstructionOrKeys::BackgroundColor(background_color_instruction)) => { + AnsiStdinInstruction::BackgroundColor(background_color_instruction) => { self.os_input .send_to_server(ClientToServerMsg::BackgroundColor( background_color_instruction, )); }, - Some(AnsiStdinInstructionOrKeys::ForegroundColor(foreground_color_instruction)) => { + AnsiStdinInstruction::ForegroundColor(foreground_color_instruction) => { self.os_input .send_to_server(ClientToServerMsg::ForegroundColor( foreground_color_instruction, )); }, - Some(AnsiStdinInstructionOrKeys::Keys(keys)) => { - for (key, raw_bytes) in keys { - self.handle_key(&key, raw_bytes); - } + AnsiStdinInstruction::ColorRegisters(color_registers) => { + self.os_input + .send_to_server(ClientToServerMsg::ColorRegisters(color_registers)); }, - None => {}, } } fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) { @@ -352,7 +327,6 @@ pub(crate) fn input_loop( ) .handle_input(); } - /// Entry point to the module. Instantiates an [`InputHandler`] and starts /// its [`InputHandler::handle_input()`] loop. #[allow(clippy::too_many_arguments)] @@ -379,7 +353,3 @@ pub(crate) fn input_actions( ) .handle_actions(actions, &session_name, clients); } - -#[cfg(test)] -#[path = "./unit/input_handler_tests.rs"] -mod input_handler_tests; diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 9790984e..3b81694b 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -13,8 +13,10 @@ use std::env::current_exe; use std::io::{self, Write}; use std::path::Path; use std::process::Command; +use std::sync::{Arc, Mutex}; use std::thread; +use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser}; use crate::{ command_is_executing::CommandIsExecuting, input_handler::input_loop, os_input_output::ClientOsApi, stdin_handler::stdin_loop, @@ -114,7 +116,7 @@ impl ClientInfo { pub(crate) enum InputInstruction { KeyEvent(InputEvent, Vec), SwitchToMode(InputMode), - PossiblePixelRatioChange, + AnsiStdinInstructions(Vec), } pub fn start_client( @@ -126,7 +128,7 @@ pub fn start_client( layout: Option, ) { info!("Starting Zellij client!"); - let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; + let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; let bracketed_paste = "\u{1b}[?2004h"; os_input.unset_raw_mode(0).unwrap(); @@ -162,11 +164,13 @@ pub fn start_client( let first_msg = match info { ClientInfo::Attach(name, config_options) => { envs::set_session_name(name); + envs::set_initial_environment_vars(); ClientToServerMsg::AttachClient(client_attributes, config_options) }, ClientInfo::New(name) => { envs::set_session_name(name); + envs::set_initial_environment_vars(); spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); @@ -214,13 +218,15 @@ pub fn start_client( }); let on_force_close = config_options.on_force_close.unwrap_or_default(); + let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new())); let _stdin_thread = thread::Builder::new() .name("stdin_handler".to_string()) .spawn({ let os_input = os_input.clone(); let send_input_instructions = send_input_instructions.clone(); - move || stdin_loop(os_input, send_input_instructions) + let stdin_ansi_parser = stdin_ansi_parser.clone(); + move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser) }); let _input_thread = thread::Builder::new() @@ -246,7 +252,6 @@ pub fn start_client( let _signal_thread = thread::Builder::new() .name("signal_listener".to_string()) .spawn({ - let send_input_instructions = send_input_instructions.clone(); let os_input = os_input.clone(); move || { os_input.handle_signals( @@ -256,8 +261,16 @@ pub fn start_client( os_api.send_to_server(ClientToServerMsg::TerminalResize( os_api.get_terminal_size_using_fd(0), )); - let _ = send_input_instructions - .send(InputInstruction::PossiblePixelRatioChange); + // send a query to the terminal emulator in case the font size changed + // as well - we'll parse the response through STDIN + let terminal_emulator_query_string = stdin_ansi_parser + .lock() + .unwrap() + .window_size_change_query_string(); + let _ = os_api + .get_stdout_writer() + .write(terminal_emulator_query_string.as_bytes()) + .unwrap(); } }), Box::new({ @@ -385,3 +398,7 @@ pub fn start_client( let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); stdout.flush().unwrap(); } + +#[cfg(test)] +#[path = "./unit/stdin_tests.rs"] +mod stdin_tests; diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index 60c22cc9..f9f3c590 100644 --- a/zellij-client/src/os_input_output.rs +++ b/zellij-client/src/os_input_output.rs @@ -94,7 +94,7 @@ pub trait ClientOsApi: Send + Sync { fn get_stdout_writer(&self) -> Box; fn get_stdin_reader(&self) -> Box; /// Returns the raw contents of standard input. - fn read_from_stdin(&self) -> Vec; + fn read_from_stdin(&mut self) -> Vec; /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. fn box_clone(&self) -> Box; /// Sends a message to the server. @@ -126,7 +126,7 @@ impl ClientOsApi for ClientOsInputOutput { fn box_clone(&self) -> Box { Box::new((*self).clone()) } - fn read_from_stdin(&self) -> Vec { + fn read_from_stdin(&mut self) -> Vec { let stdin = std::io::stdin(); let mut stdin = stdin.lock(); let buffer = stdin.fill_buf().unwrap(); diff --git a/zellij-client/src/stdin_ansi_parser.rs b/zellij-client/src/stdin_ansi_parser.rs index 25f0e4fb..09247bf9 100644 --- a/zellij-client/src/stdin_ansi_parser.rs +++ b/zellij-client/src/stdin_ansi_parser.rs @@ -1,200 +1,234 @@ +use std::time::{Duration, Instant}; + +const STARTUP_PARSE_DEADLINE_MS: u64 = 500; +const SIGWINCH_PARSE_DEADLINE_MS: u64 = 200; use zellij_utils::{ - data::{CharOrArrow, Key}, - ipc::PixelDimensions, - lazy_static::lazy_static, - pane_size::SizeInPixels, - regex::Regex, + ipc::PixelDimensions, lazy_static::lazy_static, pane_size::SizeInPixels, regex::Regex, }; +#[derive(Debug)] pub struct StdinAnsiParser { - expected_ansi_instructions: usize, - current_buffer: Vec<(Key, Vec)>, + raw_buffer: Vec, + pending_color_sequences: Vec<(usize, String)>, + pending_events: Vec, + parse_deadline: Option, } impl StdinAnsiParser { pub fn new() -> Self { StdinAnsiParser { - expected_ansi_instructions: 0, - current_buffer: vec![], + raw_buffer: vec![], + pending_color_sequences: vec![], + pending_events: vec![], + parse_deadline: None, } } - pub fn increment_expected_ansi_instructions(&mut self, to: usize) { - self.expected_ansi_instructions += to; + pub fn terminal_emulator_query_string(&mut self) -> String { + // note that this assumes the String will be sent to the terminal emulator and so starts a + // deadline timeout (self.parse_deadline) + + // [14t => get text area size in pixels, + // [16t => get character cell size in pixels + // ]11;?\ => get background color + // ]10;?\ => get foreground color + let mut query_string = + String::from("\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}"); + + // query colors + // eg. ]4;5;?\ => query color register number 5 + for i in 0..256 { + query_string.push_str(&format!("\u{1b}]4;{};?\u{1b}\u{5c}", i)); + } + self.parse_deadline = + Some(Instant::now() + Duration::from_millis(STARTUP_PARSE_DEADLINE_MS)); + query_string } - pub fn decrement_expected_ansi_instructions(&mut self, by: usize) { - self.expected_ansi_instructions = self.expected_ansi_instructions.saturating_sub(by); + pub fn window_size_change_query_string(&mut self) -> String { + // note that this assumes the String will be sent to the terminal emulator and so starts a + // deadline timeout (self.parse_deadline) + + // [14t => get text area size in pixels, + // [16t => get character cell size in pixels + let query_string = String::from("\u{1b}[14t\u{1b}[16t"); + + self.parse_deadline = + Some(Instant::now() + Duration::from_millis(SIGWINCH_PARSE_DEADLINE_MS)); + query_string } - pub fn expected_instructions(&self) -> usize { - self.expected_ansi_instructions - } - pub fn parse(&mut self, key: Key, raw_bytes: Vec) -> Option { - if self.current_buffer.is_empty() - && (key != Key::Esc && key != Key::Alt(CharOrArrow::Char(']'))) + fn drain_pending_events(&mut self) -> Vec { + let mut events = vec![]; + events.append(&mut self.pending_events); + if let Some(color_registers) = + AnsiStdinInstruction::color_registers_from_bytes(&mut self.pending_color_sequences) { - // the first key of a sequence is always Esc, but termwiz interprets esc + ] as Alt+] - self.current_buffer.push((key, raw_bytes)); - self.expected_ansi_instructions = 0; - return Some(AnsiStdinInstructionOrKeys::Keys( - self.current_buffer.drain(..).collect(), - )); - } - if let Key::Char('t') = key { - self.current_buffer.push((key, raw_bytes)); - match AnsiStdinInstructionOrKeys::pixel_dimensions_from_keys(&self.current_buffer) { - Ok(pixel_instruction) => { - self.decrement_expected_ansi_instructions(1); - self.current_buffer.clear(); - Some(pixel_instruction) - }, - Err(_) => { - self.expected_ansi_instructions = 0; - Some(AnsiStdinInstructionOrKeys::Keys( - self.current_buffer.drain(..).collect(), - )) - }, - } - } else if let Key::Alt(CharOrArrow::Char('\\')) | Key::Ctrl('g') = key { - match AnsiStdinInstructionOrKeys::color_sequence_from_keys(&self.current_buffer) { - Ok(color_instruction) => { - self.decrement_expected_ansi_instructions(1); - self.current_buffer.clear(); - Some(color_instruction) - }, - Err(_) => { - self.expected_ansi_instructions = 0; - Some(AnsiStdinInstructionOrKeys::Keys( - self.current_buffer.drain(..).collect(), - )) - }, - } - } else if self.key_is_valid(key) { - self.current_buffer.push((key, raw_bytes)); - None - } else { - self.current_buffer.push((key, raw_bytes)); - self.expected_ansi_instructions = 0; - Some(AnsiStdinInstructionOrKeys::Keys( - self.current_buffer.drain(..).collect(), - )) + events.push(color_registers); } + events } - fn key_is_valid(&self, key: Key) -> bool { - match key { - Key::Esc => { - // this is a UX improvement - // in case the user's terminal doesn't support one or more of these signals, - // if they spam ESC they need to be able to get back to normal mode and not "us - // waiting for ansi instructions" mode - !self.current_buffer.iter().any(|(key, _)| *key == Key::Esc) - }, - Key::Char(';') - | Key::Char('[') - | Key::Char(']') - | Key::Char('r') - | Key::Char('g') - | Key::Char('b') - | Key::Char('\\') - | Key::Char(':') - | Key::Char('/') => true, - Key::Alt(CharOrArrow::Char(']')) => true, - Key::Alt(CharOrArrow::Char('\\')) => true, - Key::Char(c) => { - matches!(c, '0'..='9' | 'a'..='f') - }, - _ => false, + pub fn should_parse(&self) -> bool { + if let Some(parse_deadline) = self.parse_deadline { + if parse_deadline >= Instant::now() { + return true; + } + } + false + } + pub fn parse(&mut self, mut raw_bytes: Vec) -> Vec { + for byte in raw_bytes.drain(..) { + self.parse_byte(byte); + } + self.drain_pending_events() + } + fn parse_byte(&mut self, byte: u8) { + if byte == b't' { + self.raw_buffer.push(byte); + match AnsiStdinInstruction::pixel_dimensions_from_bytes(&self.raw_buffer) { + Ok(ansi_sequence) => { + self.pending_events.push(ansi_sequence); + self.raw_buffer.clear(); + }, + Err(_) => { + self.raw_buffer.clear(); + }, + } + } else if byte == b'\\' { + self.raw_buffer.push(byte); + if let Ok(ansi_sequence) = AnsiStdinInstruction::bg_or_fg_from_bytes(&self.raw_buffer) { + self.pending_events.push(ansi_sequence); + self.raw_buffer.clear(); + } else if let Ok((color_register, color_sequence)) = + color_sequence_from_bytes(&self.raw_buffer) + { + self.raw_buffer.clear(); + self.pending_color_sequences + .push((color_register, color_sequence)); + } else { + self.raw_buffer.clear(); + } + } else { + self.raw_buffer.push(byte); } } } -#[derive(Debug)] -pub enum AnsiStdinInstructionOrKeys { +#[derive(Debug, Clone)] +pub enum AnsiStdinInstruction { PixelDimensions(PixelDimensions), BackgroundColor(String), ForegroundColor(String), - Keys(Vec<(Key, Vec)>), + ColorRegisters(Vec<(usize, String)>), } -impl AnsiStdinInstructionOrKeys { - pub fn pixel_dimensions_from_keys(keys: &[(Key, Vec)]) -> Result { +impl AnsiStdinInstruction { + pub fn pixel_dimensions_from_bytes(bytes: &[u8]) -> Result { + // eg. [4;21;8t lazy_static! { static ref RE: Regex = Regex::new(r"^\u{1b}\[(\d+);(\d+);(\d+)t$").unwrap(); } - let key_sequence: Vec> = keys - .iter() - .map(|(key, _)| match key { - Key::Char(c) => Some(*c), - Key::Esc => Some('\u{1b}'), - _ => None, - }) - .collect(); - if key_sequence.iter().all(|k| k.is_some()) { - let key_string: String = key_sequence.iter().map(|k| k.unwrap()).collect(); - let captures = RE - .captures_iter(&key_string) - .next() - .ok_or("invalid_instruction")?; - let csi_index = captures[1].parse::(); - let first_field = captures[2].parse::(); - let second_field = captures[3].parse::(); - if csi_index.is_err() || first_field.is_err() || second_field.is_err() { - return Err("invalid_instruction"); - } - match csi_index { - Ok(4) => { - // text area size - Ok(AnsiStdinInstructionOrKeys::PixelDimensions( - PixelDimensions { - character_cell_size: None, - text_area_size: Some(SizeInPixels { - height: first_field.unwrap(), - width: second_field.unwrap(), - }), - }, - )) - }, - Ok(6) => { - // character cell size - Ok(AnsiStdinInstructionOrKeys::PixelDimensions( - PixelDimensions { - character_cell_size: Some(SizeInPixels { - height: first_field.unwrap(), - width: second_field.unwrap(), - }), - text_area_size: None, - }, - )) - }, - _ => Err("invalid sequence"), - } - } else { - Err("invalid sequence") + let key_string = String::from_utf8_lossy(bytes); // TODO: handle error + let captures = RE + .captures_iter(&key_string) + .next() + .ok_or("invalid_instruction")?; + let csi_index = captures[1].parse::(); + let first_field = captures[2].parse::(); + let second_field = captures[3].parse::(); + if csi_index.is_err() || first_field.is_err() || second_field.is_err() { + return Err("invalid_instruction"); + } + match csi_index { + Ok(4) => { + // text area size + Ok(AnsiStdinInstruction::PixelDimensions(PixelDimensions { + character_cell_size: None, + text_area_size: Some(SizeInPixels { + height: first_field.unwrap(), + width: second_field.unwrap(), + }), + })) + }, + Ok(6) => { + // character cell size + Ok(AnsiStdinInstruction::PixelDimensions(PixelDimensions { + character_cell_size: Some(SizeInPixels { + height: first_field.unwrap(), + width: second_field.unwrap(), + }), + text_area_size: None, + })) + }, + _ => Err("invalid sequence"), } } - pub fn color_sequence_from_keys(keys: &[(Key, Vec)]) -> Result { + pub fn bg_or_fg_from_bytes(bytes: &[u8]) -> Result { + // eg. ]11;rgb:0000/0000/0000\ lazy_static! { - static ref BACKGROUND_RE: Regex = Regex::new(r"11;(.*)$").unwrap(); + static ref BACKGROUND_RE: Regex = Regex::new(r"\]11;(.*)\u{1b}\\$").unwrap(); } + // eg. ]10;rgb:ffff/ffff/ffff\ lazy_static! { - static ref FOREGROUND_RE: Regex = Regex::new(r"10;(.*)$").unwrap(); + static ref FOREGROUND_RE: Regex = Regex::new(r"\]10;(.*)\u{1b}\\$").unwrap(); } - let key_string = keys.iter().fold(String::new(), |mut acc, (key, _)| { - if let Key::Char(c) = key { - acc.push(*c) - }; - acc - }); + let key_string = String::from_utf8_lossy(bytes); if let Some(captures) = BACKGROUND_RE.captures_iter(&key_string).next() { let background_query_response = captures[1].parse::(); - Ok(AnsiStdinInstructionOrKeys::BackgroundColor( - background_query_response.unwrap(), - )) + match background_query_response { + Ok(background_query_response) => Ok(AnsiStdinInstruction::BackgroundColor( + background_query_response, + )), + _ => Err("invalid_instruction"), + } } else if let Some(captures) = FOREGROUND_RE.captures_iter(&key_string).next() { let foreground_query_response = captures[1].parse::(); - Ok(AnsiStdinInstructionOrKeys::ForegroundColor( - foreground_query_response.unwrap(), - )) + match foreground_query_response { + Ok(foreground_query_response) => Ok(AnsiStdinInstruction::ForegroundColor( + foreground_query_response, + )), + _ => Err("invalid_instruction"), + } } else { Err("invalid_instruction") } } + pub fn color_registers_from_bytes(color_sequences: &mut Vec<(usize, String)>) -> Option { + if color_sequences.is_empty() { + return None; + } + let mut registers = vec![]; + for (color_register, color_sequence) in color_sequences.drain(..) { + registers.push((color_register, color_sequence)); + } + Some(AnsiStdinInstruction::ColorRegisters(registers)) + } +} + +fn color_sequence_from_bytes(bytes: &[u8]) -> Result<(usize, String), &'static str> { + lazy_static! { + static ref COLOR_REGISTER_RE: Regex = Regex::new(r"\]4;(.*);(.*)\u{1b}\\$").unwrap(); + } + lazy_static! { + // this form is used by eg. Alacritty, where the leading 4 is dropped in the response + static ref ALTERNATIVE_COLOR_REGISTER_RE: Regex = Regex::new(r"\](.*);(.*)\u{1b}\\$").unwrap(); + } + let key_string = String::from_utf8_lossy(bytes); + if let Some(captures) = COLOR_REGISTER_RE.captures_iter(&key_string).next() { + let color_register_response = captures[1].parse::(); + let color_response = captures[2].parse::(); + match (color_register_response, color_response) { + (Ok(crr), Ok(cr)) => Ok((crr, cr)), + _ => Err("invalid_instruction"), + } + } else if let Some(captures) = ALTERNATIVE_COLOR_REGISTER_RE + .captures_iter(&key_string) + .next() + { + let color_register_response = captures[1].parse::(); + let color_response = captures[2].parse::(); + match (color_register_response, color_response) { + (Ok(crr), Ok(cr)) => Ok((crr, cr)), + _ => Err("invalid_instruction"), + } + } else { + Err("invalid_instruction") + } } diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs index 687ecd10..8d6e51b0 100644 --- a/zellij-client/src/stdin_handler.rs +++ b/zellij-client/src/stdin_handler.rs @@ -1,17 +1,46 @@ use crate::os_input_output::ClientOsApi; +use crate::stdin_ansi_parser::StdinAnsiParser; use crate::InputInstruction; +use std::sync::{Arc, Mutex}; use zellij_utils::channels::SenderWithContext; use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons}; pub(crate) fn stdin_loop( - os_input: Box, + mut os_input: Box, send_input_instructions: SenderWithContext, + stdin_ansi_parser: Arc>, ) { let mut holding_mouse = false; let mut input_parser = InputParser::new(); let mut current_buffer = vec![]; + // on startup we send a query to the terminal emulator for stuff like the pixel size and colors + // we get a response through STDIN, so it makes sense to do this here + let terminal_emulator_query_string = stdin_ansi_parser + .lock() + .unwrap() + .terminal_emulator_query_string(); + let _ = os_input + .get_stdout_writer() + .write(terminal_emulator_query_string.as_bytes()) + .unwrap(); loop { let buf = os_input.read_from_stdin(); + { + // here we check if we need to parse specialized ANSI instructions sent over STDIN + // this happens either on startup (see above) or on SIGWINCH + // + // if we need to parse them, we do so with an internal timeout - anything else we + // receive on STDIN during that timeout is unceremoniously dropped + let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap(); + if stdin_ansi_parser.should_parse() { + let events = stdin_ansi_parser.parse(buf); + if !events.is_empty() { + let _ = send_input_instructions + .send(InputInstruction::AnsiStdinInstructions(events)); + } + continue; + } + } current_buffer.append(&mut buf.to_vec()); let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely let mut events = vec![]; diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs deleted file mode 100644 index b772eb5c..00000000 --- a/zellij-client/src/unit/input_handler_tests.rs +++ /dev/null @@ -1,733 +0,0 @@ -use super::input_loop; -use zellij_utils::data::{InputMode, Palette}; -use zellij_utils::input::actions::{Action, Direction}; -use zellij_utils::input::config::Config; -use zellij_utils::input::options::Options; -use zellij_utils::nix; -use zellij_utils::pane_size::{Size, SizeInPixels}; -use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers}; - -use crate::InputInstruction; -use crate::{ - os_input_output::{ClientOsApi, StdinPoller}, - ClientInstruction, CommandIsExecuting, -}; - -use std::path::Path; - -use std::io; -use std::os::unix::io::RawFd; -use std::sync::{Arc, Mutex}; -use zellij_utils::{ - errors::ErrorContext, - ipc::{ClientToServerMsg, PixelDimensions, ServerToClientMsg}, -}; - -use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext}; - -#[allow(unused)] -pub mod commands { - pub const QUIT: [u8; 1] = [17]; // ctrl-q - pub const ESC: [u8; 1] = [27]; - pub const ENTER: [u8; 1] = [10]; // char '\n' - - pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h - pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l - - pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p - pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n - pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p - pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d - pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r - pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f - pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x - pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j - pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k - pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h - pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l - - pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s - pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k - pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j - pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b - pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f - - pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r - pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j - pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k - pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h - pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l - - pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t - pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n - pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l - pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h - pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x - - pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~ - pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201 - pub const SLEEP: [u8; 0] = []; -} - -#[derive(Default, Clone)] -struct FakeStdoutWriter { - buffer: Arc>>, -} -impl FakeStdoutWriter { - pub fn new(buffer: Arc>>) -> Self { - FakeStdoutWriter { buffer } - } -} -impl io::Write for FakeStdoutWriter { - fn write(&mut self, buf: &[u8]) -> Result { - self.buffer.lock().unwrap().extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> Result<(), io::Error> { - Ok(()) - } -} - -#[derive(Clone)] -struct FakeClientOsApi { - events_sent_to_server: Arc>>, - command_is_executing: Arc>, - stdout_buffer: Arc>>, -} - -impl FakeClientOsApi { - pub fn new( - events_sent_to_server: Arc>>, - command_is_executing: CommandIsExecuting, - ) -> Self { - // while command_is_executing itself is implemented with an Arc, we have to have an - // Arc here because we need interior mutability, otherwise we'll have to change the - // ClientOsApi trait, and that will cause a lot of havoc - let command_is_executing = Arc::new(Mutex::new(command_is_executing)); - let stdout_buffer = Arc::new(Mutex::new(vec![])); - FakeClientOsApi { - events_sent_to_server, - command_is_executing, - stdout_buffer, - } - } - pub fn stdout_buffer(&self) -> Vec { - self.stdout_buffer.lock().unwrap().drain(..).collect() - } -} - -impl ClientOsApi for FakeClientOsApi { - fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size { - unimplemented!() - } - fn set_raw_mode(&mut self, _fd: RawFd) { - unimplemented!() - } - fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> { - unimplemented!() - } - fn get_stdout_writer(&self) -> Box { - let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone()); - Box::new(fake_stdout_writer) - } - fn get_stdin_reader(&self) -> Box { - unimplemented!() - } - fn read_from_stdin(&self) -> Vec { - unimplemented!() - } - fn box_clone(&self) -> Box { - unimplemented!() - } - fn send_to_server(&self, msg: ClientToServerMsg) { - { - let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap(); - events_sent_to_server.push(msg); - } - { - let mut command_is_executing = self.command_is_executing.lock().unwrap(); - command_is_executing.unblock_input_thread(); - } - } - fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> { - unimplemented!() - } - fn handle_signals(&self, _sigwinch_cb: Box, _quit_cb: Box) { - unimplemented!() - } - fn connect_to_server(&self, _path: &Path) { - unimplemented!() - } - fn load_palette(&self) -> Palette { - unimplemented!() - } - fn enable_mouse(&self) {} - fn disable_mouse(&self) {} - fn stdin_poller(&self) -> StdinPoller { - unimplemented!() - } -} - -fn extract_actions_sent_to_server( - events_sent_to_server: Arc>>, -) -> Vec { - let events_sent_to_server = events_sent_to_server.lock().unwrap(); - events_sent_to_server.iter().fold(vec![], |mut acc, event| { - if let ClientToServerMsg::Action(action, None) = event { - acc.push(action.clone()); - } - acc - }) -} - -fn extract_pixel_events_sent_to_server( - events_sent_to_server: Arc>>, -) -> Vec { - let events_sent_to_server = events_sent_to_server.lock().unwrap(); - events_sent_to_server.iter().fold(vec![], |mut acc, event| { - if let ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions) = event { - acc.push(*pixel_dimensions); - } - acc - }) -} - -#[test] -pub fn quit_breaks_input_loop() { - let stdin_events = vec![( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - )]; - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = Box::new(FakeClientOsApi::new( - events_sent_to_server.clone(), - command_is_executing.clone(), - )); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - input_loop( - client_os_api, - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let expected_actions_sent_to_server = vec![Action::Quit]; - let received_actions = extract_actions_sent_to_server(events_sent_to_server); - assert_eq!( - expected_actions_sent_to_server, received_actions, - "All actions sent to server properly" - ); -} - -#[test] -pub fn move_focus_left_in_normal_mode() { - let stdin_events = vec![ - ( - commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('h'), - modifiers: Modifiers::ALT, - }), - ), - ( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - ), - ]; - - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = Box::new(FakeClientOsApi::new( - events_sent_to_server.clone(), - command_is_executing.clone(), - )); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - input_loop( - client_os_api, - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let expected_actions_sent_to_server = - vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit]; - let received_actions = extract_actions_sent_to_server(events_sent_to_server); - assert_eq!( - expected_actions_sent_to_server, received_actions, - "All actions sent to server properly" - ); -} - -#[test] -pub fn pixel_info_queried_from_terminal_emulator() { - let stdin_events = vec![( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - )]; - - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = FakeClientOsApi::new(events_sent_to_server, command_is_executing.clone()); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - let client_os_api_clone = client_os_api.clone(); - input_loop( - Box::new(client_os_api), - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let extracted_stdout_buffer = client_os_api_clone.stdout_buffer(); - assert_eq!( - String::from_utf8(extracted_stdout_buffer), - Ok(String::from( - "\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\\\u{1b}]10;?\u{1b}\\" - )), - ); -} - -#[test] -pub fn pixel_info_sent_to_server() { - let stdin_events = vec![ - ( - vec![27], - InputEvent::Key(KeyEvent { - key: KeyCode::Escape, - modifiers: Modifiers::NONE, - }), - ), - ( - "[".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('['), - modifiers: Modifiers::NONE, - }), - ), - ( - "6".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('6'), - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "1".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('1'), - modifiers: Modifiers::NONE, - }), - ), - ( - "0".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('0'), - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "5".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('5'), - modifiers: Modifiers::NONE, - }), - ), - ( - "t".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('t'), - modifiers: Modifiers::NONE, - }), - ), - ( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - ), - ]; - - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = - FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - input_loop( - Box::new(client_os_api), - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); - let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); - assert_eq!(actions_sent_to_server, vec![Action::Quit]); - assert_eq!( - pixel_events_sent_to_server, - vec![PixelDimensions { - character_cell_size: Some(SizeInPixels { - height: 10, - width: 5 - }), - text_area_size: None - }], - ); -} - -#[test] -pub fn corrupted_pixel_info_sent_as_key_events() { - let stdin_events = vec![ - ( - vec![27], - InputEvent::Key(KeyEvent { - key: KeyCode::Escape, - modifiers: Modifiers::NONE, - }), - ), - ( - "[".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('['), - modifiers: Modifiers::NONE, - }), - ), - ( - "f".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('f'), - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "1".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('1'), - modifiers: Modifiers::NONE, - }), - ), - ( - "0".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('0'), - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "5".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('5'), - modifiers: Modifiers::NONE, - }), - ), - ( - "t".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('t'), - modifiers: Modifiers::NONE, - }), - ), - ( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - ), - ]; - - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = - FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - input_loop( - Box::new(client_os_api), - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); - let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); - assert_eq!( - actions_sent_to_server, - vec![ - Action::Write(vec![27]), - Action::Write(vec![b'[']), - Action::Write(vec![b'f']), - Action::Write(vec![b';']), - Action::Write(vec![b'1']), - Action::Write(vec![b'0']), - Action::Write(vec![b';']), - Action::Write(vec![b'5']), - Action::Write(vec![b't']), - Action::Quit - ] - ); - assert_eq!(pixel_events_sent_to_server, vec![],); -} - -#[test] -pub fn esc_in_the_middle_of_pixelinfo_breaks_out_of_it() { - let stdin_events = vec![ - ( - vec![27], - InputEvent::Key(KeyEvent { - key: KeyCode::Escape, - modifiers: Modifiers::NONE, - }), - ), - ( - "[".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('['), - modifiers: Modifiers::NONE, - }), - ), - ( - vec![27], - InputEvent::Key(KeyEvent { - key: KeyCode::Escape, - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "1".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('1'), - modifiers: Modifiers::NONE, - }), - ), - ( - "0".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('0'), - modifiers: Modifiers::NONE, - }), - ), - ( - ";".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char(';'), - modifiers: Modifiers::NONE, - }), - ), - ( - "5".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('5'), - modifiers: Modifiers::NONE, - }), - ), - ( - "t".as_bytes().to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('t'), - modifiers: Modifiers::NONE, - }), - ), - ( - commands::QUIT.to_vec(), - InputEvent::Key(KeyEvent { - key: KeyCode::Char('q'), - modifiers: Modifiers::CTRL, - }), - ), - ]; - - let events_sent_to_server = Arc::new(Mutex::new(vec![])); - let command_is_executing = CommandIsExecuting::new(); - let client_os_api = - FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); - let config = Config::from_default_assets().unwrap(); - let options = Options::default(); - - let (send_client_instructions, _receive_client_instructions): ChannelWithContext< - ClientInstruction, - > = channels::bounded(50); - let send_client_instructions = SenderWithContext::new(send_client_instructions); - - let (send_input_instructions, receive_input_instructions): ChannelWithContext< - InputInstruction, - > = channels::bounded(50); - let send_input_instructions = SenderWithContext::new(send_input_instructions); - for event in stdin_events { - send_input_instructions - .send(InputInstruction::KeyEvent(event.1, event.0)) - .unwrap(); - } - - let default_mode = InputMode::Normal; - input_loop( - Box::new(client_os_api), - config, - options, - command_is_executing, - send_client_instructions, - default_mode, - receive_input_instructions, - ); - let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); - let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); - assert_eq!( - actions_sent_to_server, - vec![ - Action::Write(vec![27]), - Action::Write(vec![b'[']), - Action::Write(vec![27]), - Action::Write(vec![b';']), - Action::Write(vec![b'1']), - Action::Write(vec![b'0']), - Action::Write(vec![b';']), - Action::Write(vec![b'5']), - Action::Write(vec![b't']), - Action::Quit - ] - ); - assert_eq!(pixel_events_sent_to_server, vec![],); -} diff --git a/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap b/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap new file mode 100644 index 00000000..14e5d101 --- /dev/null +++ b/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap @@ -0,0 +1,6 @@ +--- +source: zellij-client/src/./unit/input_handler_tests.rs +assertion_line: 429 +expression: "*format!(\"{:?}\", events_sent_to_server.lock().unwrap())" +--- +[TerminalPixelDimensions(PixelDimensions { text_area_size: Some(SizeInPixels { height: 401, width: 1566 }), character_cell_size: None }), TerminalPixelDimensions(PixelDimensions { text_area_size: None, character_cell_size: Some(SizeInPixels { height: 21, width: 8 }) }), BackgroundColor("rgb:0000/0000/0000"), ForegroundColor("rgb:ffff/ffff/ffff"), ColorRegisters([(0, "rgb:0000/0000/0000"), (1, "rgb:cdcd/0000/0000"), (2, "rgb:0000/cdcd/0000"), (3, "rgb:cdcd/cdcd/0000"), (4, "rgb:0000/0000/eeee"), (5, "rgb:cdcd/0000/cdcd"), (6, "rgb:0000/cdcd/cdcd"), (7, "rgb:e5e5/e5e5/e5e5"), (8, "rgb:7f7f/7f7f/7f7f"), (9, "rgb:ffff/0000/0000"), (10, "rgb:0000/ffff/0000"), (11, "rgb:ffff/ffff/0000"), (12, "rgb:5c5c/5c5c/ffff"), (13, "rgb:ffff/0000/ffff"), (14, "rgb:0000/ffff/ffff"), (15, "rgb:ffff/ffff/ffff"), (16, "rgb:0000/0000/0000"), (17, "rgb:0000/0000/5f5f"), (18, "rgb:0000/0000/8787"), (19, "rgb:0000/0000/afaf"), (20, "rgb:0000/0000/d7d7"), (21, "rgb:0000/0000/ffff"), (22, "rgb:0000/5f5f/0000"), (23, "rgb:0000/5f5f/5f5f"), (24, "rgb:0000/5f5f/8787"), (25, "rgb:0000/5f5f/afaf"), (26, "rgb:0000/5f5f/d7d7"), (27, "rgb:0000/5f5f/ffff"), (28, "rgb:0000/8787/0000"), (29, "rgb:0000/8787/5f5f"), (30, "rgb:0000/8787/8787"), (31, "rgb:0000/8787/afaf"), (32, "rgb:0000/8787/d7d7"), (33, "rgb:0000/8787/ffff"), (34, "rgb:0000/afaf/0000"), (35, "rgb:0000/afaf/5f5f"), (36, "rgb:0000/afaf/8787"), (37, "rgb:0000/afaf/afaf"), (38, "rgb:0000/afaf/d7d7"), (39, "rgb:0000/afaf/ffff"), (40, "rgb:0000/d7d7/0000"), (41, "rgb:0000/d7d7/5f5f"), (42, "rgb:0000/d7d7/8787"), (43, "rgb:0000/d7d7/afaf"), (44, "rgb:0000/d7d7/d7d7"), (45, "rgb:0000/d7d7/ffff"), (46, "rgb:0000/ffff/0000"), (47, "rgb:0000/ffff/5f5f"), (48, "rgb:0000/ffff/8787"), (49, "rgb:0000/ffff/afaf"), (50, "rgb:0000/ffff/d7d7"), (51, "rgb:0000/ffff/ffff"), (52, "rgb:5f5f/0000/0000"), (53, "rgb:5f5f/0000/5f5f"), (54, "rgb:5f5f/0000/8787"), (55, "rgb:5f5f/0000/afaf"), (56, "rgb:5f5f/0000/d7d7"), (57, "rgb:5f5f/0000/ffff"), (58, "rgb:5f5f/5f5f/0000"), (59, "rgb:5f5f/5f5f/5f5f"), (60, "rgb:5f5f/5f5f/8787"), (61, "rgb:5f5f/5f5f/afaf"), (62, "rgb:5f5f/5f5f/d7d7"), (63, "rgb:5f5f/5f5f/ffff"), (64, "rgb:5f5f/8787/0000"), (65, "rgb:5f5f/8787/5f5f"), (66, "rgb:5f5f/8787/8787"), (67, "rgb:5f5f/8787/afaf"), (68, "rgb:5f5f/8787/d7d7"), (69, "rgb:5f5f/8787/ffff"), (70, "rgb:5f5f/afaf/0000"), (71, "rgb:5f5f/afaf/5f5f"), (72, "rgb:5f5f/afaf/8787"), (73, "rgb:5f5f/afaf/afaf"), (74, "rgb:5f5f/afaf/d7d7"), (75, "rgb:5f5f/afaf/ffff"), (76, "rgb:5f5f/d7d7/0000"), (77, "rgb:5f5f/d7d7/5f5f"), (78, "rgb:5f5f/d7d7/8787"), (79, "rgb:5f5f/d7d7/afaf"), (80, "rgb:5f5f/d7d7/d7d7"), (81, "rgb:5f5f/d7d7/ffff"), (82, "rgb:5f5f/ffff/0000"), (83, "rgb:5f5f/ffff/5f5f"), (84, "rgb:5f5f/ffff/8787"), (85, "rgb:5f5f/ffff/afaf"), (86, "rgb:5f5f/ffff/d7d7"), (87, "rgb:5f5f/ffff/ffff"), (88, "rgb:8787/0000/0000"), (89, "rgb:8787/0000/5f5f"), (90, "rgb:8787/0000/8787"), (91, "rgb:8787/0000/afaf"), (92, "rgb:8787/0000/d7d7"), (93, "rgb:8787/0000/ffff"), (94, "rgb:8787/5f5f/0000"), (95, "rgb:8787/5f5f/5f5f"), (96, "rgb:8787/5f5f/8787"), (97, "rgb:8787/5f5f/afaf"), (98, "rgb:8787/5f5f/d7d7"), (99, "rgb:8787/5f5f/ffff"), (100, "rgb:8787/8787/0000"), (101, "rgb:8787/8787/5f5f"), (102, "rgb:8787/8787/8787"), (103, "rgb:8787/8787/afaf"), (104, "rgb:8787/8787/d7d7"), (105, "rgb:8787/8787/ffff"), (106, "rgb:8787/afaf/0000"), (107, "rgb:8787/afaf/5f5f"), (108, "rgb:8787/afaf/8787"), (109, "rgb:8787/afaf/afaf"), (110, "rgb:8787/afaf/d7d7"), (111, "rgb:8787/afaf/ffff"), (112, "rgb:8787/d7d7/0000"), (113, "rgb:8787/d7d7/5f5f"), (114, "rgb:8787/d7d7/8787"), (115, "rgb:8787/d7d7/afaf"), (116, "rgb:8787/d7d7/d7d7"), (117, "rgb:8787/d7d7/ffff"), (118, "rgb:8787/ffff/0000"), (119, "rgb:8787/ffff/5f5f"), (120, "rgb:8787/ffff/8787"), (121, "rgb:8787/ffff/afaf"), (122, "rgb:8787/ffff/d7d7"), (123, "rgb:8787/ffff/ffff"), (124, "rgb:afaf/0000/0000"), (125, "rgb:afaf/0000/5f5f"), (126, "rgb:afaf/0000/8787"), (127, "rgb:afaf/0000/afaf"), (128, "rgb:afaf/0000/d7d7"), (129, "rgb:afaf/0000/ffff"), (130, "rgb:afaf/5f5f/0000"), (131, "rgb:afaf/5f5f/5f5f"), (132, "rgb:afaf/5f5f/8787"), (133, "rgb:afaf/5f5f/afaf"), (134, "rgb:afaf/5f5f/d7d7"), (135, "rgb:afaf/5f5f/ffff"), (136, "rgb:afaf/8787/0000"), (137, "rgb:afaf/8787/5f5f"), (138, "rgb:afaf/8787/8787"), (139, "rgb:afaf/8787/afaf"), (140, "rgb:afaf/8787/d7d7"), (141, "rgb:afaf/8787/ffff"), (142, "rgb:afaf/afaf/0000"), (143, "rgb:afaf/afaf/5f5f"), (144, "rgb:afaf/afaf/8787"), (145, "rgb:afaf/afaf/afaf"), (146, "rgb:afaf/afaf/d7d7"), (147, "rgb:afaf/afaf/ffff"), (148, "rgb:afaf/d7d7/0000"), (149, "rgb:afaf/d7d7/5f5f"), (150, "rgb:afaf/d7d7/8787"), (151, "rgb:afaf/d7d7/afaf"), (152, "rgb:afaf/d7d7/d7d7"), (153, "rgb:afaf/d7d7/ffff"), (154, "rgb:afaf/ffff/0000"), (155, "rgb:afaf/ffff/5f5f"), (156, "rgb:afaf/ffff/8787"), (157, "rgb:afaf/ffff/afaf"), (158, "rgb:afaf/ffff/d7d7"), (159, "rgb:afaf/ffff/ffff"), (160, "rgb:d7d7/0000/0000"), (161, "rgb:d7d7/0000/5f5f"), (162, "rgb:d7d7/0000/8787"), (163, "rgb:d7d7/0000/afaf"), (164, "rgb:d7d7/0000/d7d7"), (165, "rgb:d7d7/0000/ffff"), (166, "rgb:d7d7/5f5f/0000"), (167, "rgb:d7d7/5f5f/5f5f"), (168, "rgb:d7d7/5f5f/8787"), (169, "rgb:d7d7/5f5f/afaf"), (170, "rgb:d7d7/5f5f/d7d7"), (171, "rgb:d7d7/5f5f/ffff"), (172, "rgb:d7d7/8787/0000"), (173, "rgb:d7d7/8787/5f5f"), (174, "rgb:d7d7/8787/8787"), (175, "rgb:d7d7/8787/afaf"), (176, "rgb:d7d7/8787/d7d7"), (177, "rgb:d7d7/8787/ffff"), (178, "rgb:d7d7/afaf/0000"), (179, "rgb:d7d7/afaf/5f5f"), (180, "rgb:d7d7/afaf/8787"), (181, "rgb:d7d7/afaf/afaf"), (182, "rgb:d7d7/afaf/d7d7"), (183, "rgb:d7d7/afaf/ffff"), (184, "rgb:d7d7/d7d7/0000"), (185, "rgb:d7d7/d7d7/5f5f"), (186, "rgb:d7d7/d7d7/8787"), (187, "rgb:d7d7/d7d7/afaf"), (188, "rgb:d7d7/d7d7/d7d7"), (189, "rgb:d7d7/d7d7/ffff"), (190, "rgb:d7d7/ffff/0000"), (191, "rgb:d7d7/ffff/5f5f"), (192, "rgb:d7d7/ffff/8787"), (193, "rgb:d7d7/ffff/afaf"), (194, "rgb:d7d7/ffff/d7d7"), (195, "rgb:d7d7/ffff/ffff"), (196, "rgb:ffff/0000/0000"), (197, "rgb:ffff/0000/5f5f"), (198, "rgb:ffff/0000/8787"), (199, "rgb:ffff/0000/afaf"), (200, "rgb:ffff/0000/d7d7"), (201, "rgb:ffff/0000/ffff"), (202, "rgb:ffff/5f5f/0000"), (203, "rgb:ffff/5f5f/5f5f"), (204, "rgb:ffff/5f5f/8787"), (205, "rgb:ffff/5f5f/afaf"), (206, "rgb:ffff/5f5f/d7d7"), (207, "rgb:ffff/5f5f/ffff"), (208, "rgb:ffff/8787/0000"), (209, "rgb:ffff/8787/5f5f"), (210, "rgb:ffff/8787/8787"), (211, "rgb:ffff/8787/afaf"), (212, "rgb:ffff/8787/d7d7"), (213, "rgb:ffff/8787/ffff"), (214, "rgb:ffff/afaf/0000"), (215, "rgb:ffff/afaf/5f5f"), (216, "rgb:ffff/afaf/8787"), (217, "rgb:ffff/afaf/afaf"), (218, "rgb:ffff/afaf/d7d7"), (219, "rgb:ffff/afaf/ffff"), (220, "rgb:ffff/d7d7/0000"), (221, "rgb:ffff/d7d7/5f5f"), (222, "rgb:ffff/d7d7/8787"), (223, "rgb:ffff/d7d7/afaf"), (224, "rgb:ffff/d7d7/d7d7"), (225, "rgb:ffff/d7d7/ffff"), (226, "rgb:ffff/ffff/0000"), (227, "rgb:ffff/ffff/5f5f"), (228, "rgb:ffff/ffff/8787"), (229, "rgb:ffff/ffff/afaf"), (230, "rgb:ffff/ffff/d7d7"), (231, "rgb:ffff/ffff/ffff"), (232, "rgb:0808/0808/0808"), (233, "rgb:1212/1212/1212"), (234, "rgb:1c1c/1c1c/1c1c"), (235, "rgb:2626/2626/2626"), (236, "rgb:3030/3030/3030"), (237, "rgb:3a3a/3a3a/3a3a"), (238, "rgb:4444/4444/4444"), (239, "rgb:4e4e/4e4e/4e4e"), (240, "rgb:5858/5858/5858"), (241, "rgb:6262/6262/6262"), (242, "rgb:6c6c/6c6c/6c6c"), (243, "rgb:7676/7676/7676"), (244, "rgb:8080/8080/8080"), (245, "rgb:8a8a/8a8a/8a8a"), (246, "rgb:9494/9494/9494"), (247, "rgb:9e9e/9e9e/9e9e"), (248, "rgb:a8a8/a8a8/a8a8"), (249, "rgb:b2b2/b2b2/b2b2"), (250, "rgb:bcbc/bcbc/bcbc"), (251, "rgb:c6c6/c6c6/c6c6"), (252, "rgb:d0d0/d0d0/d0d0"), (253, "rgb:dada/dada/dada"), (254, "rgb:e4e4/e4e4/e4e4"), (255, "rgb:eeee/eeee/eeee")])] diff --git a/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap b/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap new file mode 100644 index 00000000..c58a2f5a --- /dev/null +++ b/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap @@ -0,0 +1,6 @@ +--- +source: zellij-client/src/./unit/stdin_tests.rs +assertion_line: 399 +expression: "*format!(\"{:?}\", events_sent_to_server.lock().unwrap())" +--- +[TerminalPixelDimensions(PixelDimensions { text_area_size: Some(SizeInPixels { height: 401, width: 1566 }), character_cell_size: None }), TerminalPixelDimensions(PixelDimensions { text_area_size: None, character_cell_size: Some(SizeInPixels { height: 21, width: 8 }) }), BackgroundColor("rgb:0000/0000/0000"), ForegroundColor("rgb:ffff/ffff/ffff"), ColorRegisters([(0, "rgb:0000/0000/0000"), (1, "rgb:cdcd/0000/0000"), (2, "rgb:0000/cdcd/0000"), (3, "rgb:cdcd/cdcd/0000"), (4, "rgb:0000/0000/eeee"), (5, "rgb:cdcd/0000/cdcd"), (6, "rgb:0000/cdcd/cdcd"), (7, "rgb:e5e5/e5e5/e5e5"), (8, "rgb:7f7f/7f7f/7f7f"), (9, "rgb:ffff/0000/0000"), (10, "rgb:0000/ffff/0000"), (11, "rgb:ffff/ffff/0000"), (12, "rgb:5c5c/5c5c/ffff"), (13, "rgb:ffff/0000/ffff"), (14, "rgb:0000/ffff/ffff"), (15, "rgb:ffff/ffff/ffff"), (16, "rgb:0000/0000/0000"), (17, "rgb:0000/0000/5f5f"), (18, "rgb:0000/0000/8787"), (19, "rgb:0000/0000/afaf"), (20, "rgb:0000/0000/d7d7"), (21, "rgb:0000/0000/ffff"), (22, "rgb:0000/5f5f/0000"), (23, "rgb:0000/5f5f/5f5f"), (24, "rgb:0000/5f5f/8787"), (25, "rgb:0000/5f5f/afaf"), (26, "rgb:0000/5f5f/d7d7"), (27, "rgb:0000/5f5f/ffff"), (28, "rgb:0000/8787/0000"), (29, "rgb:0000/8787/5f5f"), (30, "rgb:0000/8787/8787"), (31, "rgb:0000/8787/afaf"), (32, "rgb:0000/8787/d7d7"), (33, "rgb:0000/8787/ffff"), (34, "rgb:0000/afaf/0000"), (35, "rgb:0000/afaf/5f5f"), (36, "rgb:0000/afaf/8787"), (37, "rgb:0000/afaf/afaf"), (38, "rgb:0000/afaf/d7d7"), (39, "rgb:0000/afaf/ffff"), (40, "rgb:0000/d7d7/0000"), (41, "rgb:0000/d7d7/5f5f"), (42, "rgb:0000/d7d7/8787"), (43, "rgb:0000/d7d7/afaf"), (44, "rgb:0000/d7d7/d7d7"), (45, "rgb:0000/d7d7/ffff"), (46, "rgb:0000/ffff/0000"), (47, "rgb:0000/ffff/5f5f"), (48, "rgb:0000/ffff/8787"), (49, "rgb:0000/ffff/afaf"), (50, "rgb:0000/ffff/d7d7"), (51, "rgb:0000/ffff/ffff"), (52, "rgb:5f5f/0000/0000"), (53, "rgb:5f5f/0000/5f5f"), (54, "rgb:5f5f/0000/8787"), (55, "rgb:5f5f/0000/afaf"), (56, "rgb:5f5f/0000/d7d7"), (57, "rgb:5f5f/0000/ffff"), (58, "rgb:5f5f/5f5f/0000"), (59, "rgb:5f5f/5f5f/5f5f"), (60, "rgb:5f5f/5f5f/8787"), (61, "rgb:5f5f/5f5f/afaf"), (62, "rgb:5f5f/5f5f/d7d7"), (63, "rgb:5f5f/5f5f/ffff"), (64, "rgb:5f5f/8787/0000"), (65, "rgb:5f5f/8787/5f5f"), (66, "rgb:5f5f/8787/8787"), (67, "rgb:5f5f/8787/afaf"), (68, "rgb:5f5f/8787/d7d7"), (69, "rgb:5f5f/8787/ffff"), (70, "rgb:5f5f/afaf/0000"), (71, "rgb:5f5f/afaf/5f5f"), (72, "rgb:5f5f/afaf/8787"), (73, "rgb:5f5f/afaf/afaf"), (74, "rgb:5f5f/afaf/d7d7"), (75, "rgb:5f5f/afaf/ffff"), (76, "rgb:5f5f/d7d7/0000"), (77, "rgb:5f5f/d7d7/5f5f"), (78, "rgb:5f5f/d7d7/8787"), (79, "rgb:5f5f/d7d7/afaf"), (80, "rgb:5f5f/d7d7/d7d7"), (81, "rgb:5f5f/d7d7/ffff"), (82, "rgb:5f5f/ffff/0000"), (83, "rgb:5f5f/ffff/5f5f"), (84, "rgb:5f5f/ffff/8787"), (85, "rgb:5f5f/ffff/afaf"), (86, "rgb:5f5f/ffff/d7d7"), (87, "rgb:5f5f/ffff/ffff"), (88, "rgb:8787/0000/0000"), (89, "rgb:8787/0000/5f5f"), (90, "rgb:8787/0000/8787"), (91, "rgb:8787/0000/afaf"), (92, "rgb:8787/0000/d7d7"), (93, "rgb:8787/0000/ffff"), (94, "rgb:8787/5f5f/0000"), (95, "rgb:8787/5f5f/5f5f"), (96, "rgb:8787/5f5f/8787"), (97, "rgb:8787/5f5f/afaf"), (98, "rgb:8787/5f5f/d7d7"), (99, "rgb:8787/5f5f/ffff"), (100, "rgb:8787/8787/0000"), (101, "rgb:8787/8787/5f5f"), (102, "rgb:8787/8787/8787"), (103, "rgb:8787/8787/afaf"), (104, "rgb:8787/8787/d7d7"), (105, "rgb:8787/8787/ffff"), (106, "rgb:8787/afaf/0000"), (107, "rgb:8787/afaf/5f5f"), (108, "rgb:8787/afaf/8787"), (109, "rgb:8787/afaf/afaf"), (110, "rgb:8787/afaf/d7d7"), (111, "rgb:8787/afaf/ffff"), (112, "rgb:8787/d7d7/0000"), (113, "rgb:8787/d7d7/5f5f"), (114, "rgb:8787/d7d7/8787"), (115, "rgb:8787/d7d7/afaf"), (116, "rgb:8787/d7d7/d7d7"), (117, "rgb:8787/d7d7/ffff"), (118, "rgb:8787/ffff/0000"), (119, "rgb:8787/ffff/5f5f"), (120, "rgb:8787/ffff/8787"), (121, "rgb:8787/ffff/afaf"), (122, "rgb:8787/ffff/d7d7"), (123, "rgb:8787/ffff/ffff"), (124, "rgb:afaf/0000/0000"), (125, "rgb:afaf/0000/5f5f"), (126, "rgb:afaf/0000/8787"), (127, "rgb:afaf/0000/afaf"), (128, "rgb:afaf/0000/d7d7"), (129, "rgb:afaf/0000/ffff"), (130, "rgb:afaf/5f5f/0000"), (131, "rgb:afaf/5f5f/5f5f"), (132, "rgb:afaf/5f5f/8787"), (133, "rgb:afaf/5f5f/afaf"), (134, "rgb:afaf/5f5f/d7d7"), (135, "rgb:afaf/5f5f/ffff"), (136, "rgb:afaf/8787/0000"), (137, "rgb:afaf/8787/5f5f"), (138, "rgb:afaf/8787/8787"), (139, "rgb:afaf/8787/afaf"), (140, "rgb:afaf/8787/d7d7"), (141, "rgb:afaf/8787/ffff"), (142, "rgb:afaf/afaf/0000"), (143, "rgb:afaf/afaf/5f5f"), (144, "rgb:afaf/afaf/8787"), (145, "rgb:afaf/afaf/afaf"), (146, "rgb:afaf/afaf/d7d7"), (147, "rgb:afaf/afaf/ffff"), (148, "rgb:afaf/d7d7/0000"), (149, "rgb:afaf/d7d7/5f5f"), (150, "rgb:afaf/d7d7/8787"), (151, "rgb:afaf/d7d7/afaf"), (152, "rgb:afaf/d7d7/d7d7"), (153, "rgb:afaf/d7d7/ffff"), (154, "rgb:afaf/ffff/0000"), (155, "rgb:afaf/ffff/5f5f"), (156, "rgb:afaf/ffff/8787"), (157, "rgb:afaf/ffff/afaf"), (158, "rgb:afaf/ffff/d7d7"), (159, "rgb:afaf/ffff/ffff"), (160, "rgb:d7d7/0000/0000"), (161, "rgb:d7d7/0000/5f5f"), (162, "rgb:d7d7/0000/8787"), (163, "rgb:d7d7/0000/afaf"), (164, "rgb:d7d7/0000/d7d7"), (165, "rgb:d7d7/0000/ffff"), (166, "rgb:d7d7/5f5f/0000"), (167, "rgb:d7d7/5f5f/5f5f"), (168, "rgb:d7d7/5f5f/8787"), (169, "rgb:d7d7/5f5f/afaf"), (170, "rgb:d7d7/5f5f/d7d7"), (171, "rgb:d7d7/5f5f/ffff"), (172, "rgb:d7d7/8787/0000"), (173, "rgb:d7d7/8787/5f5f"), (174, "rgb:d7d7/8787/8787"), (175, "rgb:d7d7/8787/afaf"), (176, "rgb:d7d7/8787/d7d7"), (177, "rgb:d7d7/8787/ffff"), (178, "rgb:d7d7/afaf/0000"), (179, "rgb:d7d7/afaf/5f5f"), (180, "rgb:d7d7/afaf/8787"), (181, "rgb:d7d7/afaf/afaf"), (182, "rgb:d7d7/afaf/d7d7"), (183, "rgb:d7d7/afaf/ffff"), (184, "rgb:d7d7/d7d7/0000"), (185, "rgb:d7d7/d7d7/5f5f"), (186, "rgb:d7d7/d7d7/8787"), (187, "rgb:d7d7/d7d7/afaf"), (188, "rgb:d7d7/d7d7/d7d7"), (189, "rgb:d7d7/d7d7/ffff"), (190, "rgb:d7d7/ffff/0000"), (191, "rgb:d7d7/ffff/5f5f"), (192, "rgb:d7d7/ffff/8787"), (193, "rgb:d7d7/ffff/afaf"), (194, "rgb:d7d7/ffff/d7d7"), (195, "rgb:d7d7/ffff/ffff"), (196, "rgb:ffff/0000/0000"), (197, "rgb:ffff/0000/5f5f"), (198, "rgb:ffff/0000/8787"), (199, "rgb:ffff/0000/afaf"), (200, "rgb:ffff/0000/d7d7"), (201, "rgb:ffff/0000/ffff"), (202, "rgb:ffff/5f5f/0000"), (203, "rgb:ffff/5f5f/5f5f"), (204, "rgb:ffff/5f5f/8787"), (205, "rgb:ffff/5f5f/afaf"), (206, "rgb:ffff/5f5f/d7d7"), (207, "rgb:ffff/5f5f/ffff"), (208, "rgb:ffff/8787/0000"), (209, "rgb:ffff/8787/5f5f"), (210, "rgb:ffff/8787/8787"), (211, "rgb:ffff/8787/afaf"), (212, "rgb:ffff/8787/d7d7"), (213, "rgb:ffff/8787/ffff"), (214, "rgb:ffff/afaf/0000"), (215, "rgb:ffff/afaf/5f5f"), (216, "rgb:ffff/afaf/8787"), (217, "rgb:ffff/afaf/afaf"), (218, "rgb:ffff/afaf/d7d7"), (219, "rgb:ffff/afaf/ffff"), (220, "rgb:ffff/d7d7/0000"), (221, "rgb:ffff/d7d7/5f5f"), (222, "rgb:ffff/d7d7/8787"), (223, "rgb:ffff/d7d7/afaf"), (224, "rgb:ffff/d7d7/d7d7"), (225, "rgb:ffff/d7d7/ffff"), (226, "rgb:ffff/ffff/0000"), (227, "rgb:ffff/ffff/5f5f"), (228, "rgb:ffff/ffff/8787"), (229, "rgb:ffff/ffff/afaf"), (230, "rgb:ffff/ffff/d7d7"), (231, "rgb:ffff/ffff/ffff"), (232, "rgb:0808/0808/0808"), (233, "rgb:1212/1212/1212"), (234, "rgb:1c1c/1c1c/1c1c"), (235, "rgb:2626/2626/2626"), (236, "rgb:3030/3030/3030"), (237, "rgb:3a3a/3a3a/3a3a"), (238, "rgb:4444/4444/4444"), (239, "rgb:4e4e/4e4e/4e4e"), (240, "rgb:5858/5858/5858"), (241, "rgb:6262/6262/6262"), (242, "rgb:6c6c/6c6c/6c6c"), (243, "rgb:7676/7676/7676"), (244, "rgb:8080/8080/8080"), (245, "rgb:8a8a/8a8a/8a8a"), (246, "rgb:9494/9494/9494"), (247, "rgb:9e9e/9e9e/9e9e"), (248, "rgb:a8a8/a8a8/a8a8"), (249, "rgb:b2b2/b2b2/b2b2"), (250, "rgb:bcbc/bcbc/bcbc"), (251, "rgb:c6c6/c6c6/c6c6"), (252, "rgb:d0d0/d0d0/d0d0"), (253, "rgb:dada/dada/dada"), (254, "rgb:e4e4/e4e4/e4e4"), (255, "rgb:eeee/eeee/eeee")])] diff --git a/zellij-client/src/unit/stdin_tests.rs b/zellij-client/src/unit/stdin_tests.rs new file mode 100644 index 00000000..d51734ac --- /dev/null +++ b/zellij-client/src/unit/stdin_tests.rs @@ -0,0 +1,409 @@ +use super::input_loop; +use crate::stdin_ansi_parser::StdinAnsiParser; +use crate::stdin_loop; +use zellij_utils::data::{InputMode, Palette}; +use zellij_utils::input::actions::{Action, Direction}; +use zellij_utils::input::config::Config; +use zellij_utils::input::options::Options; +use zellij_utils::nix; +use zellij_utils::pane_size::Size; +use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers}; + +use crate::InputInstruction; +use crate::{ + os_input_output::{ClientOsApi, StdinPoller}, + ClientInstruction, CommandIsExecuting, +}; + +use ::insta::assert_snapshot; +use std::path::Path; + +use std::io; +use std::os::unix::io::RawFd; +use std::sync::{Arc, Mutex}; +use std::thread; +use zellij_utils::{ + errors::ErrorContext, + ipc::{ClientToServerMsg, ServerToClientMsg}, +}; + +use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext}; + +fn read_fixture(fixture_name: &str) -> Vec { + 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)) +} + +#[allow(unused)] +pub mod commands { + pub const QUIT: [u8; 1] = [17]; // ctrl-q + pub const ESC: [u8; 1] = [27]; + pub const ENTER: [u8; 1] = [10]; // char '\n' + + pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h + pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l + + pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p + pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n + pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p + pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d + pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r + pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f + pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x + pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j + pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k + pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h + pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l + + pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s + pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k + pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j + pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b + pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f + + pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r + pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j + pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k + pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h + pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l + + pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t + pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n + pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l + pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h + pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x + + pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~ + pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201 + pub const SLEEP: [u8; 0] = []; +} + +#[derive(Default, Clone)] +struct FakeStdoutWriter { + buffer: Arc>>, +} +impl FakeStdoutWriter { + pub fn new(buffer: Arc>>) -> Self { + FakeStdoutWriter { buffer } + } +} +impl io::Write for FakeStdoutWriter { + fn write(&mut self, buf: &[u8]) -> Result { + self.buffer.lock().unwrap().extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), io::Error> { + Ok(()) + } +} + +#[derive(Clone)] +struct FakeClientOsApi { + events_sent_to_server: Arc>>, + command_is_executing: Arc>, + stdout_buffer: Arc>>, + stdin_buffer: Vec, +} + +impl FakeClientOsApi { + pub fn new( + events_sent_to_server: Arc>>, + command_is_executing: CommandIsExecuting, + ) -> Self { + // while command_is_executing itself is implemented with an Arc, we have to have an + // Arc here because we need interior mutability, otherwise we'll have to change the + // ClientOsApi trait, and that will cause a lot of havoc + let command_is_executing = Arc::new(Mutex::new(command_is_executing)); + let stdout_buffer = Arc::new(Mutex::new(vec![])); + FakeClientOsApi { + events_sent_to_server, + command_is_executing, + stdout_buffer, + stdin_buffer: vec![], + } + } + pub fn with_stdin_buffer(mut self, stdin_buffer: Vec) -> Self { + self.stdin_buffer = stdin_buffer; + self + } + pub fn stdout_buffer(&self) -> Vec { + self.stdout_buffer.lock().unwrap().drain(..).collect() + } +} + +impl ClientOsApi for FakeClientOsApi { + fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size { + unimplemented!() + } + fn set_raw_mode(&mut self, _fd: RawFd) { + unimplemented!() + } + fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> { + unimplemented!() + } + fn get_stdout_writer(&self) -> Box { + let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone()); + Box::new(fake_stdout_writer) + } + fn get_stdin_reader(&self) -> Box { + unimplemented!() + } + fn read_from_stdin(&mut self) -> Vec { + self.stdin_buffer.drain(..).collect() + } + fn box_clone(&self) -> Box { + unimplemented!() + } + fn send_to_server(&self, msg: ClientToServerMsg) { + { + let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap(); + events_sent_to_server.push(msg); + } + { + let mut command_is_executing = self.command_is_executing.lock().unwrap(); + command_is_executing.unblock_input_thread(); + } + } + fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> { + unimplemented!() + } + fn handle_signals(&self, _sigwinch_cb: Box, _quit_cb: Box) { + unimplemented!() + } + fn connect_to_server(&self, _path: &Path) { + unimplemented!() + } + fn load_palette(&self) -> Palette { + unimplemented!() + } + fn enable_mouse(&self) {} + fn disable_mouse(&self) {} + fn stdin_poller(&self) -> StdinPoller { + unimplemented!() + } +} + +fn extract_actions_sent_to_server( + events_sent_to_server: Arc>>, +) -> Vec { + let events_sent_to_server = events_sent_to_server.lock().unwrap(); + events_sent_to_server.iter().fold(vec![], |mut acc, event| { + if let ClientToServerMsg::Action(action, None) = event { + acc.push(action.clone()); + } + acc + }) +} + +#[test] +pub fn quit_breaks_input_loop() { + let stdin_events = vec![( + commands::QUIT.to_vec(), + InputEvent::Key(KeyEvent { + key: KeyCode::Char('q'), + modifiers: Modifiers::CTRL, + }), + )]; + let events_sent_to_server = Arc::new(Mutex::new(vec![])); + let command_is_executing = CommandIsExecuting::new(); + let client_os_api = Box::new(FakeClientOsApi::new( + events_sent_to_server.clone(), + command_is_executing.clone(), + )); + let config = Config::from_default_assets().unwrap(); + let options = Options::default(); + + let (send_client_instructions, _receive_client_instructions): ChannelWithContext< + ClientInstruction, + > = channels::bounded(50); + let send_client_instructions = SenderWithContext::new(send_client_instructions); + + let (send_input_instructions, receive_input_instructions): ChannelWithContext< + InputInstruction, + > = channels::bounded(50); + let send_input_instructions = SenderWithContext::new(send_input_instructions); + for event in stdin_events { + send_input_instructions + .send(InputInstruction::KeyEvent(event.1, event.0)) + .unwrap(); + } + + let default_mode = InputMode::Normal; + input_loop( + client_os_api, + config, + options, + command_is_executing, + send_client_instructions, + default_mode, + receive_input_instructions, + ); + let expected_actions_sent_to_server = vec![Action::Quit]; + let received_actions = extract_actions_sent_to_server(events_sent_to_server); + assert_eq!( + expected_actions_sent_to_server, received_actions, + "All actions sent to server properly" + ); +} + +#[test] +pub fn move_focus_left_in_normal_mode() { + let stdin_events = vec![ + ( + commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(), + InputEvent::Key(KeyEvent { + key: KeyCode::Char('h'), + modifiers: Modifiers::ALT, + }), + ), + ( + commands::QUIT.to_vec(), + InputEvent::Key(KeyEvent { + key: KeyCode::Char('q'), + modifiers: Modifiers::CTRL, + }), + ), + ]; + + let events_sent_to_server = Arc::new(Mutex::new(vec![])); + let command_is_executing = CommandIsExecuting::new(); + let client_os_api = Box::new(FakeClientOsApi::new( + events_sent_to_server.clone(), + command_is_executing.clone(), + )); + let config = Config::from_default_assets().unwrap(); + let options = Options::default(); + + let (send_client_instructions, _receive_client_instructions): ChannelWithContext< + ClientInstruction, + > = channels::bounded(50); + let send_client_instructions = SenderWithContext::new(send_client_instructions); + + let (send_input_instructions, receive_input_instructions): ChannelWithContext< + InputInstruction, + > = channels::bounded(50); + let send_input_instructions = SenderWithContext::new(send_input_instructions); + for event in stdin_events { + send_input_instructions + .send(InputInstruction::KeyEvent(event.1, event.0)) + .unwrap(); + } + + let default_mode = InputMode::Normal; + input_loop( + client_os_api, + config, + options, + command_is_executing, + send_client_instructions, + default_mode, + receive_input_instructions, + ); + let expected_actions_sent_to_server = + vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit]; + let received_actions = extract_actions_sent_to_server(events_sent_to_server); + assert_eq!( + expected_actions_sent_to_server, received_actions, + "All actions sent to server properly" + ); +} + +#[test] +pub fn terminal_info_queried_from_terminal_emulator() { + let events_sent_to_server = Arc::new(Mutex::new(vec![])); + let command_is_executing = CommandIsExecuting::new(); + let client_os_api = FakeClientOsApi::new(events_sent_to_server, command_is_executing); + + let client_os_api_clone = client_os_api.clone(); + let (send_input_instructions, _receive_input_instructions): ChannelWithContext< + InputInstruction, + > = channels::bounded(50); + let send_input_instructions = SenderWithContext::new(send_input_instructions); + let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new())); + + let stdin_thread = thread::Builder::new() + .name("stdin_handler".to_string()) + .spawn({ + move || { + stdin_loop( + Box::new(client_os_api), + send_input_instructions, + stdin_ansi_parser, + ) + } + }); + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for initial query to be sent + + let extracted_stdout_buffer = client_os_api_clone.stdout_buffer(); + let mut expected_query = + String::from("\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}"); + for i in 0..256 { + expected_query.push_str(&format!("\u{1b}]4;{};?\u{1b}\u{5c}", i)); + } + assert_eq!( + String::from_utf8(extracted_stdout_buffer), + Ok(expected_query), + ); + drop(stdin_thread); +} + +#[test] +pub fn pixel_info_sent_to_server() { + let fake_stdin_buffer = read_fixture("terminal_emulator_startup_response"); + let events_sent_to_server = Arc::new(Mutex::new(vec![])); + let command_is_executing = CommandIsExecuting::new(); + let client_os_api = + FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()) + .with_stdin_buffer(fake_stdin_buffer); + let config = Config::from_default_assets().unwrap(); + let options = Options::default(); + + let (send_client_instructions, _receive_client_instructions): ChannelWithContext< + ClientInstruction, + > = channels::bounded(50); + let send_client_instructions = SenderWithContext::new(send_client_instructions); + + let (send_input_instructions, receive_input_instructions): ChannelWithContext< + InputInstruction, + > = channels::bounded(50); + let send_input_instructions = SenderWithContext::new(send_input_instructions); + let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new())); + let stdin_thread = thread::Builder::new() + .name("stdin_handler".to_string()) + .spawn({ + let client_os_api = client_os_api.clone(); + move || { + stdin_loop( + Box::new(client_os_api), + send_input_instructions, + stdin_ansi_parser, + ) + } + }); + + let default_mode = InputMode::Normal; + let input_thread = thread::Builder::new() + .name("input_handler".to_string()) + .spawn({ + move || { + input_loop( + Box::new(client_os_api), + config, + options, + command_is_executing, + send_client_instructions, + default_mode, + receive_input_instructions, + ) + } + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); // wait for initial query to be sent + assert_snapshot!(*format!("{:?}", events_sent_to_server.lock().unwrap())); + drop(stdin_thread); + drop(input_thread); +} diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index 4154571e..41fe4ffb 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -27,6 +27,9 @@ typetag = "0.1.7" chrono = "0.4.19" close_fds = "0.3.2" sysinfo = "0.22.5" +sixel-tokenizer = "0.1.0" +sixel-image = "0.1.0" +arrayvec = "0.7.2" uuid = { version = "0.8.2", features = ["serde", "v4"] } [dev-dependencies] diff --git a/zellij-server/src/output/mod.rs b/zellij-server/src/output/mod.rs index 6d801001..c13848ad 100644 --- a/zellij-server/src/output/mod.rs +++ b/zellij-server/src/output/mod.rs @@ -4,6 +4,7 @@ use crate::panes::selection::Selection; use crate::panes::Row; use crate::{ + panes::sixel::SixelImageStore, panes::terminal_character::{AnsiCode, CharacterStyles}, panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER}, ClientId, @@ -16,6 +17,7 @@ use std::{ str, }; use zellij_utils::pane_size::PaneGeom; +use zellij_utils::pane_size::SizeInPixels; fn vte_goto_instruction(x_coords: usize, y_coords: usize, vte_output: &mut String) { write!( @@ -54,7 +56,6 @@ fn write_changed_styles( if let Some(new_styles) = character_styles.update_and_return_diff(¤t_character_styles, chunk_changed_colors) { - // if let Some(osc8_link) = link_handler.as_ref().and_then(|l_h| l_h.borrow().output_osc8(new_styles.link_anchor)) { if let Some(osc8_link) = link_handler.and_then(|l_h| l_h.output_osc8(new_styles.link_anchor)) { @@ -65,11 +66,14 @@ fn write_changed_styles( } } -fn serialize_character_chunks( +fn serialize_chunks( character_chunks: Vec, + sixel_chunks: Option<&Vec>, link_handler: Option<&mut Rc>>, + sixel_image_store: &mut SixelImageStore, ) -> String { - let mut vte_output = String::new(); // TODO: preallocate character_chunks.len()? + let mut vte_output = String::new(); + let mut sixel_vte: Option = None; let link_handler = link_handler.map(|l_h| l_h.borrow()); for character_chunk in character_chunks { let chunk_selection_and_background_color = character_chunk.selection_and_background_color(); @@ -96,6 +100,32 @@ fn serialize_character_chunks( } character_styles.clear(); } + if let Some(sixel_chunks) = sixel_chunks { + for sixel_chunk in sixel_chunks { + let serialized_sixel_image = sixel_image_store.serialize_image( + sixel_chunk.sixel_image_id, + sixel_chunk.sixel_image_pixel_x, + sixel_chunk.sixel_image_pixel_y, + sixel_chunk.sixel_image_pixel_width, + sixel_chunk.sixel_image_pixel_height, + ); + if let Some(serialized_sixel_image) = serialized_sixel_image { + let sixel_vte = sixel_vte.get_or_insert_with(String::new); + vte_goto_instruction(sixel_chunk.cell_x, sixel_chunk.cell_y, sixel_vte); + sixel_vte.push_str(&serialized_sixel_image); + } + } + } + if let Some(ref sixel_vte) = sixel_vte { + // we do this at the end because of the implied z-index, + // images should be above text unless the text was explicitly inserted after them (the + // latter being a case we handle in our own internal state and not in the output) + let save_cursor_position = "\u{1b}[s"; + let restore_cursor_position = "\u{1b}[u"; + vte_output.push_str(save_cursor_position); + vte_output.push_str(sixel_vte); + vte_output.push_str(restore_cursor_position); + } vte_output } @@ -148,11 +178,24 @@ pub struct Output { pre_vte_instructions: HashMap>, post_vte_instructions: HashMap>, client_character_chunks: HashMap>, + sixel_chunks: HashMap>, link_handler: Option>>, + sixel_image_store: Rc>, + character_cell_size: Rc>>, floating_panes_stack: Option, } impl Output { + pub fn new( + sixel_image_store: Rc>, + character_cell_size: Rc>>, + ) -> Self { + Output { + sixel_image_store, + character_cell_size, + ..Default::default() + } + } pub fn add_clients( &mut self, client_ids: &HashSet, @@ -240,6 +283,48 @@ impl Output { .or_insert_with(Vec::new); entry.push(String::from(vte_instruction)); } + pub fn add_sixel_image_chunks_to_client( + &mut self, + client_id: ClientId, + sixel_image_chunks: Vec, + z_index: Option, + ) { + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let mut sixel_chunks = if let Some(floating_panes_stack) = &self.floating_panes_stack { + floating_panes_stack.visible_sixel_image_chunks( + sixel_image_chunks, + z_index, + &character_cell_size, + ) + } else { + sixel_image_chunks + }; + let entry = self.sixel_chunks.entry(client_id).or_insert_with(Vec::new); + entry.append(&mut sixel_chunks); + } + } + pub fn add_sixel_image_chunks_to_multiple_clients( + &mut self, + sixel_image_chunks: Vec, + client_ids: impl Iterator, + z_index: Option, + ) { + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let sixel_chunks = if let Some(floating_panes_stack) = &self.floating_panes_stack { + floating_panes_stack.visible_sixel_image_chunks( + sixel_image_chunks, + z_index, + &character_cell_size, + ) + } else { + sixel_image_chunks + }; + for client_id in client_ids { + let entry = self.sixel_chunks.entry(client_id).or_insert_with(Vec::new); + entry.append(&mut sixel_chunks.clone()); + } + } + } pub fn serialize(&mut self) -> HashMap { let mut serialized_render_instructions = HashMap::new(); @@ -256,9 +341,11 @@ impl Output { } // append the actual vte - client_serialized_render_instructions.push_str(&serialize_character_chunks( + client_serialized_render_instructions.push_str(&serialize_chunks( client_character_chunks, + self.sixel_chunks.get(&client_id), self.link_handler.as_mut(), + &mut self.sixel_image_store.borrow_mut(), )); // TODO: less allocations? // append post-vte instructions for this client @@ -319,6 +406,26 @@ impl FloatingPanesStack { } visible_chunks } + pub fn visible_sixel_image_chunks( + &self, + mut sixel_image_chunks: Vec, + z_index: Option, + character_cell_size: &SizeInPixels, + ) -> Vec { + let z_index = z_index.unwrap_or(0); + let mut chunks_to_check: Vec = sixel_image_chunks.drain(..).collect(); + let panes_to_check = self.layers.iter().skip(z_index); + for pane_geom in panes_to_check { + let chunks_to_check_against_this_pane: Vec = + chunks_to_check.drain(..).collect(); + for s_chunk in chunks_to_check_against_this_pane { + let mut uncovered_chunks = + self.remove_covered_sixel_parts(pane_geom, &s_chunk, character_cell_size); + chunks_to_check.append(&mut uncovered_chunks); + } + } + chunks_to_check + } fn remove_covered_parts( &self, pane_geom: &PaneGeom, @@ -368,6 +475,165 @@ impl FloatingPanesStack { }; None } + fn remove_covered_sixel_parts( + &self, + pane_geom: &PaneGeom, + s_chunk: &SixelImageChunk, + character_cell_size: &SizeInPixels, + ) -> Vec { + // round these up to the nearest cell edge + let rounded_sixel_image_pixel_height = + if s_chunk.sixel_image_pixel_height % character_cell_size.height > 0 { + let modulus = s_chunk.sixel_image_pixel_height % character_cell_size.height; + s_chunk.sixel_image_pixel_height + (character_cell_size.height - modulus) + } else { + s_chunk.sixel_image_pixel_height + }; + let rounded_sixel_image_pixel_width = + if s_chunk.sixel_image_pixel_width % character_cell_size.width > 0 { + let modulus = s_chunk.sixel_image_pixel_width % character_cell_size.width; + s_chunk.sixel_image_pixel_width + (character_cell_size.width - modulus) + } else { + s_chunk.sixel_image_pixel_width + }; + + let pane_top_edge = pane_geom.y * character_cell_size.height; + let pane_left_edge = pane_geom.x * character_cell_size.width; + let pane_bottom_edge = (pane_geom.y + pane_geom.rows.as_usize().saturating_sub(1)) + * character_cell_size.height; + let pane_right_edge = + (pane_geom.x + pane_geom.cols.as_usize().saturating_sub(1)) * character_cell_size.width; + let s_chunk_top_edge = s_chunk.cell_y * character_cell_size.height; + let s_chunk_bottom_edge = s_chunk_top_edge + rounded_sixel_image_pixel_height; + let s_chunk_left_edge = s_chunk.cell_x * character_cell_size.width; + let s_chunk_right_edge = s_chunk_left_edge + rounded_sixel_image_pixel_width; + + let mut uncovered_chunks = vec![]; + let pane_covers_chunk_completely = pane_top_edge <= s_chunk_top_edge + && pane_bottom_edge >= s_chunk_bottom_edge + && pane_left_edge <= s_chunk_left_edge + && pane_right_edge >= s_chunk_right_edge; + let pane_intersects_with_chunk_vertically = (pane_left_edge >= s_chunk_left_edge + && pane_left_edge <= s_chunk_right_edge) + || (pane_right_edge >= s_chunk_left_edge && pane_right_edge <= s_chunk_right_edge) + || (pane_left_edge <= s_chunk_left_edge && pane_right_edge >= s_chunk_right_edge); + let pane_intersects_with_chunk_horizontally = (pane_top_edge >= s_chunk_top_edge + && pane_top_edge <= s_chunk_bottom_edge) + || (pane_bottom_edge >= s_chunk_top_edge && pane_bottom_edge <= s_chunk_bottom_edge) + || (pane_top_edge <= s_chunk_top_edge && pane_bottom_edge >= s_chunk_bottom_edge); + if pane_covers_chunk_completely { + return uncovered_chunks; + } + if pane_top_edge >= s_chunk_top_edge + && pane_top_edge <= s_chunk_bottom_edge + && pane_intersects_with_chunk_vertically + { + // pane covers image bottom + let top_image_chunk = SixelImageChunk { + cell_x: s_chunk.cell_x, + cell_y: s_chunk.cell_y, + sixel_image_pixel_x: s_chunk.sixel_image_pixel_x, + sixel_image_pixel_y: s_chunk.sixel_image_pixel_y, + sixel_image_pixel_width: rounded_sixel_image_pixel_width, + sixel_image_pixel_height: pane_top_edge - s_chunk_top_edge, + sixel_image_id: s_chunk.sixel_image_id, + }; + uncovered_chunks.push(top_image_chunk); + } + if pane_bottom_edge <= s_chunk_bottom_edge + && pane_bottom_edge >= s_chunk_top_edge + && pane_intersects_with_chunk_vertically + { + // pane covers image top + let bottom_image_chunk = SixelImageChunk { + cell_x: s_chunk.cell_x, + cell_y: (pane_bottom_edge / character_cell_size.height) + 1, + sixel_image_pixel_x: s_chunk.sixel_image_pixel_x, + sixel_image_pixel_y: s_chunk.sixel_image_pixel_y + + (pane_bottom_edge - s_chunk_top_edge) + + character_cell_size.height, + sixel_image_pixel_width: rounded_sixel_image_pixel_width, + sixel_image_pixel_height: (rounded_sixel_image_pixel_height + - (pane_bottom_edge - s_chunk_top_edge)) + .saturating_sub(character_cell_size.height), + sixel_image_id: s_chunk.sixel_image_id, + }; + uncovered_chunks.push(bottom_image_chunk); + } + if pane_left_edge >= s_chunk_left_edge + && pane_left_edge <= s_chunk_right_edge + && pane_intersects_with_chunk_horizontally + { + // pane covers image right + let sixel_image_pixel_y = if s_chunk_top_edge < pane_top_edge { + s_chunk.sixel_image_pixel_y + (pane_top_edge - s_chunk_top_edge) + } else { + s_chunk.sixel_image_pixel_y + }; + let max_image_height = if s_chunk_top_edge < pane_top_edge { + rounded_sixel_image_pixel_height.saturating_sub(pane_top_edge - s_chunk_top_edge) + } else { + rounded_sixel_image_pixel_height + }; + let left_image_chunk = SixelImageChunk { + cell_x: s_chunk.cell_x, + // if the pane_top_edge is lower than the image, we want to start there, because we + // already cut that part above when checking if the pane covered the chunk bottom + cell_y: std::cmp::max(s_chunk.cell_y, pane_top_edge / character_cell_size.height), + sixel_image_pixel_x: s_chunk.sixel_image_pixel_x, + sixel_image_pixel_y, + sixel_image_pixel_width: rounded_sixel_image_pixel_width + .saturating_sub(s_chunk_right_edge.saturating_sub(pane_left_edge)), + sixel_image_pixel_height: std::cmp::min( + pane_bottom_edge - pane_top_edge + character_cell_size.height, + max_image_height, + ), + sixel_image_id: s_chunk.sixel_image_id, + }; + uncovered_chunks.push(left_image_chunk); + } + if pane_right_edge <= s_chunk_right_edge + && pane_right_edge >= s_chunk_left_edge + && pane_intersects_with_chunk_horizontally + { + // pane covers image left + let sixel_image_pixel_y = if s_chunk_top_edge < pane_top_edge { + s_chunk.sixel_image_pixel_y + (pane_top_edge - s_chunk_top_edge) + } else { + s_chunk.sixel_image_pixel_y + }; + let max_image_height = if s_chunk_top_edge < pane_top_edge { + rounded_sixel_image_pixel_height.saturating_sub(pane_top_edge - s_chunk_top_edge) + } else { + rounded_sixel_image_pixel_height + }; + let sixel_image_pixel_x = s_chunk.sixel_image_pixel_x + + (pane_right_edge - s_chunk_left_edge) + + character_cell_size.width; + let right_image_chunk = SixelImageChunk { + cell_x: (pane_right_edge / character_cell_size.width) + 1, + // if the pane_top_edge is lower than the image, we want to start there, because we + // already cut that part above when checking if the pane covered the chunk bottom + cell_y: std::cmp::max(s_chunk.cell_y, pane_top_edge / character_cell_size.height), + sixel_image_pixel_x, + sixel_image_pixel_y, + sixel_image_pixel_width: (rounded_sixel_image_pixel_width + .saturating_sub(pane_right_edge - s_chunk_left_edge)) + .saturating_sub(character_cell_size.width), + sixel_image_pixel_height: std::cmp::min( + pane_bottom_edge - pane_top_edge + character_cell_size.height, + max_image_height, + ), + sixel_image_id: s_chunk.sixel_image_id, + }; + uncovered_chunks.push(right_image_chunk); + } + if uncovered_chunks.is_empty() { + // the pane doesn't cover the chunk at all, so we return it as is + uncovered_chunks.push(*s_chunk); + } + uncovered_chunks + } } #[derive(Debug, Clone, Default)] @@ -379,6 +645,17 @@ pub struct CharacterChunk { selection_and_background_color: Option<(Selection, AnsiCode)>, } +#[derive(Debug, Clone, Copy, Default)] +pub struct SixelImageChunk { + pub cell_x: usize, + pub cell_y: usize, + pub sixel_image_pixel_x: usize, + pub sixel_image_pixel_y: usize, + pub sixel_image_pixel_width: usize, + pub sixel_image_pixel_height: usize, + pub sixel_image_id: usize, +} + impl CharacterChunk { pub fn new(terminal_characters: Vec, x: usize, y: usize) -> Self { CharacterChunk { @@ -480,8 +757,8 @@ impl CharacterChunk { #[derive(Clone, Debug)] pub struct OutputBuffer { - changed_lines: Vec, // line index - should_update_all_lines: bool, + pub changed_lines: Vec, // line index + pub should_update_all_lines: bool, } impl Default for OutputBuffer { @@ -520,6 +797,7 @@ impl OutputBuffer { for line_index in 0..viewport_height { let terminal_characters = self.extract_line_from_viewport(line_index, viewport, viewport_width); + let x = x_offset; // right now we only buffer full lines as this doesn't seem to have a huge impact on performance, but the infra is here if we want to change this let y = line_index + y_offset; changed_chunks.push(CharacterChunk::new(terminal_characters, x, y)); @@ -568,4 +846,42 @@ impl OutputBuffer { }, } } + pub fn changed_rects_in_viewport(&self, viewport_height: usize) -> HashMap { + // group the changed lines into "changed_rects", which indicate where the line starts (the + // hashmap key) and how many lines are in there (its value) + let mut changed_rects: HashMap = HashMap::new(); // + let mut last_changed_line_index: Option = None; + let mut changed_line_count = 0; + let mut add_changed_line = |line_index| match last_changed_line_index.as_mut() { + Some(changed_line_index) => { + if *changed_line_index + changed_line_count == line_index { + changed_line_count += 1 + } else { + changed_rects.insert(*changed_line_index, changed_line_count); + last_changed_line_index = Some(line_index); + changed_line_count = 1; + } + }, + None => { + last_changed_line_index = Some(line_index); + changed_line_count = 1; + }, + }; + + // TODO: move this whole thing to output_buffer + if self.should_update_all_lines { + // for line_index in 0..self.viewport.len() { + for line_index in 0..viewport_height { + add_changed_line(line_index); + } + } else { + for line_index in self.changed_lines.iter().copied() { + add_changed_line(line_index); + } + } + if let Some(changed_line_index) = last_changed_line_index { + changed_rects.insert(changed_line_index, changed_line_count); + } + changed_rects + } } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 23df7948..8c6e3e09 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1,4 +1,6 @@ +use super::sixel::{PixelRect, SixelGrid, SixelImageStore}; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use unicode_width::UnicodeWidthChar; use zellij_utils::regex::Regex; @@ -24,7 +26,7 @@ pub const MAX_TITLE_STACK_SIZE: usize = 1000; use vte::{Params, Perform}; use zellij_utils::{consts::VERSION, shared::version_number}; -use crate::output::{CharacterChunk, OutputBuffer}; +use crate::output::{CharacterChunk, OutputBuffer, SixelImageChunk}; use crate::panes::alacritty_functions::{parse_number, xparse_color}; use crate::panes::link_handler::LinkHandler; use crate::panes::selection::Selection; @@ -112,6 +114,7 @@ fn get_top_canonical_row_and_wraps(rows: &mut Vec) -> Vec { fn transfer_rows_from_lines_above_to_viewport( lines_above: &mut VecDeque, viewport: &mut Vec, + sixel_grid: &mut SixelGrid, count: usize, max_viewport_width: usize, ) -> usize { @@ -143,7 +146,7 @@ fn transfer_rows_from_lines_above_to_viewport( } if !next_lines.is_empty() { let excess_row = Row::from_rows(next_lines, 0); - bounded_push(lines_above, excess_row); + bounded_push(lines_above, sixel_grid, excess_row); } match usize::try_from(lines_added_to_viewport) { Ok(n) => n, @@ -154,6 +157,7 @@ fn transfer_rows_from_lines_above_to_viewport( fn transfer_rows_from_viewport_to_lines_above( viewport: &mut Vec, lines_above: &mut VecDeque, + sixel_grid: &mut SixelGrid, count: usize, max_viewport_width: usize, ) -> isize { @@ -176,7 +180,7 @@ fn transfer_rows_from_viewport_to_lines_above( break; // no more rows } } - let dropped_line_width = bounded_push(lines_above, next_lines.remove(0)); + let dropped_line_width = bounded_push(lines_above, sixel_grid, next_lines.remove(0)); if let Some(width) = dropped_line_width { transferred_rows_count -= calculate_row_display_height(width, max_viewport_width) as isize; @@ -232,11 +236,12 @@ fn transfer_rows_from_lines_below_to_viewport( } } -fn bounded_push(vec: &mut VecDeque, value: Row) -> Option { +fn bounded_push(vec: &mut VecDeque, sixel_grid: &mut SixelGrid, value: Row) -> Option { let mut dropped_line_width = None; if vec.len() >= *SCROLL_BUFFER_SIZE.get().unwrap() { let line = vec.pop_front(); if let Some(line) = line { + sixel_grid.offset_grid_top(); dropped_line_width = Some(line.width()); } } @@ -298,7 +303,7 @@ pub struct Grid { viewport: Vec, lines_below: Vec, horizontal_tabstops: BTreeSet, - alternate_lines_above_viewport_and_cursor: Option<(VecDeque, Vec, Cursor)>, + alternate_screen_state: Option, cursor: Cursor, saved_cursor_position: Option, // FIXME: change scroll_region to be (usize, usize) - where the top line is always the first @@ -309,14 +314,17 @@ pub struct Grid { active_charset: CharsetIndex, preceding_char: Option, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, output_buffer: OutputBuffer, title_stack: Vec, character_cell_size: Rc>>, + sixel_grid: SixelGrid, pub changed_colors: Option<[Option; 256]>, pub should_render: bool, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub bracketed_paste_mode: bool, // when set, paste instructions to the terminal should be escaped with a special sequence pub erasure_mode: bool, // ERM + pub sixel_scrolling: bool, // DECSDM pub insert_mode: bool, pub disable_linewrap: bool, pub clear_viewport_before_rendering: bool, @@ -334,7 +342,41 @@ pub struct Grid { impl Debug for Grid { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for (i, row) in self.viewport.iter().enumerate() { + let mut buffer: Vec = self.viewport.clone(); + // pad buffer + for _ in buffer.len()..self.height { + buffer.push(Row::new(self.width).canonical()); + } + + // display sixel placeholder + let sixel_indication_character = |x| { + let sixel_indication_word = "Sixel"; + sixel_indication_word + .chars() + .nth(x % sixel_indication_word.len()) + .unwrap() + }; + for image_coordinates in self + .sixel_grid + .image_cell_coordinates_in_viewport(self.height, self.lines_above.len()) + { + let (image_top_edge, image_bottom_edge, image_left_edge, image_right_edge) = + image_coordinates; + for y in image_top_edge..image_bottom_edge { + let row = buffer.get_mut(y).unwrap(); + for x in image_left_edge..image_right_edge { + let fake_sixel_terminal_character = TerminalCharacter { + character: sixel_indication_character(x), + width: 1, + styles: Default::default(), + }; + row.add_character_at(fake_sixel_terminal_character, x); + } + } + } + + // display terminal characters with stripped styles + for (i, row) in buffer.iter().enumerate() { if row.is_canonical { writeln!(f, "{:02?} (C): {:?}", i, row)?; } else { @@ -350,9 +392,12 @@ impl Grid { rows: usize, columns: usize, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, link_handler: Rc>, character_cell_size: Rc>>, + sixel_image_store: Rc>, ) -> Self { + let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store); Grid { lines_above: VecDeque::with_capacity( // .get_or_init() is used instead of .get().unwrap() to prevent @@ -372,13 +417,15 @@ impl Grid { cursor_key_mode: false, bracketed_paste_mode: false, erasure_mode: false, + sixel_scrolling: false, insert_mode: false, disable_linewrap: false, - alternate_lines_above_viewport_and_cursor: None, + alternate_screen_state: None, clear_viewport_before_rendering: false, active_charset: Default::default(), pending_messages_to_pty: vec![], terminal_emulator_colors, + terminal_emulator_color_codes, output_buffer: Default::default(), selection: Default::default(), title_stack: vec![], @@ -390,6 +437,7 @@ impl Grid { scrollback_buffer_lines: 0, mouse_mode: false, character_cell_size, + sixel_grid, } } pub fn render_full_viewport(&mut self) { @@ -534,6 +582,7 @@ impl Grid { let transferred_rows_height = transfer_rows_from_lines_above_to_viewport( &mut self.lines_above, &mut self.viewport, + &mut self.sixel_grid, 1, self.width, ); @@ -560,7 +609,8 @@ impl Grid { last_line_above }; - let dropped_line_width = bounded_push(&mut self.lines_above, line_to_push_up); + let dropped_line_width = + bounded_push(&mut self.lines_above, &mut self.sixel_grid, line_to_push_up); if let Some(width) = dropped_line_width { let dropped_line_height = calculate_row_display_height(width, self.width); @@ -607,7 +657,8 @@ impl Grid { return; } self.selection.reset(); - if new_columns != self.width && self.alternate_lines_above_viewport_and_cursor.is_none() { + self.sixel_grid.character_cell_size_possibly_changed(); + if new_columns != self.width && self.alternate_screen_state.is_none() { self.horizontal_tabstops = create_horizontal_tabstops(new_columns); let mut cursor_canonical_line_index = self.cursor_canonical_line_index(); let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line(); @@ -697,6 +748,7 @@ impl Grid { transfer_rows_from_lines_above_to_viewport( &mut self.lines_above, &mut self.viewport, + &mut self.sixel_grid, row_count_to_transfer, new_columns, ); @@ -713,6 +765,7 @@ impl Grid { transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, + &mut self.sixel_grid, row_count_to_transfer, new_columns, ); @@ -725,9 +778,7 @@ impl Grid { saved_cursor_position.y = new_cursor_y; saved_cursor_position.x = new_cursor_x; }; - } else if new_columns != self.width - && self.alternate_lines_above_viewport_and_cursor.is_some() - { + } else if new_columns != self.width && self.alternate_screen_state.is_some() { // in alternate screen just truncate exceeding width for row in &mut self.viewport { if row.width() >= new_columns { @@ -744,6 +795,7 @@ impl Grid { transfer_rows_from_lines_above_to_viewport( &mut self.lines_above, &mut self.viewport, + &mut self.sixel_grid, row_count_to_transfer, new_columns, ); @@ -763,13 +815,16 @@ impl Grid { } else { self.cursor.y -= row_count_to_transfer; if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() { - saved_cursor_position.y -= row_count_to_transfer + saved_cursor_position.y = saved_cursor_position + .y + .saturating_sub(row_count_to_transfer); }; } - if self.alternate_lines_above_viewport_and_cursor.is_none() { + if self.alternate_screen_state.is_none() { transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, + &mut self.sixel_grid, row_count_to_transfer, new_columns, ); @@ -813,16 +868,34 @@ impl Grid { } lines } - pub fn read_changes(&mut self, x_offset: usize, y_offset: usize) -> Vec { - let changes = self.output_buffer.changed_chunks_in_viewport( + pub fn read_changes( + &mut self, + x_offset: usize, + y_offset: usize, + ) -> (Vec, Vec) { + let changed_character_chunks = self.output_buffer.changed_chunks_in_viewport( &self.viewport, self.width, self.height, x_offset, y_offset, ); + let changed_rects = self + .output_buffer + .changed_rects_in_viewport(self.viewport.len()); + let changed_sixel_image_chunks = self.sixel_grid.changed_sixel_chunks_in_viewport( + changed_rects, + self.lines_above.len(), + self.width, + x_offset, + y_offset, + ); + if let Some(image_ids_to_reap) = self.sixel_grid.drain_image_ids_to_reap() { + self.sixel_grid.reap_images(image_ids_to_reap); + } self.output_buffer.clear(); - changes + + (changed_character_chunks, changed_sixel_image_chunks) } pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { if self.cursor.is_hidden { @@ -901,7 +974,7 @@ impl Grid { } } pub fn fill_viewport(&mut self, character: TerminalCharacter) { - if self.alternate_lines_above_viewport_and_cursor.is_some() { + if self.alternate_screen_state.is_some() { self.viewport.clear(); } else { self.transfer_rows_to_lines_above(self.viewport.len()) @@ -927,7 +1000,7 @@ impl Grid { return; } if scroll_region_bottom == self.height - 1 && scroll_region_top == 0 { - if self.alternate_lines_above_viewport_and_cursor.is_none() { + if self.alternate_screen_state.is_none() { self.transfer_rows_to_lines_above(1); } else { self.viewport.remove(0); @@ -963,9 +1036,10 @@ impl Grid { } if self.cursor.y == self.height - 1 { if self.scroll_region.is_none() { - if self.alternate_lines_above_viewport_and_cursor.is_none() { + if self.alternate_screen_state.is_none() { self.transfer_rows_to_lines_above(1); } else { + self.sixel_grid.offset_grid_top(); self.viewport.remove(0); } @@ -997,6 +1071,27 @@ impl Grid { } else { row.add_character_at(terminal_character, self.cursor.x); } + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let scrollback_size_in_pixels = + self.lines_above.len() * character_cell_size.height; + let absolute_x_in_pixels = self.cursor.x * character_cell_size.width; + let absolute_y_in_pixels = + scrollback_size_in_pixels + (self.cursor.y * character_cell_size.height); + let rect_to_cut_out = PixelRect { + x: absolute_x_in_pixels, + y: absolute_y_in_pixels as isize, + width: character_cell_size.width, + height: character_cell_size.height, + }; + if let Some(images_to_cut_out) = + self.sixel_grid.cut_off_rect_from_images(rect_to_cut_out) + { + for (image_id, rect_in_image_to_cut_out) in images_to_cut_out { + self.sixel_grid + .remove_pixels_from_image(image_id, rect_in_image_to_cut_out); + } + } + } self.output_buffer.update_line(self.cursor.y); }, None => { @@ -1088,7 +1183,7 @@ impl Grid { fn line_wrap(&mut self) { self.cursor.x = 0; if self.cursor.y == self.height - 1 { - if self.alternate_lines_above_viewport_and_cursor.is_none() { + if self.alternate_screen_state.is_none() { self.transfer_rows_to_lines_above(1); } else { self.viewport.remove(0); @@ -1333,7 +1428,7 @@ impl Grid { self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap()); self.lines_below = vec![]; self.viewport = vec![Row::new(self.width).canonical()]; - self.alternate_lines_above_viewport_and_cursor = None; + self.alternate_screen_state = None; self.cursor_key_mode = false; self.scroll_region = None; self.clear_viewport_before_rendering = true; @@ -1346,6 +1441,10 @@ impl Grid { self.output_buffer.update_all_lines(); self.changed_colors = None; self.scrollback_buffer_lines = 0; + self.sixel_scrolling = false; + if let Some(images_to_reap) = self.sixel_grid.clear() { + self.sixel_grid.reap_images(images_to_reap); + } } fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { self.preceding_char = Some(terminal_character); @@ -1486,6 +1585,7 @@ impl Grid { let transferred_rows_count = transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, + &mut self.sixel_grid, count, self.width, ); @@ -1493,6 +1593,57 @@ impl Grid { self.scrollback_buffer_lines = subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count); } + fn move_cursor_down_by_pixels(&mut self, pixel_count: usize) { + if let Some(character_cell_size) = { + let c = *self.character_cell_size.borrow(); + c + } { + // thanks borrow checker + let pixel_height = character_cell_size.height; + let to_move = (pixel_count as f64 / pixel_height as f64).ceil() as usize; + for _ in 0..to_move { + self.add_canonical_line(); + } + } + } + fn current_cursor_pixel_coordinates(&self) -> Option<(usize, usize)> { + // (x, y) + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let line_count_in_scrollback = self.lines_above.len(); + let y_coordinates = + (line_count_in_scrollback + self.cursor.y) * character_cell_size.height; + let x_coordinates = self.cursor.x * character_cell_size.width; + Some((x_coordinates, y_coordinates)) + } else { + None + } + } + fn create_sixel_image(&mut self) { + if let Some((x_pixel_coordinates, y_pixel_coordinates)) = + self.current_cursor_pixel_coordinates() + { + let (x_pixel_coordinates, y_pixel_coordinates) = if self.sixel_scrolling { + let scrollback_pixel_height = + self.lines_above.len() * self.character_cell_size.borrow().unwrap().height; + (0, scrollback_pixel_height) + } else { + (x_pixel_coordinates, y_pixel_coordinates) + }; + let new_image_id = self.sixel_grid.next_image_id(); + let new_sixel_image = + self.sixel_grid + .end_image(new_image_id, x_pixel_coordinates, y_pixel_coordinates); + if let Some(new_sixel_image) = new_sixel_image { + let (image_pixel_height, _image_pixel_width) = new_sixel_image.pixel_size(); + self.sixel_grid + .new_sixel_image(new_image_id, new_sixel_image); + if !self.sixel_scrolling { + self.move_cursor_down_by_pixels(image_pixel_height); + } + self.render_full_viewport(); // TODO: this could be optimized if it's a performance bottleneck + } + } + } } impl Perform for Grid { @@ -1543,16 +1694,40 @@ impl Perform for Grid { } } - fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) { - // TBD + fn hook(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) { + if c == 'q' { + // we only process sixel images if we know the pixel size of each character cell, + // otherwise we can't reliably display them + if self.current_cursor_pixel_coordinates().is_some() { + let max_sixel_height_in_pixels = if self.sixel_scrolling { + let character_cell_height = self.character_cell_size.borrow().unwrap().height; // unwrap here is safe because `current_cursor_pixel_coordinates` above is only Some if it exists + Some(self.height * character_cell_height) + } else { + None + }; + self.sixel_grid.start_image( + max_sixel_height_in_pixels, + intermediates.iter().collect(), + params.iter().collect(), + ); + } + } } - fn put(&mut self, _byte: u8) { - // TBD + fn put(&mut self, byte: u8) { + if self.sixel_grid.is_parsing() { + self.sixel_grid.handle_byte(byte); + // we explicitly set this to false here because in the context of Sixel, we only render the + // image when it's done, i.e. in the unhook method + self.should_render = false; + } } fn unhook(&mut self) { - // TBD + if self.sixel_grid.is_parsing() { + self.create_sixel_image(); + } + self.mark_for_rerender(); } fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { @@ -1588,6 +1763,18 @@ impl Perform for Grid { } self.changed_colors.as_mut().unwrap()[i as usize] = Some(c); return; + } else if chunk.get(1).as_ref().and_then(|c| c.get(0)) == Some(&b'?') { + if let Some(index) = index { + let terminal_emulator_color_codes = + self.terminal_emulator_color_codes.borrow(); + let color = terminal_emulator_color_codes.get(&(index as usize)); + if let Some(color) = color { + let color_response_message = + format!("\u{1b}]4;{};{}{}", index, color, terminator); + self.pending_messages_to_pty + .push(color_response_message.as_bytes().to_vec()); + } + } } } }, @@ -1808,18 +1995,22 @@ impl Perform for Grid { self.bracketed_paste_mode = false; }, Some(1049) => { - // leave alternate buffer - if let Some(( - alternative_lines_above, - alternative_viewport, - alternative_cursor, - )) = &mut self.alternate_lines_above_viewport_and_cursor + if let Some(mut alternate_screen_state) = self.alternate_screen_state.take() { - std::mem::swap(&mut self.lines_above, alternative_lines_above); - std::mem::swap(&mut self.viewport, alternative_viewport); - std::mem::swap(&mut self.cursor, alternative_cursor); + if let Some(image_ids_to_reap) = self.sixel_grid.clear() { + // reap images before dropping the alternate_screen_state contents + // - we can't implement a drop method for this because the store is + // outside of the alternate_screen_state struct + self.sixel_grid.reap_images(image_ids_to_reap); + } + alternate_screen_state.apply_contents_to( + &mut self.lines_above, + &mut self.viewport, + &mut self.cursor, + &mut self.sixel_grid, + ); } - self.alternate_lines_above_viewport_and_cursor = None; + self.alternate_screen_state = None; self.clear_viewport_before_rendering = true; self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size... self.mark_for_rerender(); @@ -1844,6 +2035,9 @@ impl Perform for Grid { Some(7) => { self.disable_linewrap = true; }, + Some(80) => { + self.sixel_scrolling = false; + }, Some(1006) => { self.mouse_mode = false; }, @@ -1878,8 +2072,17 @@ impl Perform for Grid { vec![Row::new(self.width).canonical()], ); let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0)); - self.alternate_lines_above_viewport_and_cursor = - Some((current_lines_above, current_viewport, current_cursor)); + let sixel_image_store = self.sixel_grid.sixel_image_store.clone(); + let alternate_sixelgrid = std::mem::replace( + &mut self.sixel_grid, + SixelGrid::new(self.character_cell_size.clone(), sixel_image_store), + ); + self.alternate_screen_state = Some(AlternateScreenState::new( + current_lines_above, + current_viewport, + current_cursor, + alternate_sixelgrid, + )); self.clear_viewport_before_rendering = true; self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count(); self.output_buffer.update_all_lines(); // make sure the screen gets cleared in the next render @@ -1900,6 +2103,9 @@ impl Perform for Grid { Some(7) => { self.disable_linewrap = false; }, + Some(80) => { + self.sixel_scrolling = true; + }, Some(1006) => { self.mouse_mode = true; }, @@ -1969,9 +2175,45 @@ impl Perform for Grid { let line_count = next_param_or(1); self.rotate_scroll_region_up(line_count as usize); } else if c == 'S' { - // move scroll up - let count = next_param_or(1); - self.rotate_scroll_region_down(count); + let first_intermediate_is_questionmark = match intermediates.get(0) { + Some(b'?') => true, + None => false, + _ => false, + }; + if first_intermediate_is_questionmark { + let query_type = params_iter.next(); + let is_query = params_iter.next() == Some(&[1]); + if is_query { + // XTSMGRAPHICS + match query_type { + Some(&[1]) => { + // number of color registers + let response = "\u{1b}[?1;0;65536S"; + self.pending_messages_to_pty + .push(response.as_bytes().to_vec()); + }, + Some(&[2]) => { + // Sixel graphics geometry in pixels + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let sixel_area_geometry = format!( + "\u{1b}[?2;0;{};{}S", + character_cell_size.width * self.width, + character_cell_size.height * self.height, + ); + self.pending_messages_to_pty + .push(sixel_area_geometry.as_bytes().to_vec()); + } + }, + _ => { + // unsupported (eg. ReGIS graphics geometry) + }, + } + } + } else { + // move scroll up + let count = next_param_or(1); + self.rotate_scroll_region_down(count); + } } else if c == 's' { self.save_cursor_position(); } else if c == 'u' { @@ -2022,6 +2264,11 @@ impl Perform for Grid { if let Some(cursor_shape) = shape { self.cursor.change_shape(cursor_shape); } + } else if matches!(intermediates.get(0), Some(b'>')) { + let version = version_number(VERSION); + let xtversion = format!("\u{1b}P>|Zellij({})\u{1b}\\", version); + self.pending_messages_to_pty + .push(xtversion.as_bytes().to_vec()); } } else if c == 'Z' { for _ in 0..next_param_or(1) { @@ -2033,7 +2280,7 @@ impl Perform for Grid { match intermediates.get(0) { None | Some(0) => { // primary device attributes - let terminal_capabilities = "\u{1b}[?6c"; + let terminal_capabilities = "\u{1b}[?64;4c"; self.pending_messages_to_pty .push(terminal_capabilities.as_bytes().to_vec()); }, @@ -2175,6 +2422,41 @@ impl Perform for Grid { } } +#[derive(Clone)] +pub struct AlternateScreenState { + lines_above: VecDeque, + viewport: Vec, + cursor: Cursor, + sixel_grid: SixelGrid, +} +impl AlternateScreenState { + pub fn new( + lines_above: VecDeque, + viewport: Vec, + cursor: Cursor, + sixel_grid: SixelGrid, + ) -> Self { + AlternateScreenState { + lines_above, + viewport, + cursor, + sixel_grid, + } + } + pub fn apply_contents_to( + &mut self, + lines_above: &mut VecDeque, + viewport: &mut Vec, + cursor: &mut Cursor, + sixel_grid: &mut SixelGrid, + ) { + std::mem::swap(&mut self.lines_above, lines_above); + std::mem::swap(&mut self.viewport, viewport); + std::mem::swap(&mut self.cursor, cursor); + std::mem::swap(&mut self.sixel_grid, sixel_grid); + } +} + #[derive(Clone)] pub struct Row { pub columns: VecDeque, diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs index c1fb31d1..569dbd42 100644 --- a/zellij-server/src/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -4,6 +4,7 @@ pub mod grid; pub mod link_handler; mod plugin_pane; pub mod selection; +pub mod sixel; pub mod terminal_character; mod terminal_pane; mod tiled_panes; @@ -13,6 +14,7 @@ pub use floating_panes::*; pub use grid::*; pub use link_handler::*; pub(crate) use plugin_pane::*; +pub use sixel::*; pub(crate) use terminal_character::*; pub use terminal_pane::*; pub use tiled_panes::*; diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 87e0ac7e..7d56b589 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use std::sync::mpsc::channel; use std::time::Instant; -use crate::output::CharacterChunk; +use crate::output::{CharacterChunk, SixelImageChunk}; use crate::panes::PaneId; use crate::pty::VteBytes; use crate::tab::Pane; @@ -141,7 +141,7 @@ impl Pane for PluginPane { fn render( &mut self, client_id: Option, - ) -> Option<(Vec, Option)> { + ) -> Option<(Vec, Option, Vec)> { // this is a bit of a hack but works in a pinch client_id?; let client_id = client_id.unwrap(); @@ -214,7 +214,7 @@ impl Pane for PluginPane { } } } - Some((vec![], Some(vte_output))) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer + Some((vec![], Some(vte_output), vec![])) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer } else { None } diff --git a/zellij-server/src/panes/sixel.rs b/zellij-server/src/panes/sixel.rs new file mode 100644 index 00000000..b5cf0bc0 --- /dev/null +++ b/zellij-server/src/panes/sixel.rs @@ -0,0 +1,466 @@ +use crate::output::SixelImageChunk; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use sixel_image::{SixelDeserializer, SixelImage}; +use sixel_tokenizer::SixelEvent; + +use std::fmt::Debug; + +use zellij_utils::pane_size::SizeInPixels; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub struct PixelRect { + pub x: usize, + pub y: isize, // this can potentially be negative (eg. when the image top has scrolled past the edge of the scrollbuffer) + pub width: usize, + pub height: usize, +} + +impl PixelRect { + pub fn new(x: usize, y: usize, height: usize, width: usize) -> Self { + PixelRect { + x, + y: y as isize, + width, + height, + } + } + pub fn intersecting_rect(&self, other: &PixelRect) -> Option { + // if the two rects intersect, this returns a PixelRect *relative to self* + let self_top_edge = self.y; + let self_bottom_edge = self.y + self.height as isize; + let self_left_edge = self.x; + let self_right_edge = self.x + self.width; + let other_top_edge = other.y; + let other_bottom_edge = other.y + other.height as isize; + let other_left_edge = other.x; + let other_right_edge = other.x + other.width; + + let absolute_x = std::cmp::max(self_left_edge, other_left_edge); + let absolute_y = std::cmp::max(self_top_edge, other_top_edge); + let absolute_right_edge = std::cmp::min(self_right_edge, other_right_edge); + let absolute_bottom_edge = std::cmp::min(self_bottom_edge, other_bottom_edge); + let width = absolute_right_edge.saturating_sub(absolute_x); + let height = absolute_bottom_edge.saturating_sub(absolute_y); + let x = absolute_x - self.x; + let y = absolute_y - self.y; + if width > 0 && height > 0 { + Some(PixelRect { + x, + y, + width, + height: height as usize, + }) + } else { + None + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct SixelGrid { + sixel_image_locations: HashMap, + previous_cell_size: Option, + character_cell_size: Rc>>, + currently_parsing: Option, + image_ids_to_reap: Vec, + sixel_parser: Option, + pub sixel_image_store: Rc>, +} + +impl SixelGrid { + pub fn new( + character_cell_size: Rc>>, + sixel_image_store: Rc>, + ) -> Self { + let previous_cell_size = *character_cell_size.borrow(); + SixelGrid { + previous_cell_size, + character_cell_size, + sixel_image_store, + ..Default::default() + } + } + pub fn handle_byte(&mut self, byte: u8) { + self.sixel_parser + .as_mut() + .unwrap() + .advance(&byte, |sixel_event| { + if let Some(currently_parsing) = self.currently_parsing.as_mut() { + let _ = currently_parsing.handle_event(sixel_event); + } + }); + } + pub fn handle_event(&mut self, sixel_event: SixelEvent) { + if let Some(currently_parsing) = self.currently_parsing.as_mut() { + let _ = currently_parsing.handle_event(sixel_event); + } + } + pub fn is_parsing(&self) -> bool { + self.sixel_parser.is_some() + } + pub fn start_image( + &mut self, + max_height_in_pixels: Option, + dcs_intermediates: Vec<&u8>, + dcs_params: Vec<&[u16]>, + ) { + self.sixel_parser = Some(sixel_tokenizer::Parser::new()); + match max_height_in_pixels { + Some(max_height_in_pixels) => { + self.currently_parsing = + Some(SixelDeserializer::new().max_height(max_height_in_pixels)); + }, + None => { + self.currently_parsing = Some(SixelDeserializer::new()); + }, + } + + self.handle_byte(27); + self.handle_byte(b'P'); + + for byte in dcs_intermediates { + self.handle_byte(*byte); + } + + // send DCS event to parser + for (i, param) in dcs_params.iter().enumerate() { + if i != 0 { + self.handle_byte(b';'); + } + for subparam in param.iter() { + let mut b = [0; 4]; + for digit in subparam.to_string().chars() { + let len = digit.encode_utf8(&mut b).len(); + for byte in b.iter().take(len) { + self.handle_byte(*byte); + } + } + } + } + self.handle_byte(b'q'); + } + pub fn end_image( + &mut self, + new_image_id: usize, + x_pixel_coordinates: usize, + y_pixel_coordinates: usize, + ) -> Option { + // usize is image_id + self.sixel_parser = None; + if let Some(sixel_deserializer) = self.currently_parsing.as_mut() { + if let Ok(sixel_image) = sixel_deserializer.create_image() { + let image_pixel_size = sixel_image.pixel_size(); + let image_size_and_coordinates = PixelRect::new( + x_pixel_coordinates, + y_pixel_coordinates, + image_pixel_size.0, + image_pixel_size.1, + ); + + // here we remove images which this image covers completely to save on system + // resources - TODO: also do this with partial covers, eg. if several images + // together cover one image + for (image_id, pixel_rect) in &self.sixel_image_locations { + if let Some(intersecting_rect) = + pixel_rect.intersecting_rect(&image_size_and_coordinates) + { + if intersecting_rect.x == pixel_rect.x + && intersecting_rect.y == pixel_rect.y + && intersecting_rect.height == pixel_rect.height + && intersecting_rect.width == pixel_rect.width + { + self.image_ids_to_reap.push(*image_id); + } + } + } + for image_id in &self.image_ids_to_reap { + self.sixel_image_locations.remove(image_id); + } + + self.sixel_image_locations + .insert(new_image_id, image_size_and_coordinates); + self.currently_parsing = None; + Some(sixel_image) + } else { + None + } + } else { + None + } + } + pub fn image_coordinates(&self) -> impl Iterator { + self.sixel_image_locations + .iter() + .map(|(image_id, pixel_rect)| (*image_id, pixel_rect)) + } + pub fn cut_off_rect_from_images( + &mut self, + rect_to_cut_out: PixelRect, + ) -> Option> { + // if there is an image at this cursor location, this returns the image ID and the PixelRect inside the image to be removed + let mut ret = None; + for (image_id, pixel_rect) in &self.sixel_image_locations { + if let Some(intersecting_rect) = pixel_rect.intersecting_rect(&rect_to_cut_out) { + let ret = ret.get_or_insert(vec![]); + ret.push((*image_id, intersecting_rect)); + } + } + ret + } + pub fn offset_grid_top(&mut self) { + if let Some(character_cell_size) = *self.character_cell_size.borrow() { + let height_to_reduce = character_cell_size.height as isize; + for (sixel_image_id, pixel_rect) in self.sixel_image_locations.iter_mut() { + pixel_rect.y -= height_to_reduce; + if pixel_rect.y + pixel_rect.height as isize <= 0 { + self.image_ids_to_reap.push(*sixel_image_id); + } + } + for image_id in &self.image_ids_to_reap { + self.sixel_image_locations.remove(image_id); + } + } + } + pub fn drain_image_ids_to_reap(&mut self) -> Option> { + let images_to_reap = self.image_ids_to_reap.drain(..); + if images_to_reap.len() > 0 { + Some(images_to_reap.collect()) + } else { + None + } + } + pub fn character_cell_size_possibly_changed(&mut self) { + if let (Some(previous_cell_size), Some(character_cell_size)) = + (self.previous_cell_size, *self.character_cell_size.borrow()) + { + if previous_cell_size != character_cell_size { + for (_image_id, pixel_rect) in self.sixel_image_locations.iter_mut() { + pixel_rect.x = + (pixel_rect.x / previous_cell_size.width) * character_cell_size.width; + pixel_rect.y = (pixel_rect.y / previous_cell_size.height as isize) + * character_cell_size.height as isize; + } + } + } + self.previous_cell_size = *self.character_cell_size.borrow(); + } + pub fn clear(&mut self) -> Option> { + // returns image ids to reap + let mut image_ids: Vec = self + .sixel_image_locations + .drain() + .map(|(image_id, _image_rect)| image_id) + .collect(); + image_ids.append(&mut self.image_ids_to_reap); + if !image_ids.is_empty() { + Some(image_ids) + } else { + None + } + } + pub fn next_image_id(&self) -> usize { + self.sixel_image_store.borrow().sixel_images.keys().len() + } + pub fn new_sixel_image(&mut self, sixel_image_id: usize, sixel_image: SixelImage) { + self.sixel_image_store + .borrow_mut() + .sixel_images + .insert(sixel_image_id, (sixel_image, HashMap::new())); + } + pub fn remove_pixels_from_image(&mut self, image_id: usize, pixel_rect: PixelRect) { + if let Some((sixel_image, sixel_image_cache)) = self + .sixel_image_store + .borrow_mut() + .sixel_images + .get_mut(&image_id) + { + sixel_image.cut_out( + pixel_rect.x, + pixel_rect.y as usize, + pixel_rect.width, + pixel_rect.height, + ); + sixel_image_cache.clear(); // TODO: more intelligent cache clearing + } + } + pub fn reap_images(&mut self, ids_to_reap: Vec) { + for id in ids_to_reap { + drop(self.sixel_image_store.borrow_mut().sixel_images.remove(&id)); + } + } + pub fn image_cell_coordinates_in_viewport( + &self, + viewport_height: usize, + scrollback_height: usize, + ) -> Vec<(usize, usize, usize, usize)> { + match *self.character_cell_size.borrow() { + Some(character_cell_size) => self + .sixel_image_locations + .iter() + .map(|(_image_id, pixel_rect)| { + let scrollback_size_in_pixels = scrollback_height * character_cell_size.height; + let y_pixel_coordinates_in_viewport = + pixel_rect.y - scrollback_size_in_pixels as isize; + let image_y = std::cmp::max(y_pixel_coordinates_in_viewport, 0) as usize + / character_cell_size.height; + let image_x = pixel_rect.x / character_cell_size.width; + let image_height_in_pixels = if y_pixel_coordinates_in_viewport < 0 { + pixel_rect.height as isize + y_pixel_coordinates_in_viewport + } else { + pixel_rect.height as isize + }; + let image_height = image_height_in_pixels as usize / character_cell_size.height; + let image_width = pixel_rect.width / character_cell_size.width; + let height_remainder = + if image_height_in_pixels as usize % character_cell_size.height > 0 { + 1 + } else { + 0 + }; + let width_remainder = if pixel_rect.width % character_cell_size.width > 0 { + 1 + } else { + 0 + }; + let image_top_edge = image_y; + let image_bottom_edge = + std::cmp::min(image_y + image_height + height_remainder, viewport_height); + let image_left_edge = image_x; + let image_right_edge = image_x + image_width + width_remainder; + ( + image_top_edge, + image_bottom_edge, + image_left_edge, + image_right_edge, + ) + }) + .collect(), + None => vec![], + } + } + pub fn changed_sixel_chunks_in_viewport( + &self, + changed_rects: HashMap, + scrollback_size_in_lines: usize, + viewport_width_in_cells: usize, + viewport_x_offset: usize, + viewport_y_offset: usize, + ) -> Vec { + let mut changed_sixel_image_chunks = vec![]; + if let Some(character_cell_size) = { *self.character_cell_size.borrow() } { + for (sixel_image_id, sixel_image_pixel_rect) in self.image_coordinates() { + for (line_index, line_count) in &changed_rects { + let changed_rect_pixel_height = line_count * character_cell_size.height; + let changed_rect_top_edge = ((line_index + scrollback_size_in_lines) + * character_cell_size.height) + as isize; + let changed_rect_bottom_edge = + changed_rect_top_edge + changed_rect_pixel_height as isize; + let sixel_image_top_edge = sixel_image_pixel_rect.y; + let sixel_image_bottom_edge = + sixel_image_pixel_rect.y + sixel_image_pixel_rect.height as isize; + + let cell_x_in_current_pane = + sixel_image_pixel_rect.x / character_cell_size.width; + let cell_x = viewport_x_offset + cell_x_in_current_pane; + let sixel_image_pixel_width = if sixel_image_pixel_rect.x + + sixel_image_pixel_rect.width + <= (viewport_width_in_cells * character_cell_size.width) + { + sixel_image_pixel_rect.width + } else { + (viewport_width_in_cells * character_cell_size.width) + .saturating_sub(sixel_image_pixel_rect.x) + }; + if sixel_image_pixel_width == 0 { + continue; + } + + let sixel_image_cell_distance_from_scrollback_top = + sixel_image_top_edge as usize / character_cell_size.height; + // if the image is above the rect top, this will be 0 + let sixel_image_cell_distance_from_changed_rect_top = + sixel_image_cell_distance_from_scrollback_top + .saturating_sub(line_index + scrollback_size_in_lines); + let cell_y = viewport_y_offset + + line_index + + sixel_image_cell_distance_from_changed_rect_top; + let sixel_image_pixel_x = 0; + // if the image is above the rect top, this will be 0 + let sixel_image_pixel_y = (changed_rect_top_edge as usize) + .saturating_sub(sixel_image_top_edge as usize) + as usize; + let sixel_image_pixel_height = std::cmp::min( + (std::cmp::min(changed_rect_bottom_edge, sixel_image_bottom_edge) + - std::cmp::max(changed_rect_top_edge, sixel_image_top_edge)) + as usize, + sixel_image_pixel_rect.height, + ); + + if (sixel_image_top_edge >= changed_rect_top_edge + && sixel_image_top_edge <= changed_rect_bottom_edge) + || (sixel_image_bottom_edge >= changed_rect_top_edge + && sixel_image_bottom_edge <= changed_rect_bottom_edge) + || (sixel_image_bottom_edge >= changed_rect_bottom_edge + && sixel_image_top_edge <= changed_rect_top_edge) + { + changed_sixel_image_chunks.push(SixelImageChunk { + cell_x, + cell_y, + sixel_image_pixel_x, + sixel_image_pixel_y, + sixel_image_pixel_width, + sixel_image_pixel_height, + sixel_image_id, + }); + } + } + } + } + changed_sixel_image_chunks + } +} + +type SixelImageCache = HashMap; +#[derive(Debug, Clone, Default)] +pub struct SixelImageStore { + sixel_images: HashMap, +} + +impl SixelImageStore { + pub fn serialize_image( + &mut self, + image_id: usize, + pixel_x: usize, + pixel_y: usize, + pixel_width: usize, + pixel_height: usize, + ) -> Option { + self.sixel_images + .get_mut(&image_id) + .map(|(sixel_image, sixel_image_cache)| { + if let Some(cached_image) = sixel_image_cache.get(&PixelRect::new( + pixel_x, + pixel_y, + pixel_height, + pixel_width, + )) { + cached_image.clone() + } else { + let serialized_image = + sixel_image.serialize_range(pixel_x, pixel_y, pixel_width, pixel_height); + sixel_image_cache.insert( + PixelRect::new(pixel_x, pixel_y, pixel_height, pixel_width), + serialized_image.clone(), + ); + serialized_image + } + }) + } + pub fn image_count(&self) -> usize { + self.sixel_images.len() + } +} diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 64af5859..b6640037 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -1,4 +1,5 @@ -use crate::output::CharacterChunk; +use crate::output::{CharacterChunk, SixelImageChunk}; +use crate::panes::sixel::SixelImageStore; use crate::panes::{ grid::Grid, terminal_character::{CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER}, @@ -99,10 +100,10 @@ impl Pane for TerminalPane { self.reflow_lines(); } fn handle_pty_bytes(&mut self, bytes: VteBytes) { + self.set_should_render(true); for &byte in &bytes { self.vte_parser.advance(&mut self.grid, byte); } - self.set_should_render(true); } fn cursor_coordinates(&self) -> Option<(usize, usize)> { // (x, y) @@ -205,13 +206,14 @@ impl Pane for TerminalPane { fn render( &mut self, _client_id: Option, - ) -> Option<(Vec, Option)> { + ) -> Option<(Vec, Option, Vec)> { if self.should_render() { let mut raw_vte_output = String::new(); let content_x = self.get_content_x(); let content_y = self.get_content_y(); - let mut character_chunks = self.grid.read_changes(content_x, content_y); + let (mut character_chunks, sixel_image_chunks) = + self.grid.read_changes(content_x, content_y); for character_chunk in character_chunks.iter_mut() { character_chunk.add_changed_colors(self.grid.changed_colors); if self @@ -237,7 +239,7 @@ impl Pane for TerminalPane { self.grid.ring_bell = false; } self.set_should_render(false); - Some((character_chunks, Some(raw_vte_output))) + Some((character_chunks, Some(raw_vte_output), sixel_image_chunks)) } else { None } @@ -509,15 +511,19 @@ impl TerminalPane { pane_name: String, link_handler: Rc>, character_cell_size: Rc>>, + sixel_image_store: Rc>, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, ) -> TerminalPane { let initial_pane_title = format!("Pane #{}", pane_index); let grid = Grid::new( position_and_size.rows.as_usize(), position_and_size.cols.as_usize(), terminal_emulator_colors, + terminal_emulator_color_codes, link_handler, character_cell_size, + sixel_image_store, ); TerminalPane { frame: HashMap::new(), diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index 057c1466..42e7ff36 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -1,10 +1,14 @@ use super::super::Grid; +use crate::panes::grid::SixelImageStore; use crate::panes::link_handler::LinkHandler; use ::insta::assert_snapshot; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use zellij_utils::{data::Palette, pane_size::SizeInPixels, position::Position, vte}; +use std::fmt::Write; + fn read_fixture(fixture_name: &str) -> Vec { let mut path_to_file = std::path::PathBuf::new(); path_to_file.push("../src"); @@ -18,12 +22,16 @@ fn read_fixture(fixture_name: &str) -> Vec { #[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 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, ); let fixture_name = "vttest1-0"; let content = read_fixture(fixture_name); @@ -36,12 +44,16 @@ fn vttest1_0() { #[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 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, ); let fixture_name = "vttest1-1"; let content = read_fixture(fixture_name); @@ -54,12 +66,16 @@ fn vttest1_1() { #[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 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, ); let fixture_name = "vttest1-2"; let content = read_fixture(fixture_name); @@ -72,12 +88,16 @@ fn vttest1_2() { #[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 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, ); let fixture_name = "vttest1-3"; let content = read_fixture(fixture_name); @@ -90,12 +110,16 @@ fn vttest1_3() { #[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 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, ); let fixture_name = "vttest1-4"; let content = read_fixture(fixture_name); @@ -108,12 +132,16 @@ fn vttest1_4() { #[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 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, ); let fixture_name = "vttest1-5"; let content = read_fixture(fixture_name); @@ -126,12 +154,16 @@ fn vttest1_5() { #[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 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, ); let fixture_name = "vttest2-0"; let content = read_fixture(fixture_name); @@ -144,12 +176,16 @@ fn vttest2_0() { #[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 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, ); let fixture_name = "vttest2-1"; let content = read_fixture(fixture_name); @@ -162,12 +198,16 @@ fn vttest2_1() { #[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 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, ); let fixture_name = "vttest2-2"; let content = read_fixture(fixture_name); @@ -180,12 +220,16 @@ fn vttest2_2() { #[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 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, ); let fixture_name = "vttest2-3"; let content = read_fixture(fixture_name); @@ -198,12 +242,16 @@ fn vttest2_3() { #[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 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, ); let fixture_name = "vttest2-4"; let content = read_fixture(fixture_name); @@ -216,12 +264,16 @@ fn vttest2_4() { #[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 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, ); let fixture_name = "vttest2-5"; let content = read_fixture(fixture_name); @@ -234,12 +286,16 @@ fn vttest2_5() { #[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 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, ); let fixture_name = "vttest2-6"; let content = read_fixture(fixture_name); @@ -252,12 +308,16 @@ fn vttest2_6() { #[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 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, ); let fixture_name = "vttest2-7"; let content = read_fixture(fixture_name); @@ -270,12 +330,16 @@ fn vttest2_7() { #[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 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, ); let fixture_name = "vttest2-8"; let content = read_fixture(fixture_name); @@ -288,12 +352,16 @@ fn vttest2_8() { #[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 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, ); let fixture_name = "vttest2-9"; let content = read_fixture(fixture_name); @@ -306,12 +374,16 @@ fn vttest2_9() { #[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 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, ); let fixture_name = "vttest2-10"; let content = read_fixture(fixture_name); @@ -324,12 +396,16 @@ fn vttest2_10() { #[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 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, ); let fixture_name = "vttest2-11"; let content = read_fixture(fixture_name); @@ -342,12 +418,16 @@ fn vttest2_11() { #[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 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, ); let fixture_name = "vttest2-12"; let content = read_fixture(fixture_name); @@ -360,12 +440,16 @@ fn vttest2_12() { #[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 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, ); let fixture_name = "vttest2-13"; let content = read_fixture(fixture_name); @@ -378,12 +462,16 @@ fn vttest2_13() { #[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 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, ); let fixture_name = "vttest2-14"; let content = read_fixture(fixture_name); @@ -396,12 +484,16 @@ fn vttest2_14() { #[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 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, ); let fixture_name = "vttest3-0"; let content = read_fixture(fixture_name); @@ -414,12 +506,16 @@ fn vttest3_0() { #[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 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, ); let fixture_name = "vttest8-0"; let content = read_fixture(fixture_name); @@ -432,12 +528,16 @@ fn vttest8_0() { #[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 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, ); let fixture_name = "vttest8-1"; let content = read_fixture(fixture_name); @@ -450,12 +550,16 @@ fn vttest8_1() { #[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 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, ); let fixture_name = "vttest8-2"; let content = read_fixture(fixture_name); @@ -468,12 +572,16 @@ fn vttest8_2() { #[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 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, ); let fixture_name = "vttest8-3"; let content = read_fixture(fixture_name); @@ -486,12 +594,16 @@ fn vttest8_3() { #[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 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, ); let fixture_name = "vttest8-4"; let content = read_fixture(fixture_name); @@ -504,12 +616,16 @@ fn vttest8_4() { #[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 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, ); let fixture_name = "vttest8-5"; let content = read_fixture(fixture_name); @@ -522,12 +638,16 @@ fn vttest8_5() { #[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 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, ); let fixture_name = "csi-b"; let content = read_fixture(fixture_name); @@ -540,12 +660,16 @@ fn csi_b() { #[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 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, ); let fixture_name = "csi-capital-i"; let content = read_fixture(fixture_name); @@ -558,12 +682,16 @@ fn csi_capital_i() { #[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 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, ); let fixture_name = "csi-capital-z"; let content = read_fixture(fixture_name); @@ -576,12 +704,16 @@ fn csi_capital_z() { #[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 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, ); let fixture_name = "terminal_reports"; let content = read_fixture(fixture_name); @@ -594,12 +726,16 @@ fn terminal_reports() { #[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 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, ); let fixture_name = "wide_characters"; let content = read_fixture(fixture_name); @@ -612,12 +748,16 @@ fn wide_characters() { #[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 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, ); let fixture_name = "wide_characters_line_wrap"; let content = read_fixture(fixture_name); @@ -630,12 +770,16 @@ fn wide_characters_line_wrap() { #[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 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, ); let fixture_name = "wide_characters_middle_line_insert"; let content = read_fixture(fixture_name); @@ -648,12 +792,16 @@ fn insert_character_in_line_with_wide_character() { #[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 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, ); let fixture_name = "wide-chars-delete-middle"; let content = read_fixture(fixture_name); @@ -666,12 +814,16 @@ fn delete_char_in_middle_of_line_with_widechar() { #[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 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, ); let fixture_name = "wide-chars-delete-middle-after-multi"; let content = read_fixture(fixture_name); @@ -684,12 +836,16 @@ fn delete_char_in_middle_of_line_with_multiple_widechars() { #[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 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, ); let fixture_name = "fish_wide_characters_override_clock"; let content = read_fixture(fixture_name); @@ -702,12 +858,16 @@ fn fish_wide_characters_override_clock() { #[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 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, ); let fixture_name = "bash_delete_wide_characters"; let content = read_fixture(fixture_name); @@ -720,12 +880,16 @@ fn bash_delete_wide_characters() { #[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 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, ); let fixture_name = "delete_wide_characters_before_cursor"; let content = read_fixture(fixture_name); @@ -738,12 +902,16 @@ fn delete_wide_characters_before_cursor() { #[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 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, ); let fixture_name = "delete_wide_characters_before_cursor_when_cursor_is_on_wide_character"; let content = read_fixture(fixture_name); @@ -756,12 +924,16 @@ fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() { #[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 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, ); let fixture_name = "delete_wide_character_under_cursor"; let content = read_fixture(fixture_name); @@ -774,12 +946,16 @@ fn delete_wide_character_under_cursor() { #[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 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, ); let fixture_name = "replace_wide_character_under_cursor"; let content = read_fixture(fixture_name); @@ -792,12 +968,16 @@ fn replace_wide_character_under_cursor() { #[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 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, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -810,12 +990,16 @@ fn wrap_wide_characters() { #[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 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, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -829,12 +1013,16 @@ fn wrap_wide_characters_on_size_change() { #[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 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, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -849,12 +1037,16 @@ fn unwrap_wide_characters_on_size_change() { #[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 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, ); let fixture_name = "wide_characters_line_middle"; let content = read_fixture(fixture_name); @@ -867,12 +1059,16 @@ fn wrap_wide_characters_in_the_middle_of_the_line() { #[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 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, ); let fixture_name = "wide_characters_line_end"; let content = read_fixture(fixture_name); @@ -885,12 +1081,16 @@ fn wrap_wide_characters_at_the_end_of_the_line() { #[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 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, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -911,12 +1111,16 @@ fn copy_selected_text_from_viewport() { #[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 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, ); let fixture_name = "grid_copy_wrapped"; let content = read_fixture(fixture_name); @@ -936,12 +1140,16 @@ fn copy_wrapped_selected_text_from_viewport() { #[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 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, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -962,12 +1170,16 @@ fn copy_selected_text_from_lines_above() { #[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 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, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -996,12 +1208,16 @@ fn copy_selected_text_from_lines_below() { #[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 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, ); let fixture_name = "fish_and_bandwhich"; let content = read_fixture(fixture_name); @@ -1014,12 +1230,16 @@ fn run_bandwhich_from_fish_shell() { #[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 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, ); let fixture_name = "fish_tab_completion_options"; let content = read_fixture(fixture_name); @@ -1036,13 +1256,18 @@ pub fn fish_select_tab_completion_options() { // 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 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, ); let fixture_name = "fish_select_tab_completion_options"; let content = read_fixture(fixture_name); @@ -1059,16 +1284,22 @@ pub fn vim_scroll_region_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 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, ); let fixture_name = "vim_scroll_region_down"; let content = read_fixture(fixture_name); @@ -1085,14 +1316,19 @@ pub fn vim_ctrl_d() { // 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 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, ); let fixture_name = "vim_ctrl_d"; let content = read_fixture(fixture_name); @@ -1109,13 +1345,18 @@ pub fn vim_ctrl_u() { // 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 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, ); let fixture_name = "vim_ctrl_u"; let content = read_fixture(fixture_name); @@ -1128,12 +1369,16 @@ pub fn vim_ctrl_u() { #[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 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, ); let fixture_name = "htop"; let content = read_fixture(fixture_name); @@ -1146,12 +1391,16 @@ pub fn htop() { #[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 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, ); let fixture_name = "htop_scrolling"; let content = read_fixture(fixture_name); @@ -1164,12 +1413,16 @@ pub fn htop_scrolling() { #[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 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, ); let fixture_name = "htop_right_scrolling"; let content = read_fixture(fixture_name); @@ -1186,16 +1439,22 @@ pub fn vim_overwrite() { // * 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 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, ); let fixture_name = "vim_overwrite"; let content = read_fixture(fixture_name); @@ -1210,12 +1469,16 @@ 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 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, ); let fixture_name = "clear_scroll_region"; let content = read_fixture(fixture_name); @@ -1228,12 +1491,16 @@ pub fn clear_scroll_region() { #[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 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, ); let fixture_name = "tab_characters"; let content = read_fixture(fixture_name); @@ -1246,12 +1513,16 @@ pub fn display_tab_characters_properly() { #[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 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, ); let fixture_name = "nvim_insert"; let content = read_fixture(fixture_name); @@ -1264,12 +1535,16 @@ pub fn neovim_insert_mode() { #[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 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, ); let fixture_name = "bash_cursor_linewrap"; let content = read_fixture(fixture_name); @@ -1284,12 +1559,16 @@ 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 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, ); let fixture_name = "fish_paste_multiline"; let content = read_fixture(fixture_name); @@ -1302,12 +1581,16 @@ pub fn fish_paste_multiline() { #[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 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, ); let fixture_name = "git_log"; let content = read_fixture(fixture_name); @@ -1322,12 +1605,16 @@ 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 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, ); let fixture_name = "git_diff_scrollup"; let content = read_fixture(fixture_name); @@ -1340,12 +1627,16 @@ pub fn git_diff_scrollup() { #[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 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, ); let fixture_name = "emacs_longbuf_tutorial"; let content = read_fixture(fixture_name); @@ -1358,12 +1649,16 @@ pub fn emacs_longbuf() { #[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 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, ); let fixture_name = "top_and_quit"; let content = read_fixture(fixture_name); @@ -1380,14 +1675,19 @@ pub fn exa_plus_omf_theme() { // 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 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, ); let fixture_name = "exa_plus_omf_theme"; let content = read_fixture(fixture_name); @@ -1400,12 +1700,16 @@ pub fn exa_plus_omf_theme() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1419,12 +1723,16 @@ pub fn scroll_up() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1439,12 +1747,16 @@ pub fn scroll_down() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1458,12 +1770,16 @@ pub fn scroll_up_with_line_wraps() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1478,12 +1794,16 @@ pub fn scroll_down_with_line_wraps() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1503,12 +1823,16 @@ pub fn scroll_up_decrease_width_and_scroll_down() { #[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 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, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -1528,12 +1852,16 @@ pub fn scroll_up_increase_width_and_scroll_down() { #[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 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, ); let fixture_name = "move_cursor_below_scroll_region"; let content = read_fixture(fixture_name); @@ -1546,12 +1874,16 @@ pub fn move_cursor_below_scroll_region() { #[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 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, ); let fixture_name = "chinese_characters_line_middle"; let content = read_fixture(fixture_name); @@ -1568,13 +1900,18 @@ pub fn full_screen_scroll_region_and_scroll_up() { // 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 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, ); let fixture_name = "scroll_region_full_screen"; let content = read_fixture(fixture_name); @@ -1590,12 +1927,16 @@ pub fn full_screen_scroll_region_and_scroll_up() { #[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 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, ); let fixture_name = "ring_bell"; let content = read_fixture(fixture_name); @@ -1608,12 +1949,16 @@ pub fn 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 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, ); let fixture_name = "alternate_screen_change_size"; let content = read_fixture(fixture_name); @@ -1630,12 +1975,16 @@ pub fn alternate_screen_change_size() { #[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 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, ); let fixture_name = "fzf_fullscreen"; let content = read_fixture(fixture_name); @@ -1652,12 +2001,16 @@ pub fn replace_multiple_wide_characters_under_cursor() { // 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 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, ); let fixture_name = "replace_multiple_wide_characters"; let content = read_fixture(fixture_name); @@ -1674,12 +2027,16 @@ pub fn replace_non_wide_characters_with_wide_characters() { // 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 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, ); let fixture_name = "replace_non_wide_characters_with_wide_characters"; let content = read_fixture(fixture_name); @@ -1692,12 +2049,16 @@ pub fn replace_non_wide_characters_with_wide_characters() { #[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 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, ); let fixture_name = "scroll_down"; let content = read_fixture(fixture_name); @@ -1710,12 +2071,16 @@ pub fn scroll_down_ansi() { #[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 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, ); let content = "foo\u{1b}[14Tbar".as_bytes(); for byte in content { @@ -1727,12 +2092,16 @@ pub fn ansi_capital_t() { #[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 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, ); 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(); for byte in content { @@ -1744,15 +2113,19 @@ pub fn ansi_capital_s() { #[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 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, ); let fixture_name = "terminal_pixel_size_reports"; let content = read_fixture(fixture_name); @@ -1771,12 +2144,16 @@ fn terminal_pixel_size_reports() { #[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 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, ); let fixture_name = "terminal_pixel_size_reports"; let content = read_fixture(fixture_name); @@ -1796,12 +2173,16 @@ fn terminal_pixel_size_reports_in_unsupported_terminals() { #[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 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, ); let content = "foo\u{1b}[2D\u{1b}[2@".as_bytes(); for byte in content { @@ -1809,3 +2190,326 @@ pub fn ansi_csi_at_sign() { } 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 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(), + ); + let pane_content = read_fixture("sixel-image-500px.six"); + for byte in pane_content { + vte_parser.advance(&mut grid, byte); + } + 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] +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 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(), + ); + let pane_content = read_fixture("sixel-image-500px.six"); + for byte in pane_content { + vte_parser.advance(&mut grid, byte); + } + 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 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(), + ); + + let move_to_alternate_screen = "\u{1b}[?1049h"; + for byte in move_to_alternate_screen.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + + let pane_content = read_fixture("sixel-image-500px.six"); + for byte in pane_content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); // should include the image + // + let move_away_from_alternate_screen = "\u{1b}[?1049l"; + for byte in move_away_from_alternate_screen.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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 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, + ); + + // enter DECSDM + let move_to_decsdm = "\u{1b}[?80h"; + for byte in move_to_decsdm.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + + // 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(); + } + for byte in text_to_fill_pane.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + + // render a sixel image (will appear on the top left and partially cover the text) + let pane_content = read_fixture("sixel-image-100px.six"); + for byte in pane_content { + vte_parser.advance(&mut grid, byte); + } + // 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"; + for byte in move_away_from_decsdm.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + + // Go down to the beginning of the next line + let mut go_down_once = String::new(); + writeln!(&mut go_down_once, "\n\r").unwrap(); + for byte in go_down_once.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + + // render another sixel image, should appear under the cursor + let pane_content = read_fixture("sixel-image-100px.six"); + for byte in pane_content { + vte_parser.advance(&mut grid, byte); + } + + // 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 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, + ); + let content = "\u{1b}]10;?\u{1b}\\"; + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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}]10;rgb:0000/0000/0000\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 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, + ); + let content = "\u{1b}]11;?\u{1b}\\"; + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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}]11;rgb:0000/0000/0000\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 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, + ); + let content = "\u{1b}]4;222;?\u{1b}\\"; + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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}]4;222;rgb:ffff/d7d7/8787\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 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, + ); + let content = "\u{1b}[?1;1;S\u{1b}\\"; + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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 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, + ); + let content = "\u{1b}[?2;1;S\u{1b}\\"; + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + 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"); +} diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__ansi_csi_at_sign.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__ansi_csi_at_sign.snap index 12ec5d49..0802a322 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__ansi_csi_at_sign.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__ansi_csi_at_sign.snap @@ -1,7 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs -assertion_line: 1810 +assertion_line: 2189 expression: "format!(\"{:?}\", grid)" --- 00 (C): f oo +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_cursor_linewrap.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_cursor_linewrap.snap index 93634316..cb3c0a0b 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_cursor_linewrap.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_cursor_linewrap.snap @@ -1,10 +1,34 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1552 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/mosaic on main ⨯ bash 16:00:06 02 (C): [aram@green mosaic]$ 12345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345 03 (W): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_delete_wide_characters.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_delete_wide_characters.snap index acb5a608..92e3c637 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_delete_wide_characters.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_delete_wide_characters.snap @@ -1,9 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 875 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/zellij on wide-char ⨯ bash 15:35:20 -02 (C): [aram@green zellij]$ HHHHHH +02 (C): [aram@green zellij]$ HHHHHH +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap index e5c302a0..b86ed31c 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap @@ -1,9 +1,34 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1486 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22 02 (C): ⋊> ~/c/mosaic on main ⨯ 15:07:29 +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap index 2be051c5..767f2f1a 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap @@ -1,8 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 655 expression: "format!(\"{:?}\", grid)" - --- 00 (C): ffffff 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap index 44026d04..4ec1d8e4 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap @@ -1,8 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 677 expression: "format!(\"{:?}\", grid)" - --- 00 (C): foo 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap index 2411b25f..9d8dc01b 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap @@ -1,8 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 699 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 12345678foo234567890 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_multiple_widechars.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_multiple_widechars.snap index 75c8a772..240d5fd5 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_multiple_widechars.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_multiple_widechars.snap @@ -1,7 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 831 expression: "format!(\"{:?}\", grid)" - --- 00 (C): [aram@green zellij]$ 🏠🏠🏠 +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_widechar.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_widechar.snap index 6a442b69..d90531b4 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_widechar.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_char_in_middle_of_line_with_widechar.snap @@ -1,7 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 809 expression: "format!(\"{:?}\", grid)" - --- 00 (C): [aram@green zellij]$ 🏠 def abc +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_character_under_cursor.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_character_under_cursor.snap index e6d26254..5606cd37 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_character_under_cursor.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_character_under_cursor.snap @@ -1,8 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 941 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 12 4 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor.snap index 401650ad..82387285 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor.snap @@ -1,7 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 897 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Hi +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor_when_cursor_is_on_wide_character.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor_when_cursor_is_on_wide_character.snap index ae1d8a65..fc22e7d2 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor_when_cursor_is_on_wide_character.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor_when_cursor_is_on_wide_character.snap @@ -1,7 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 919 expression: "format!(\"{:?}\", grid)" - --- 00 (C): i +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__display_tab_characters_properly.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__display_tab_characters_properly.snap index 48c75d6c..1858ccdc 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__display_tab_characters_properly.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__display_tab_characters_properly.snap @@ -1,7 +1,7 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1508 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 01 (C): OS: 5.9.13-arch1-1 GNU/Linux @@ -18,4 +18,17 @@ expression: "format!(\"{:?}\", grid)" 12 (C): wlp2s0 192.168.0.3 13 (C): 14 (C): [I] [20:07] kingdom:mosaic (main) | +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap index 71cc9bc8..1168075d 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap @@ -1,10 +1,66 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1644 expression: "format!(\"{:?}\", grid)" - --- 00 (C): ➜ mosaic git:(mosaic#130) emacs 01 (C): ➜ mosaic git:(mosaic#130) emacs -nw 02 (C): ➜ mosaic git:(mosaic#130) exit 03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): +51 (C): +52 (C): +53 (C): +54 (C): +55 (C): +56 (C): +57 (C): +58 (C): +59 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_paste_multiline.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_paste_multiline.snap index faf24017..00949aa2 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_paste_multiline.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_paste_multiline.snap @@ -1,7 +1,7 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1576 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 01 (C): OS: 5.9.14-arch1-1 GNU/Linux @@ -24,4 +24,11 @@ expression: "format!(\"{:?}\", grid)" 18 (W): )$/\\\\e[0;33m\1\\\\e[0m/' | \ 19 (C): paste -sd ''\ 20 (C): ) +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_select_tab_completion_options.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_select_tab_completion_options.snap index 88addebb..0b6540c6 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_select_tab_completion_options.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_select_tab_completion_options.snap @@ -1,11 +1,34 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1275 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/mosaic on main ⨯ sudo badblocks 11:32:23 02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB) 03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB) 04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_tab_completion_options.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_tab_completion_options.snap index a093e708..65a3339a 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_tab_completion_options.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_tab_completion_options.snap @@ -1,11 +1,34 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 1247 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/mosaic on main ⨯ sudo bandwhich 11:18:26 02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB) 03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB) 04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_wide_characters_override_clock.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_wide_characters_override_clock.snap index a328b18c..4285d8d4 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_wide_characters_override_clock.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_wide_characters_override_clock.snap @@ -1,8 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 853 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell -01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__insert_character_in_line_with_wide_character.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__insert_character_in_line_with_wide_character.snap index e278348c..c8eb2a73 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__insert_character_in_line_with_wide_character.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__insert_character_in_line_with_wide_character.snap @@ -1,7 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 787 expression: "format!(\"{:?}\", grid)" - --- 00 (C): [aram@green zellij]$ 🏠 xdef abc +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_multiple_wide_characters_under_cursor.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_multiple_wide_characters_under_cursor.snap index ca1a76d4..0915c83a 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_multiple_wide_characters_under_cursor.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_multiple_wide_characters_under_cursor.snap @@ -1,8 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2018 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 👨y x🔭 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_non_wide_characters_with_wide_characters.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_non_wide_characters_with_wide_characters.snap index 60ea74c8..3d542820 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_non_wide_characters_with_wide_characters.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_non_wide_characters_with_wide_characters.snap @@ -1,8 +1,57 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2044 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 👨👨 🔭 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_wide_character_under_cursor.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_wide_character_under_cursor.snap index 3b664aaf..32826dc0 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_wide_character_under_cursor.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_wide_character_under_cursor.snap @@ -1,8 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 963 expression: "format!(\"{:?}\", grid)" - --- 00 (C): 12 4 01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer-2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer-2.snap new file mode 100644 index 00000000..e4ba2640 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer-2.snap @@ -0,0 +1,36 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2273 +expression: "format!(\"{:?}\", grid)" +--- +00 (C): +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer.snap new file mode 100644 index 00000000..a1473016 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_image_in_alternate_buffer.snap @@ -0,0 +1,36 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2267 +expression: "format!(\"{:?}\", grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +20 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +21 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +22 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +23 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm-2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm-2.snap new file mode 100644 index 00000000..9fdef65f --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm-2.snap @@ -0,0 +1,36 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2338 +expression: "format!(\"{:?}\", grid)" +--- +00 (C): SixelSixelSix +01 (C): SixelSixelSix +02 (C): SixelSixelSix +03 (C): SixelSixelSix +04 (C): SixelSixelSix +05 (C): line 6 +06 (C): line 7 +07 (C): line 8 +08 (C): line 9 +09 (C): line 10 +10 (C): +11 (C): +12 (C): SixelSixelSix +13 (C): SixelSixelSix +14 (C): SixelSixelSix +15 (C): SixelSixelSix +16 (C): SixelSixelSix +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm.snap new file mode 100644 index 00000000..2676595a --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__sixel_with_image_scrolling_decsdm.snap @@ -0,0 +1,36 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 2316 +expression: "format!(\"{:?}\", grid)" +--- +00 (C): SixelSixelSix +01 (C): SixelSixelSix +02 (C): SixelSixelSix +03 (C): SixelSixelSix +04 (C): SixelSixelSix +05 (C): line 6 +06 (C): line 7 +07 (C): line 8 +08 (C): line 9 +09 (C): line 10 +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap index d289d93f..aef38672 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid . pending_messages_to_pty)" - +assertion_line: 721 +expression: "format!(\"{:?}\", grid.pending_messages_to_pty)" --- -[[27, 91, 63, 54, 99], [27, 91, 56, 59, 53, 49, 59, 57, 55, 116], [27, 91, 63, 54, 99], [27, 91, 48, 110], [27, 91, 49, 59, 49, 82]] +[[27, 91, 63, 54, 52, 59, 52, 99], [27, 91, 56, 59, 53, 49, 59, 57, 55, 116], [27, 91, 63, 54, 99], [27, 91, 48, 110], [27, 91, 49, 59, 49, 82]] diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters.snap index cd549fb3..0fb7477d 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters.snap @@ -1,8 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 743 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHH 15:19:10 +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters_line_wrap.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters_line_wrap.snap index e9f0e2db..d7b019fd 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters_line_wrap.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters_line_wrap.snap @@ -1,10 +1,27 @@ --- source: zellij-server/src/panes/./unit/grid_tests.rs +assertion_line: 765 expression: "format!(\"{:?}\", grid)" - --- 00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/zellij on main ⨯ bash 15:50:11 02 (C): [aram@green zellij]$ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH 03 (W): HHHHH +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__changing_character_cell_size_with_sixel_images.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__changing_character_cell_size_with_sixel_images.snap new file mode 100644 index 00000000..f7b23baf --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__changing_character_cell_size_with_sixel_images.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 323 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): line 5 +04 (C): +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__keep_working_after_corrupted_sixel_image.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__keep_working_after_corrupted_sixel_image.snap new file mode 100644 index 00000000..260a1048 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__keep_working_after_corrupted_sixel_image.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 364 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): line 1 +01 (C): line 2 +02 (C): line 3 +03 (C): line 4 +04 (C): line 5 +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__multiple_sixel_images_in_pane.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__multiple_sixel_images_in_pane.snap new file mode 100644 index 00000000..7625a75b --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__multiple_sixel_images_in_pane.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 235 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): line 1 +10 (C): line 2 +11 (C): line 3 +12 (C): line 4 +13 (C): line 5 +14 (C): +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__overflowing_sixel_image_inside_terminal_pane.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__overflowing_sixel_image_inside_terminal_pane.snap new file mode 100644 index 00000000..714e84c7 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__overflowing_sixel_image_inside_terminal_pane.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 156 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__partial_sixel_image_inside_terminal_pane.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__partial_sixel_image_inside_terminal_pane.snap new file mode 100644 index 00000000..c2fdefdf --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__partial_sixel_image_inside_terminal_pane.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 123 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__resizing_pane_with_sixel_images.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__resizing_pane_with_sixel_images.snap new file mode 100644 index 00000000..b013f3ed --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__resizing_pane_with_sixel_images.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 277 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): line 1 +10 (C): line 2 +11 (C): line 3 +12 (C): line 4 +13 (C): line 5 +14 (C): +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-2.snap new file mode 100644 index 00000000..1d251353 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-2.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 195 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): line 30 +01 (C): +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-3.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-3.snap new file mode 100644 index 00000000..ac5fbc57 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image-3.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 197 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +01 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +02 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +03 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +04 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image.snap new file mode 100644 index 00000000..1eaf7066 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__scrolling_through_a_sixel_image.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 193 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): line 27 +01 (C): line 28 +02 (C): line 29 +03 (C): line 30 +04 (C): +05 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +06 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +07 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +08 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +09 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +10 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +11 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +12 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +13 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +14 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +15 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +16 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +17 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +18 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix +19 (C): SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__sixel_image_inside_terminal_pane.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__sixel_image_inside_terminal_pane.snap new file mode 100644 index 00000000..339ec9f0 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__grid_tests__sixel_image_inside_terminal_pane.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/panes/./unit/terminal_pane_tests.rs +assertion_line: 85 +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): Si +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): + diff --git a/zellij-server/src/panes/unit/terminal_pane_tests.rs b/zellij-server/src/panes/unit/terminal_pane_tests.rs index 62c2436e..2e29ca34 100644 --- a/zellij-server/src/panes/unit/terminal_pane_tests.rs +++ b/zellij-server/src/panes/unit/terminal_pane_tests.rs @@ -1,16 +1,28 @@ use super::super::TerminalPane; +use crate::panes::sixel::SixelImageStore; use crate::panes::LinkHandler; use crate::tab::Pane; use ::insta::assert_snapshot; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use zellij_utils::{ data::{Palette, Style}, - pane_size::PaneGeom, + pane_size::{PaneGeom, SizeInPixels}, }; use std::fmt::Write; +fn read_fixture(fixture_name: &str) -> Vec { + 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] pub fn scrolling_inside_a_pane() { let fake_client_id = 1; @@ -20,6 +32,9 @@ pub fn scrolling_inside_a_pane() { let pid = 1; let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -28,7 +43,9 @@ pub fn scrolling_inside_a_pane() { String::new(), Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(None)), - Rc::new(RefCell::new(Palette::default())), + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..30 { @@ -42,3 +59,335 @@ pub fn scrolling_inside_a_pane() { terminal_pane.clear_scroll(); assert_snapshot!(format!("{:?}", terminal_pane.grid)); } + +#[test] +pub fn sixel_image_inside_terminal_pane() { + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let sixel_image_bytes = "\u{1b}Pq + #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 + #1~~@@vv@@~~@@~~$ + #2??}}GG}}??}}??- + #1!14@ + \u{1b}\\"; + + terminal_pane.handle_pty_bytes(Vec::from(sixel_image_bytes.as_bytes())); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn partial_sixel_image_inside_terminal_pane() { + // here we test to make sure we partially render an image that is partially hidden in the + // scrollbuffer + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let pane_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(pane_content); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn overflowing_sixel_image_inside_terminal_pane() { + // here we test to make sure we properly render an image that overflows both in the width and + // height of the pane + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(50); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let pane_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(pane_content); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn scrolling_through_a_sixel_image() { + let fake_client_id = 1; + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let mut text_to_fill_pane = String::new(); + for i in 0..30 { + writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); + } + writeln!(&mut text_to_fill_pane, "\r").unwrap(); + let pane_sixel_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes()); + terminal_pane.handle_pty_bytes(pane_sixel_content); + terminal_pane.scroll_up(10, fake_client_id); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); + terminal_pane.scroll_down(3, fake_client_id); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); + terminal_pane.clear_scroll(); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn multiple_sixel_images_in_pane() { + let fake_client_id = 1; + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let mut text_to_fill_pane = String::new(); + for i in 0..5 { + writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); + } + writeln!(&mut text_to_fill_pane, "\r").unwrap(); + let pane_sixel_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(pane_sixel_content.clone()); // one image above text + terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes()); + terminal_pane.handle_pty_bytes(pane_sixel_content); // one image below text + terminal_pane.scroll_up(20, fake_client_id); // scroll up to see both images + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn resizing_pane_with_sixel_images() { + // here we test, for example, that sixel images don't wrap with other lines + let fake_client_id = 1; + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let mut text_to_fill_pane = String::new(); + for i in 0..5 { + writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); + } + writeln!(&mut text_to_fill_pane, "\r").unwrap(); + let pane_sixel_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(pane_sixel_content.clone()); + terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes()); + terminal_pane.handle_pty_bytes(pane_sixel_content); + let mut new_win_size = PaneGeom::default(); + new_win_size.cols.set_inner(100); + new_win_size.rows.set_inner(20); + terminal_pane.set_geom(new_win_size); + terminal_pane.scroll_up(20, fake_client_id); // scroll up to see both images + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn changing_character_cell_size_with_sixel_images() { + let fake_client_id = 1; + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size.clone(), + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + let mut text_to_fill_pane = String::new(); + for i in 0..5 { + writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); + } + writeln!(&mut text_to_fill_pane, "\r").unwrap(); + let pane_sixel_content = read_fixture("sixel-image-500px.six"); + terminal_pane.handle_pty_bytes(pane_sixel_content.clone()); + terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes()); + terminal_pane.handle_pty_bytes(pane_sixel_content); + // here the new_win_size is the same as the old one, we just update the character_cell_size + // which will be picked up upon resize (which is why we're doing set_geom below) + let mut new_win_size = PaneGeom::default(); + new_win_size.cols.set_inner(121); + new_win_size.rows.set_inner(20); + *character_cell_size.borrow_mut() = Some(SizeInPixels { + width: 8, + height: 18, + }); + terminal_pane.set_geom(new_win_size); + terminal_pane.scroll_up(10, fake_client_id); // scroll up to see both images + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} + +#[test] +pub fn keep_working_after_corrupted_sixel_image() { + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::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 mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); // 0 is the pane index + + let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq + #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 + #1~~@@vv@@~~@@~~$ + #2??}}GG}}??}}??- + #1!14@ + \u{1b}\\"; + + terminal_pane.handle_pty_bytes(Vec::from(sixel_image_bytes.as_bytes())); + let mut text_to_fill_pane = String::new(); + for i in 0..5 { + writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); + } + terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes()); + assert_snapshot!(format!("{:?}", terminal_pane.grid)); +} diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 253f62bc..4420cf36 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -28,14 +28,24 @@ fn route_action( client_id: ClientId, ) -> bool { let mut should_break = false; - session - .senders - .send_to_plugin(PluginInstruction::Update( - None, - Some(client_id), - Event::InputReceived, - )) - .unwrap(); + + // forward the action to plugins unless it is a mousehold + // this is a bit of a hack around the unfortunate architecture we use with plugins + // this will change as soon as we refactor + match action { + Action::MouseHold(_) => {}, + _ => { + session + .senders + .send_to_plugin(PluginInstruction::Update( + None, + Some(client_id), + Event::InputReceived, + )) + .unwrap(); + }, + } + match action { Action::ToggleTab => { session @@ -493,6 +503,16 @@ pub(crate) fn route_thread_main( )) .unwrap(); }, + ClientToServerMsg::ColorRegisters(color_registers) => { + rlocked_sessions + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::TerminalColorRegisters( + color_registers, + )) + .unwrap(); + }, ClientToServerMsg::NewClient( client_attributes, cli_args, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 9d284b32..b5ebad53 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -1,7 +1,7 @@ //! Things related to [`Screen`]s. use std::cell::RefCell; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; use std::str; @@ -15,6 +15,7 @@ use crate::panes::terminal_character::AnsiCode; use crate::{ output::Output, + panes::sixel::SixelImageStore, panes::PaneId, pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, tab::Tab, @@ -95,6 +96,7 @@ pub enum ScreenInstruction { TerminalPixelDimensions(PixelDimensions), TerminalBackgroundColor(String), TerminalForegroundColor(String), + TerminalColorRegisters(Vec<(usize, String)>), ChangeMode(ModeInfo, ClientId), LeftClick(Position, ClientId), RightClick(Position, ClientId), @@ -184,6 +186,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::TerminalForegroundColor(..) => { ScreenContext::TerminalForegroundColor }, + ScreenInstruction::TerminalColorRegisters(..) => ScreenContext::TerminalColorRegisters, ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode, ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab, ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt, @@ -247,9 +250,11 @@ pub(crate) struct Screen { size: Size, pixel_dimensions: PixelDimensions, character_cell_size: Rc>>, + sixel_image_store: Rc>, /// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`] overlay: OverlayWindow, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, connected_clients: Rc>>, /// The indices of this [`Screen`]'s active [`Tab`]s. active_tab_indices: BTreeMap, @@ -279,12 +284,14 @@ impl Screen { size: client_attributes.size, pixel_dimensions: Default::default(), character_cell_size: Rc::new(RefCell::new(None)), + sixel_image_store: Rc::new(RefCell::new(SixelImageStore::default())), style: client_attributes.style, connected_clients: Rc::new(RefCell::new(HashSet::new())), active_tab_indices: BTreeMap::new(), tabs: BTreeMap::new(), overlay: OverlayWindow::default(), terminal_emulator_colors: Rc::new(RefCell::new(Palette::default())), + terminal_emulator_color_codes: Rc::new(RefCell::new(HashMap::new())), tab_history: BTreeMap::new(), mode_info: BTreeMap::new(), default_mode_info: mode_info, @@ -518,10 +525,19 @@ impl Screen { self.terminal_emulator_colors.borrow_mut().fg = fg_palette_color; } } + pub fn update_terminal_color_registers(&mut self, color_registers: Vec<(usize, String)>) { + let mut terminal_emulator_color_codes = self.terminal_emulator_color_codes.borrow_mut(); + for (color_register, color_sequence) in color_registers { + terminal_emulator_color_codes.insert(color_register, color_sequence); + } + } /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. pub fn render(&mut self) { - let mut output = Output::default(); + let mut output = Output::new( + self.sixel_image_store.clone(), + self.character_cell_size.clone(), + ); let mut tabs_to_close = vec![]; let size = self.size; let overlay = self.overlay.clone(); @@ -599,6 +615,7 @@ impl Screen { String::new(), self.size, self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.bus.os_input.as_ref().unwrap().clone(), self.bus.senders.clone(), self.max_panes, @@ -610,6 +627,7 @@ impl Screen { client_id, self.copy_options.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); tab.apply_layout(layout, new_pids, tab_index, client_id); if self.session_is_mirrored { @@ -793,7 +811,6 @@ impl Screen { pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) { if let Some(active_tab) = self.get_active_tab_mut(client_id) { if !active_tab.move_focus_left(client_id) { - println!("can has true"); self.switch_tab_prev(client_id); } } else { @@ -1236,6 +1253,9 @@ pub(crate) fn screen_thread_main( ScreenInstruction::TerminalForegroundColor(background_color_instruction) => { screen.update_terminal_foreground_color(background_color_instruction); }, + ScreenInstruction::TerminalColorRegisters(color_registers) => { + screen.update_terminal_color_registers(color_registers); + }, ScreenInstruction::ChangeMode(mode_info, client_id) => { screen.change_mode(mode_info, client_id); screen.render(); @@ -1264,8 +1284,9 @@ pub(crate) fn screen_thread_main( screen.render(); }, ScreenInstruction::MouseHold(point, client_id) => { - active_tab!(screen, client_id, |tab: &mut Tab| tab - .handle_mouse_hold(&point, client_id)); + active_tab!(screen, client_id, |tab: &mut Tab| { + tab.handle_mouse_hold(&point, client_id); + }); screen.render(); }, ScreenInstruction::Copy(client_id) => { diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 10ed525f..6c5c2c6b 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -17,7 +17,8 @@ use crate::ui::pane_boundaries_frame::FrameParams; use self::clipboard::ClipboardProvider; use crate::{ os_input_output::ServerOsApi, - output::{CharacterChunk, Output}, + output::{CharacterChunk, Output, SixelImageChunk}, + panes::sixel::SixelImageStore, panes::{FloatingPanes, TiledPanes}, panes::{LinkHandler, PaneId, PluginPane, TerminalPane}, pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, @@ -77,6 +78,7 @@ pub(crate) struct Tab { viewport: Rc>, // includes all non-UI panes display_area: Rc>, // includes all panes (including eg. the status bar and tab bar in the default layout) character_cell_size: Rc>>, + sixel_image_store: Rc>, os_api: Box, pub senders: ThreadSenders, synchronize_is_active: bool, @@ -96,6 +98,7 @@ pub(crate) struct Tab { copy_on_select: bool, last_mouse_hold_position: Option, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -135,7 +138,7 @@ pub trait Pane { fn render( &mut self, client_id: Option, - ) -> Option<(Vec, Option)>; // TODO: better + ) -> Option<(Vec, Option, Vec)>; // TODO: better fn render_frame( &mut self, client_id: ClientId, @@ -287,6 +290,7 @@ impl Tab { name: String, display_area: Size, character_cell_size: Rc>>, + sixel_image_store: Rc>, os_api: Box, senders: ThreadSenders, max_panes: Option, @@ -298,6 +302,7 @@ impl Tab { client_id: ClientId, copy_options: CopyOptions, terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, ) -> Self { let name = if name.is_empty() { format!("Tab #{}", index + 1) @@ -354,6 +359,7 @@ impl Tab { viewport, display_area, character_cell_size, + sixel_image_store, synchronize_is_active: false, os_api, senders, @@ -371,6 +377,7 @@ impl Tab { copy_on_select: copy_options.copy_on_select, last_mouse_hold_position: None, terminal_emulator_colors, + terminal_emulator_color_codes, } } @@ -438,7 +445,9 @@ impl Tab { layout.pane_name.clone().unwrap_or_default(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); new_pane.set_borderless(layout.borderless); self.tiled_panes @@ -668,7 +677,9 @@ impl Tab { String::new(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame resize_pty!(new_pane, self.os_api); @@ -691,7 +702,9 @@ impl Tab { String::new(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); self.tiled_panes.insert_pane(pid, Box::new(new_terminal)); self.should_clear_display_before_rendering = true; @@ -717,7 +730,9 @@ impl Tab { String::new(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); let replaced_pane = if self.floating_panes.panes_are_visible() { self.floating_panes @@ -762,7 +777,9 @@ impl Tab { String::new(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); self.tiled_panes .split_pane_horizontally(pid, Box::new(new_terminal), client_id); @@ -790,7 +807,9 @@ impl Tab { String::new(), self.link_handler.clone(), self.character_cell_size.clone(), + self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), ); self.tiled_panes .split_pane_vertically(pid, Box::new(new_terminal), client_id); @@ -1787,7 +1806,12 @@ impl Tab { } } } - pub fn handle_mouse_hold(&mut self, position_on_screen: &Position, client_id: ClientId) { + pub fn handle_mouse_hold( + &mut self, + position_on_screen: &Position, + client_id: ClientId, + ) -> bool { + // return value indicates whether we should trigger a render // determine if event is repeated to enable smooth scrolling let is_repeated = if let Some(last_position) = self.last_mouse_hold_position { position_on_screen == &last_position @@ -1805,7 +1829,8 @@ impl Tab { .move_pane_with_mouse(*position_on_screen, search_selectable) { self.set_force_render(); - return; + return !is_repeated; // we don't need to re-render in this case if the pane did not move + // return; } let selecting = self.selecting_with_mouse; @@ -1825,10 +1850,13 @@ impl Tab { let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line); self.write_to_active_terminal(mouse_event.into_bytes(), client_id); + return true; // we need to re-render in this case so the selection disappears } else if selecting { active_pane.update_selection(&relative_position, client_id); + return true; // we need to re-render in this case so the selection is updated } } + false // we shouldn't even get here, but might as well not needlessly render if we do } pub fn copy_selection(&self, client_id: ClientId) { diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__floating_pane_above_sixel_image.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__floating_pane_above_sixel_image.snap new file mode 100644 index 00000000..19d6feb8 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__floating_pane_above_sixel_image.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 1343 +expression: snapshot +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────── SCROLL: 0/7 ┐ +01 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +02 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +03 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +04 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +05 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +06 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +07 (C): │ixelSixelSixelSixelSixelSixelSixe┌ Pane #2 ─────────────────────────────────────────────────┐ │ +08 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +09 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +10 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +11 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +12 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +13 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +14 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +15 (C): │ixelSixelSixelSixelSixelSixelSixe│ │ │ +16 (C): │ixelSixelSixelSixelSixelSixelSixe└──────────────────────────────────────────────────────────┘ │ +17 (C): │ixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixe │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_with_sixel_image.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_with_sixel_image.snap new file mode 100644 index 00000000..df091cdb --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_with_sixel_image.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 1312 +expression: snapshot +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ │ +06 (C): │ │ +07 (C): │ ┌ Pane #2 ────────────────────────────────── SCROLL: 0/17 ┐ │ +08 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +09 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +10 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +11 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +12 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +13 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +14 (C): │ │SixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSixelSix│ │ +15 (C): │ │ │ │ +16 (C): │ └──────────────────────────────────────────────────────────┘ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index efb8909d..24edc91a 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -1,4 +1,5 @@ use super::{Output, Tab}; +use crate::panes::sixel::SixelImageStore; use crate::screen::CopyOptions; use crate::Arc; use crate::Mutex; @@ -13,12 +14,11 @@ use std::path::PathBuf; use zellij_utils::envs::set_session_name; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::ipc::IpcReceiverWithContext; -use zellij_utils::pane_size::Size; +use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::position::Position; use std::cell::RefCell; -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; @@ -119,12 +119,15 @@ fn create_new_tab(size: Size) -> Tab { let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = Tab::new( index, position, name, size, character_cell_info, + sixel_image_store, os_api, senders, max_panes, @@ -136,6 +139,66 @@ fn create_new_tab(size: Size) -> Tab { client_id, copy_options, terminal_emulator_colors, + terminal_emulator_color_codes, + ); + tab.apply_layout( + LayoutTemplate::default().try_into().unwrap(), + vec![1], + index, + client_id, + ); + tab +} + +fn create_new_tab_with_sixel_support( + size: Size, + sixel_image_store: Rc>, +) -> Tab { + // this is like the create_new_tab function but includes stuff needed for sixel, + // eg. character_cell_size + set_session_name("test".into()); + let index = 0; + let position = 0; + let name = String::new(); + let os_api = Box::new(FakeInputOutput { + file_dumps: Arc::new(Mutex::new(HashMap::new())), + }); + let senders = ThreadSenders::default().silently_fail_on_send(); + let max_panes = None; + let mode_info = ModeInfo::default(); + let style = Style::default(); + let draw_pane_frames = true; + let client_id = 1; + let session_is_mirrored = true; + let mut connected_clients = HashSet::new(); + connected_clients.insert(client_id); + let connected_clients = Rc::new(RefCell::new(connected_clients)); + let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { + width: 8, + height: 21, + }))); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); + let copy_options = CopyOptions::default(); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let mut tab = Tab::new( + index, + position, + name, + size, + character_cell_size, + sixel_image_store, + os_api, + senders, + max_panes, + style, + mode_info, + draw_pane_frames, + connected_clients, + session_is_mirrored, + client_id, + copy_options, + terminal_emulator_colors, + terminal_emulator_color_codes, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), @@ -162,12 +225,48 @@ use ::insta::assert_snapshot; use zellij_utils::vte; fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String { + 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 mut grid = Grid::new( rows, columns, Rc::new(RefCell::new(palette)), + terminal_emulator_color_codes, Rc::new(RefCell::new(LinkHandler::new())), - Rc::new(RefCell::new(None)), + character_cell_size, + sixel_image_store, + ); + let mut vte_parser = vte::Parser::new(); + for &byte in ansi_instructions.as_bytes() { + vte_parser.advance(&mut grid, byte); + } + format!("{:?}", grid) +} + +fn take_snapshot_with_sixel( + ansi_instructions: &str, + rows: usize, + columns: usize, + palette: Palette, + sixel_image_store: Rc>, +) -> String { + 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 mut grid = Grid::new( + rows, + columns, + Rc::new(RefCell::new(palette)), + terminal_emulator_color_codes, + Rc::new(RefCell::new(LinkHandler::new())), + character_cell_size, + sixel_image_store, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { @@ -183,12 +282,16 @@ fn take_snapshot_and_cursor_position( palette: Palette, ) -> (String, Option<(usize, usize)>) { // snapshot, x_coordinates, y_coordinates + 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, columns, Rc::new(RefCell::new(palette)), + terminal_emulator_color_codes, Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(None)), + sixel_image_store, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { @@ -1233,6 +1336,76 @@ fn save_cursor_position_across_resizes() { assert_snapshot!(snapshot); } +#[test] +fn move_floating_pane_with_sixel_image() { + let new_pane_id = PaneId::Terminal(2); + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone()); + let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { + width: 8, + height: 21, + }))); + let mut output = Output::new(sixel_image_store.clone(), character_cell_size); + + tab.toggle_floating_panes(client_id, None); + tab.new_pane(new_pane_id, Some(client_id)); + let fixture = read_fixture("sixel-image-500px.six"); + tab.handle_pty_bytes(2, fixture); + tab.handle_left_click(&Position::new(5, 71), client_id); + tab.handle_mouse_release(&Position::new(7, 75), client_id); + + tab.render(&mut output, None); + let snapshot = take_snapshot_with_sixel( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + sixel_image_store, + ); + + assert_snapshot!(snapshot); +} + +#[test] +fn floating_pane_above_sixel_image() { + let new_pane_id = PaneId::Terminal(2); + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone()); + let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { + width: 8, + height: 21, + }))); + let mut output = Output::new(sixel_image_store.clone(), character_cell_size); + + tab.toggle_floating_panes(client_id, None); + tab.new_pane(new_pane_id, Some(client_id)); + let fixture = read_fixture("sixel-image-500px.six"); + tab.handle_pty_bytes(1, fixture); + tab.handle_left_click(&Position::new(5, 71), client_id); + tab.handle_mouse_release(&Position::new(7, 75), client_id); + + tab.render(&mut output, None); + let snapshot = take_snapshot_with_sixel( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + sixel_image_store, + ); + + assert_snapshot!(snapshot); +} + #[test] fn suppress_tiled_pane() { let size = Size { diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 045515fa..719f9ce9 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -1,4 +1,5 @@ use super::Tab; +use crate::panes::sixel::SixelImageStore; use crate::screen::CopyOptions; use crate::{ os_input_output::{AsyncReader, Pid, ServerOsApi}, @@ -13,7 +14,7 @@ use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::{Size, SizeInPixels}; use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; @@ -105,12 +106,15 @@ fn create_new_tab(size: Size) -> Tab { let connected_clients = Rc::new(RefCell::new(connected_clients)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let mut tab = Tab::new( index, position, name, size, character_cell_info, + sixel_image_store, os_api, senders, max_panes, @@ -122,6 +126,7 @@ fn create_new_tab(size: Size) -> Tab { client_id, copy_options, terminal_emulator_colors, + terminal_emulator_color_codes, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), @@ -152,12 +157,15 @@ fn create_new_tab_with_cell_size( let connected_clients = Rc::new(RefCell::new(connected_clients)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let mut tab = Tab::new( index, position, name, size, character_cell_size, + sixel_image_store, os_api, senders, max_panes, @@ -169,6 +177,7 @@ fn create_new_tab_with_cell_size( client_id, copy_options, terminal_emulator_colors, + terminal_emulator_color_codes, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs index 50f76316..b0eae8e8 100644 --- a/zellij-server/src/ui/pane_contents_and_ui.rs +++ b/zellij-server/src/ui/pane_contents_and_ui.rs @@ -44,13 +44,19 @@ impl<'a> PaneContentsAndUi<'a> { &mut self, clients: impl Iterator, ) { - if let Some((character_chunks, raw_vte_output)) = self.pane.render(None) { + if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = self.pane.render(None) + { let clients: Vec = clients.collect(); self.output.add_character_chunks_to_multiple_clients( character_chunks, clients.iter().copied(), self.z_index, ); + self.output.add_sixel_image_chunks_to_multiple_clients( + sixel_image_chunks, + clients.iter().copied(), + self.z_index, + ); if let Some(raw_vte_output) = raw_vte_output { self.output.add_post_vte_instruction_to_multiple_clients( clients.iter().copied(), @@ -65,9 +71,16 @@ impl<'a> PaneContentsAndUi<'a> { } } pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) { - if let Some((character_chunks, raw_vte_output)) = self.pane.render(Some(client_id)) { + if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = + self.pane.render(Some(client_id)) + { self.output .add_character_chunks_to_client(client_id, character_chunks, self.z_index); + self.output.add_sixel_image_chunks_to_client( + client_id, + sixel_image_chunks, + self.z_index, + ); if let Some(raw_vte_output) = raw_vte_output { self.output.add_post_vte_instruction_to_client( client_id, diff --git a/zellij-utils/src/envs.rs b/zellij-utils/src/envs.rs index 84af4480..9e457b03 100644 --- a/zellij-utils/src/envs.rs +++ b/zellij-utils/src/envs.rs @@ -24,6 +24,10 @@ pub fn set_session_name(v: String) { set_var(SESSION_NAME_ENV_KEY, v); } +pub fn set_initial_environment_vars() { + set_var("COLORTERM", "24bit"); +} + pub const SOCKET_DIR_ENV_KEY: &str = "ZELLIJ_SOCKET_DIR"; pub fn get_socket_dir() -> Result { Ok(var(SOCKET_DIR_ENV_KEY)?) diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index c1317715..d7a4ba8a 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -277,6 +277,7 @@ pub enum ScreenContext { TerminalPixelDimensions, TerminalBackgroundColor, TerminalForegroundColor, + TerminalColorRegisters, ChangeMode, LeftClick, RightClick, diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 78a9137f..db9dd039 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -78,6 +78,7 @@ pub enum ClientToServerMsg { TerminalPixelDimensions(PixelDimensions), BackgroundColor(String), ForegroundColor(String), + ColorRegisters(Vec<(usize, String)>), TerminalResize(Size), NewClient( ClientAttributes,