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
This commit is contained in:
parent
61deca80ed
commit
c89b416d76
75 changed files with 4385 additions and 1068 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
|
@ -53,6 +53,12 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|
@ -1144,9 +1150,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.14.1"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc3e639bcba360d9237acabd22014c16f3df772db463b7446cd81b070714767"
|
checksum = "689960f187c43c01650c805fb6bc6f55ab944499d86d4ffe9474ad78991d8e94"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -2213,6 +2219,25 @@ version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
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]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -2764,7 +2789,7 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.5.2",
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
"vte_generate_state_changes",
|
"vte_generate_state_changes",
|
||||||
]
|
]
|
||||||
|
|
@ -3273,6 +3298,7 @@ name = "zellij-server"
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
|
"arrayvec 0.7.2",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -3284,6 +3310,8 @@ dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"log",
|
"log",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sixel-image",
|
||||||
|
"sixel-tokenizer",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"typetag",
|
"typetag",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
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 {
|
if quicknav_show_count <= &MAX_CACHE_HITS {
|
||||||
let _ = local_cache.caching("quicknav");
|
let _ = local_cache.caching("quicknav");
|
||||||
return String::from("quicknav");
|
return String::from("quicknav");
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use zellij_server::panes::sixel::SixelImageStore;
|
||||||
use zellij_server::panes::{LinkHandler, TerminalPane};
|
use zellij_server::panes::{LinkHandler, TerminalPane};
|
||||||
use zellij_utils::data::{Palette, Style};
|
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 zellij_utils::vte;
|
||||||
|
|
||||||
use ssh2::Session;
|
use ssh2::Session;
|
||||||
|
|
@ -76,6 +78,7 @@ fn start_zellij(channel: &mut ssh2::Channel) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
channel.flush().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) {
|
fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
|
||||||
|
|
@ -90,6 +93,7 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
channel.flush().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) {
|
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();
|
.unwrap();
|
||||||
channel.flush().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) {
|
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();
|
.unwrap();
|
||||||
channel.flush().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) {
|
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
|
||||||
|
|
@ -135,6 +141,7 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
channel.flush().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) {
|
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();
|
.unwrap();
|
||||||
channel.flush().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(
|
fn read_from_channel(
|
||||||
|
|
@ -174,6 +182,11 @@ fn read_from_channel(
|
||||||
let mut retries_left = 3;
|
let mut retries_left = 3;
|
||||||
let mut should_sleep = false;
|
let mut should_sleep = false;
|
||||||
let mut vte_parser = vte::Parser::new();
|
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(
|
let mut terminal_output = TerminalPane::new(
|
||||||
0,
|
0,
|
||||||
pane_geom,
|
pane_geom,
|
||||||
|
|
@ -181,8 +194,10 @@ fn read_from_channel(
|
||||||
0,
|
0,
|
||||||
String::new(),
|
String::new(),
|
||||||
Rc::new(RefCell::new(LinkHandler::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(Palette::default())),
|
||||||
|
Rc::new(RefCell::new(HashMap::new())),
|
||||||
); // 0 is the pane index
|
); // 0 is the pane index
|
||||||
loop {
|
loop {
|
||||||
if !should_keep_running.load(Ordering::SeqCst) {
|
if !should_keep_running.load(Ordering::SeqCst) {
|
||||||
|
|
@ -322,6 +337,7 @@ impl RemoteTerminal {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
channel.flush().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) {
|
pub fn load_fixture(&mut self, name: &str) {
|
||||||
let mut channel = self.channel.lock().unwrap();
|
let mut channel = self.channel.lock().unwrap();
|
||||||
|
|
|
||||||
1
src/tests/fixtures/sixel-image-100px.six
vendored
Normal file
1
src/tests/fixtures/sixel-image-100px.six
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/tests/fixtures/sixel-image-500px.six
vendored
Normal file
1
src/tests/fixtures/sixel-image-500px.six
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/tests/fixtures/terminal_emulator_startup_response
vendored
Normal file
1
src/tests/fixtures/terminal_emulator_startup_response
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -2,12 +2,13 @@
|
||||||
//! and dispatch actions, that are specified through the command line.
|
//! and dispatch actions, that are specified through the command line.
|
||||||
//! Multiple actions at the same time can be dispatched.
|
//! Multiple actions at the same time can be dispatched.
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{fs, path::PathBuf, thread};
|
use std::{fs, path::PathBuf, thread};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_is_executing::CommandIsExecuting, input_handler::input_actions,
|
command_is_executing::CommandIsExecuting, input_handler::input_actions,
|
||||||
os_input_output::ClientOsApi, stdin_handler::stdin_loop, ClientInfo, ClientInstruction,
|
os_input_output::ClientOsApi, stdin_ansi_parser::StdinAnsiParser, stdin_handler::stdin_loop,
|
||||||
InputInstruction,
|
ClientInfo, ClientInstruction, InputInstruction,
|
||||||
};
|
};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
channels::{self, ChannelWithContext, SenderWithContext},
|
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()
|
let _stdin_thread = thread::Builder::new()
|
||||||
.name("stdin_handler".to_string())
|
.name("stdin_handler".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
let send_input_instructions = send_input_instructions.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<ClientId>;
|
let clients: Vec<ClientId>;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
//! Main input logic.
|
//! Main input logic.
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ClientOsApi,
|
os_input_output::ClientOsApi, stdin_ansi_parser::AnsiStdinInstruction, ClientId,
|
||||||
stdin_ansi_parser::{AnsiStdinInstructionOrKeys, StdinAnsiParser},
|
ClientInstruction, CommandIsExecuting, InputInstruction,
|
||||||
ClientId, ClientInstruction, CommandIsExecuting, InputInstruction,
|
|
||||||
};
|
};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
channels::{Receiver, SenderWithContext, OPENCALLS},
|
channels::{Receiver, SenderWithContext, OPENCALLS},
|
||||||
|
|
@ -69,19 +68,6 @@ impl InputHandler {
|
||||||
if self.options.mouse_mode.unwrap_or(true) {
|
if self.options.mouse_mode.unwrap_or(true) {
|
||||||
self.os_input.enable_mouse();
|
self.os_input.enable_mouse();
|
||||||
}
|
}
|
||||||
// <ESC>[14t => get text area size in pixels,
|
|
||||||
// <ESC>[16t => get character cell size in pixels
|
|
||||||
// <ESC>]11;?<ESC>\ => get background color
|
|
||||||
// <ESC>]10;?<ESC>\ => 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 {
|
loop {
|
||||||
if self.should_exit {
|
if self.should_exit {
|
||||||
break;
|
break;
|
||||||
|
|
@ -91,13 +77,7 @@ impl InputHandler {
|
||||||
match input_event {
|
match input_event {
|
||||||
InputEvent::Key(key_event) => {
|
InputEvent::Key(key_event) => {
|
||||||
let key = cast_termwiz_key(key_event, &raw_bytes);
|
let key = cast_termwiz_key(key_event, &raw_bytes);
|
||||||
if ansi_stdin_parser.expected_instructions() > 0 {
|
self.handle_key(&key, raw_bytes);
|
||||||
self.handle_possible_pixel_instruction(
|
|
||||||
ansi_stdin_parser.parse(key, raw_bytes),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.handle_key(&key, raw_bytes);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
InputEvent::Mouse(mouse_event) => {
|
InputEvent::Mouse(mouse_event) => {
|
||||||
let mouse_event =
|
let mouse_event =
|
||||||
|
|
@ -126,13 +106,13 @@ impl InputHandler {
|
||||||
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
|
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
|
||||||
self.mode = input_mode;
|
self.mode = input_mode;
|
||||||
},
|
},
|
||||||
Ok((InputInstruction::PossiblePixelRatioChange, _error_context)) => {
|
Ok((
|
||||||
let _ = self
|
InputInstruction::AnsiStdinInstructions(ansi_stdin_instructions),
|
||||||
.os_input
|
_error_context,
|
||||||
.get_stdout_writer()
|
)) => {
|
||||||
.write(get_cell_pixel_info.as_bytes())
|
for ansi_instruction in ansi_stdin_instructions {
|
||||||
.unwrap();
|
self.handle_stdin_ansi_instruction(ansi_instruction);
|
||||||
ansi_stdin_parser.increment_expected_ansi_instructions(4);
|
}
|
||||||
},
|
},
|
||||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||||
}
|
}
|
||||||
|
|
@ -147,33 +127,28 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn handle_possible_pixel_instruction(
|
fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) {
|
||||||
&mut self,
|
match ansi_stdin_instructions {
|
||||||
pixel_instruction_or_keys: Option<AnsiStdinInstructionOrKeys>,
|
AnsiStdinInstruction::PixelDimensions(pixel_dimensions) => {
|
||||||
) {
|
|
||||||
match pixel_instruction_or_keys {
|
|
||||||
Some(AnsiStdinInstructionOrKeys::PixelDimensions(pixel_dimensions)) => {
|
|
||||||
self.os_input
|
self.os_input
|
||||||
.send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
|
.send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
|
||||||
},
|
},
|
||||||
Some(AnsiStdinInstructionOrKeys::BackgroundColor(background_color_instruction)) => {
|
AnsiStdinInstruction::BackgroundColor(background_color_instruction) => {
|
||||||
self.os_input
|
self.os_input
|
||||||
.send_to_server(ClientToServerMsg::BackgroundColor(
|
.send_to_server(ClientToServerMsg::BackgroundColor(
|
||||||
background_color_instruction,
|
background_color_instruction,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
Some(AnsiStdinInstructionOrKeys::ForegroundColor(foreground_color_instruction)) => {
|
AnsiStdinInstruction::ForegroundColor(foreground_color_instruction) => {
|
||||||
self.os_input
|
self.os_input
|
||||||
.send_to_server(ClientToServerMsg::ForegroundColor(
|
.send_to_server(ClientToServerMsg::ForegroundColor(
|
||||||
foreground_color_instruction,
|
foreground_color_instruction,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
Some(AnsiStdinInstructionOrKeys::Keys(keys)) => {
|
AnsiStdinInstruction::ColorRegisters(color_registers) => {
|
||||||
for (key, raw_bytes) in keys {
|
self.os_input
|
||||||
self.handle_key(&key, raw_bytes);
|
.send_to_server(ClientToServerMsg::ColorRegisters(color_registers));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
|
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
|
||||||
|
|
@ -352,7 +327,6 @@ pub(crate) fn input_loop(
|
||||||
)
|
)
|
||||||
.handle_input();
|
.handle_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
||||||
/// its [`InputHandler::handle_input()`] loop.
|
/// its [`InputHandler::handle_input()`] loop.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|
@ -379,7 +353,3 @@ pub(crate) fn input_actions(
|
||||||
)
|
)
|
||||||
.handle_actions(actions, &session_name, clients);
|
.handle_actions(actions, &session_name, clients);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "./unit/input_handler_tests.rs"]
|
|
||||||
mod input_handler_tests;
|
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ use std::env::current_exe;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser};
|
||||||
use crate::{
|
use crate::{
|
||||||
command_is_executing::CommandIsExecuting, input_handler::input_loop,
|
command_is_executing::CommandIsExecuting, input_handler::input_loop,
|
||||||
os_input_output::ClientOsApi, stdin_handler::stdin_loop,
|
os_input_output::ClientOsApi, stdin_handler::stdin_loop,
|
||||||
|
|
@ -114,7 +116,7 @@ impl ClientInfo {
|
||||||
pub(crate) enum InputInstruction {
|
pub(crate) enum InputInstruction {
|
||||||
KeyEvent(InputEvent, Vec<u8>),
|
KeyEvent(InputEvent, Vec<u8>),
|
||||||
SwitchToMode(InputMode),
|
SwitchToMode(InputMode),
|
||||||
PossiblePixelRatioChange,
|
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_client(
|
pub fn start_client(
|
||||||
|
|
@ -126,7 +128,7 @@ pub fn start_client(
|
||||||
layout: Option<LayoutFromYaml>,
|
layout: Option<LayoutFromYaml>,
|
||||||
) {
|
) {
|
||||||
info!("Starting Zellij client!");
|
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 take_snapshot = "\u{1b}[?1049h";
|
||||||
let bracketed_paste = "\u{1b}[?2004h";
|
let bracketed_paste = "\u{1b}[?2004h";
|
||||||
os_input.unset_raw_mode(0).unwrap();
|
os_input.unset_raw_mode(0).unwrap();
|
||||||
|
|
@ -162,11 +164,13 @@ pub fn start_client(
|
||||||
let first_msg = match info {
|
let first_msg = match info {
|
||||||
ClientInfo::Attach(name, config_options) => {
|
ClientInfo::Attach(name, config_options) => {
|
||||||
envs::set_session_name(name);
|
envs::set_session_name(name);
|
||||||
|
envs::set_initial_environment_vars();
|
||||||
|
|
||||||
ClientToServerMsg::AttachClient(client_attributes, config_options)
|
ClientToServerMsg::AttachClient(client_attributes, config_options)
|
||||||
},
|
},
|
||||||
ClientInfo::New(name) => {
|
ClientInfo::New(name) => {
|
||||||
envs::set_session_name(name);
|
envs::set_session_name(name);
|
||||||
|
envs::set_initial_environment_vars();
|
||||||
|
|
||||||
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
|
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 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()
|
let _stdin_thread = thread::Builder::new()
|
||||||
.name("stdin_handler".to_string())
|
.name("stdin_handler".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
let send_input_instructions = send_input_instructions.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()
|
let _input_thread = thread::Builder::new()
|
||||||
|
|
@ -246,7 +252,6 @@ pub fn start_client(
|
||||||
let _signal_thread = thread::Builder::new()
|
let _signal_thread = thread::Builder::new()
|
||||||
.name("signal_listener".to_string())
|
.name("signal_listener".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let send_input_instructions = send_input_instructions.clone();
|
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
move || {
|
move || {
|
||||||
os_input.handle_signals(
|
os_input.handle_signals(
|
||||||
|
|
@ -256,8 +261,16 @@ pub fn start_client(
|
||||||
os_api.send_to_server(ClientToServerMsg::TerminalResize(
|
os_api.send_to_server(ClientToServerMsg::TerminalResize(
|
||||||
os_api.get_terminal_size_using_fd(0),
|
os_api.get_terminal_size_using_fd(0),
|
||||||
));
|
));
|
||||||
let _ = send_input_instructions
|
// send a query to the terminal emulator in case the font size changed
|
||||||
.send(InputInstruction::PossiblePixelRatioChange);
|
// 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({
|
Box::new({
|
||||||
|
|
@ -385,3 +398,7 @@ pub fn start_client(
|
||||||
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "./unit/stdin_tests.rs"]
|
||||||
|
mod stdin_tests;
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ pub trait ClientOsApi: Send + Sync {
|
||||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||||
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
|
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
|
||||||
/// Returns the raw contents of standard input.
|
/// Returns the raw contents of standard input.
|
||||||
fn read_from_stdin(&self) -> Vec<u8>;
|
fn read_from_stdin(&mut self) -> Vec<u8>;
|
||||||
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
||||||
/// Sends a message to the server.
|
/// Sends a message to the server.
|
||||||
|
|
@ -126,7 +126,7 @@ impl ClientOsApi for ClientOsInputOutput {
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
fn read_from_stdin(&self) -> Vec<u8> {
|
fn read_from_stdin(&mut self) -> Vec<u8> {
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
let mut stdin = stdin.lock();
|
let mut stdin = stdin.lock();
|
||||||
let buffer = stdin.fill_buf().unwrap();
|
let buffer = stdin.fill_buf().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -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::{
|
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 {
|
pub struct StdinAnsiParser {
|
||||||
expected_ansi_instructions: usize,
|
raw_buffer: Vec<u8>,
|
||||||
current_buffer: Vec<(Key, Vec<u8>)>,
|
pending_color_sequences: Vec<(usize, String)>,
|
||||||
|
pending_events: Vec<AnsiStdinInstruction>,
|
||||||
|
parse_deadline: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdinAnsiParser {
|
impl StdinAnsiParser {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
StdinAnsiParser {
|
StdinAnsiParser {
|
||||||
expected_ansi_instructions: 0,
|
raw_buffer: vec![],
|
||||||
current_buffer: vec![],
|
pending_color_sequences: vec![],
|
||||||
|
pending_events: vec![],
|
||||||
|
parse_deadline: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn increment_expected_ansi_instructions(&mut self, to: usize) {
|
pub fn terminal_emulator_query_string(&mut self) -> String {
|
||||||
self.expected_ansi_instructions += to;
|
// note that this assumes the String will be sent to the terminal emulator and so starts a
|
||||||
|
// deadline timeout (self.parse_deadline)
|
||||||
|
|
||||||
|
// <ESC>[14t => get text area size in pixels,
|
||||||
|
// <ESC>[16t => get character cell size in pixels
|
||||||
|
// <ESC>]11;?<ESC>\ => get background color
|
||||||
|
// <ESC>]10;?<ESC>\ => 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. <ESC>]4;5;?<ESC>\ => 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) {
|
pub fn window_size_change_query_string(&mut self) -> String {
|
||||||
self.expected_ansi_instructions = self.expected_ansi_instructions.saturating_sub(by);
|
// note that this assumes the String will be sent to the terminal emulator and so starts a
|
||||||
|
// deadline timeout (self.parse_deadline)
|
||||||
|
|
||||||
|
// <ESC>[14t => get text area size in pixels,
|
||||||
|
// <ESC>[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 {
|
fn drain_pending_events(&mut self) -> Vec<AnsiStdinInstruction> {
|
||||||
self.expected_ansi_instructions
|
let mut events = vec![];
|
||||||
}
|
events.append(&mut self.pending_events);
|
||||||
pub fn parse(&mut self, key: Key, raw_bytes: Vec<u8>) -> Option<AnsiStdinInstructionOrKeys> {
|
if let Some(color_registers) =
|
||||||
if self.current_buffer.is_empty()
|
AnsiStdinInstruction::color_registers_from_bytes(&mut self.pending_color_sequences)
|
||||||
&& (key != Key::Esc && key != Key::Alt(CharOrArrow::Char(']')))
|
|
||||||
{
|
{
|
||||||
// the first key of a sequence is always Esc, but termwiz interprets esc + ] as Alt+]
|
events.push(color_registers);
|
||||||
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
|
||||||
}
|
}
|
||||||
fn key_is_valid(&self, key: Key) -> bool {
|
pub fn should_parse(&self) -> bool {
|
||||||
match key {
|
if let Some(parse_deadline) = self.parse_deadline {
|
||||||
Key::Esc => {
|
if parse_deadline >= Instant::now() {
|
||||||
// this is a UX improvement
|
return true;
|
||||||
// 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
|
false
|
||||||
!self.current_buffer.iter().any(|(key, _)| *key == Key::Esc)
|
}
|
||||||
},
|
pub fn parse(&mut self, mut raw_bytes: Vec<u8>) -> Vec<AnsiStdinInstruction> {
|
||||||
Key::Char(';')
|
for byte in raw_bytes.drain(..) {
|
||||||
| Key::Char('[')
|
self.parse_byte(byte);
|
||||||
| Key::Char(']')
|
}
|
||||||
| Key::Char('r')
|
self.drain_pending_events()
|
||||||
| Key::Char('g')
|
}
|
||||||
| Key::Char('b')
|
fn parse_byte(&mut self, byte: u8) {
|
||||||
| Key::Char('\\')
|
if byte == b't' {
|
||||||
| Key::Char(':')
|
self.raw_buffer.push(byte);
|
||||||
| Key::Char('/') => true,
|
match AnsiStdinInstruction::pixel_dimensions_from_bytes(&self.raw_buffer) {
|
||||||
Key::Alt(CharOrArrow::Char(']')) => true,
|
Ok(ansi_sequence) => {
|
||||||
Key::Alt(CharOrArrow::Char('\\')) => true,
|
self.pending_events.push(ansi_sequence);
|
||||||
Key::Char(c) => {
|
self.raw_buffer.clear();
|
||||||
matches!(c, '0'..='9' | 'a'..='f')
|
},
|
||||||
},
|
Err(_) => {
|
||||||
_ => false,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AnsiStdinInstructionOrKeys {
|
pub enum AnsiStdinInstruction {
|
||||||
PixelDimensions(PixelDimensions),
|
PixelDimensions(PixelDimensions),
|
||||||
BackgroundColor(String),
|
BackgroundColor(String),
|
||||||
ForegroundColor(String),
|
ForegroundColor(String),
|
||||||
Keys(Vec<(Key, Vec<u8>)>),
|
ColorRegisters(Vec<(usize, String)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnsiStdinInstructionOrKeys {
|
impl AnsiStdinInstruction {
|
||||||
pub fn pixel_dimensions_from_keys(keys: &[(Key, Vec<u8>)]) -> Result<Self, &'static str> {
|
pub fn pixel_dimensions_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
|
||||||
|
// eg. <ESC>[4;21;8t
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new(r"^\u{1b}\[(\d+);(\d+);(\d+)t$").unwrap();
|
static ref RE: Regex = Regex::new(r"^\u{1b}\[(\d+);(\d+);(\d+)t$").unwrap();
|
||||||
}
|
}
|
||||||
let key_sequence: Vec<Option<char>> = keys
|
let key_string = String::from_utf8_lossy(bytes); // TODO: handle error
|
||||||
.iter()
|
let captures = RE
|
||||||
.map(|(key, _)| match key {
|
.captures_iter(&key_string)
|
||||||
Key::Char(c) => Some(*c),
|
.next()
|
||||||
Key::Esc => Some('\u{1b}'),
|
.ok_or("invalid_instruction")?;
|
||||||
_ => None,
|
let csi_index = captures[1].parse::<usize>();
|
||||||
})
|
let first_field = captures[2].parse::<usize>();
|
||||||
.collect();
|
let second_field = captures[3].parse::<usize>();
|
||||||
if key_sequence.iter().all(|k| k.is_some()) {
|
if csi_index.is_err() || first_field.is_err() || second_field.is_err() {
|
||||||
let key_string: String = key_sequence.iter().map(|k| k.unwrap()).collect();
|
return Err("invalid_instruction");
|
||||||
let captures = RE
|
}
|
||||||
.captures_iter(&key_string)
|
match csi_index {
|
||||||
.next()
|
Ok(4) => {
|
||||||
.ok_or("invalid_instruction")?;
|
// text area size
|
||||||
let csi_index = captures[1].parse::<usize>();
|
Ok(AnsiStdinInstruction::PixelDimensions(PixelDimensions {
|
||||||
let first_field = captures[2].parse::<usize>();
|
character_cell_size: None,
|
||||||
let second_field = captures[3].parse::<usize>();
|
text_area_size: Some(SizeInPixels {
|
||||||
if csi_index.is_err() || first_field.is_err() || second_field.is_err() {
|
height: first_field.unwrap(),
|
||||||
return Err("invalid_instruction");
|
width: second_field.unwrap(),
|
||||||
}
|
}),
|
||||||
match csi_index {
|
}))
|
||||||
Ok(4) => {
|
},
|
||||||
// text area size
|
Ok(6) => {
|
||||||
Ok(AnsiStdinInstructionOrKeys::PixelDimensions(
|
// character cell size
|
||||||
PixelDimensions {
|
Ok(AnsiStdinInstruction::PixelDimensions(PixelDimensions {
|
||||||
character_cell_size: None,
|
character_cell_size: Some(SizeInPixels {
|
||||||
text_area_size: Some(SizeInPixels {
|
height: first_field.unwrap(),
|
||||||
height: first_field.unwrap(),
|
width: second_field.unwrap(),
|
||||||
width: second_field.unwrap(),
|
}),
|
||||||
}),
|
text_area_size: None,
|
||||||
},
|
}))
|
||||||
))
|
},
|
||||||
},
|
_ => Err("invalid sequence"),
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn color_sequence_from_keys(keys: &[(Key, Vec<u8>)]) -> Result<Self, &'static str> {
|
pub fn bg_or_fg_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
|
||||||
|
// eg. <ESC>]11;rgb:0000/0000/0000\
|
||||||
lazy_static! {
|
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. <ESC>]10;rgb:ffff/ffff/ffff\
|
||||||
lazy_static! {
|
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, _)| {
|
let key_string = String::from_utf8_lossy(bytes);
|
||||||
if let Key::Char(c) = key {
|
|
||||||
acc.push(*c)
|
|
||||||
};
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
if let Some(captures) = BACKGROUND_RE.captures_iter(&key_string).next() {
|
if let Some(captures) = BACKGROUND_RE.captures_iter(&key_string).next() {
|
||||||
let background_query_response = captures[1].parse::<String>();
|
let background_query_response = captures[1].parse::<String>();
|
||||||
Ok(AnsiStdinInstructionOrKeys::BackgroundColor(
|
match background_query_response {
|
||||||
background_query_response.unwrap(),
|
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() {
|
} else if let Some(captures) = FOREGROUND_RE.captures_iter(&key_string).next() {
|
||||||
let foreground_query_response = captures[1].parse::<String>();
|
let foreground_query_response = captures[1].parse::<String>();
|
||||||
Ok(AnsiStdinInstructionOrKeys::ForegroundColor(
|
match foreground_query_response {
|
||||||
foreground_query_response.unwrap(),
|
Ok(foreground_query_response) => Ok(AnsiStdinInstruction::ForegroundColor(
|
||||||
))
|
foreground_query_response,
|
||||||
|
)),
|
||||||
|
_ => Err("invalid_instruction"),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("invalid_instruction")
|
Err("invalid_instruction")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn color_registers_from_bytes(color_sequences: &mut Vec<(usize, String)>) -> Option<Self> {
|
||||||
|
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::<usize>();
|
||||||
|
let color_response = captures[2].parse::<String>();
|
||||||
|
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::<usize>();
|
||||||
|
let color_response = captures[2].parse::<String>();
|
||||||
|
match (color_register_response, color_response) {
|
||||||
|
(Ok(crr), Ok(cr)) => Ok((crr, cr)),
|
||||||
|
_ => Err("invalid_instruction"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("invalid_instruction")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,46 @@
|
||||||
use crate::os_input_output::ClientOsApi;
|
use crate::os_input_output::ClientOsApi;
|
||||||
|
use crate::stdin_ansi_parser::StdinAnsiParser;
|
||||||
use crate::InputInstruction;
|
use crate::InputInstruction;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use zellij_utils::channels::SenderWithContext;
|
use zellij_utils::channels::SenderWithContext;
|
||||||
use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons};
|
use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons};
|
||||||
|
|
||||||
pub(crate) fn stdin_loop(
|
pub(crate) fn stdin_loop(
|
||||||
os_input: Box<dyn ClientOsApi>,
|
mut os_input: Box<dyn ClientOsApi>,
|
||||||
send_input_instructions: SenderWithContext<InputInstruction>,
|
send_input_instructions: SenderWithContext<InputInstruction>,
|
||||||
|
stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>,
|
||||||
) {
|
) {
|
||||||
let mut holding_mouse = false;
|
let mut holding_mouse = false;
|
||||||
let mut input_parser = InputParser::new();
|
let mut input_parser = InputParser::new();
|
||||||
let mut current_buffer = vec![];
|
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 {
|
loop {
|
||||||
let buf = os_input.read_from_stdin();
|
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());
|
current_buffer.append(&mut buf.to_vec());
|
||||||
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
||||||
let mut events = vec![];
|
let mut events = vec![];
|
||||||
|
|
|
||||||
|
|
@ -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<Mutex<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
impl FakeStdoutWriter {
|
|
||||||
pub fn new(buffer: Arc<Mutex<Vec<u8>>>) -> Self {
|
|
||||||
FakeStdoutWriter { buffer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl io::Write for FakeStdoutWriter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
|
||||||
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<Mutex<Vec<ClientToServerMsg>>>,
|
|
||||||
command_is_executing: Arc<Mutex<CommandIsExecuting>>,
|
|
||||||
stdout_buffer: Arc<Mutex<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FakeClientOsApi {
|
|
||||||
pub fn new(
|
|
||||||
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
|
|
||||||
command_is_executing: CommandIsExecuting,
|
|
||||||
) -> Self {
|
|
||||||
// while command_is_executing itself is implemented with an Arc<Mutex>, we have to have an
|
|
||||||
// Arc<Mutex> 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<u8> {
|
|
||||||
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<dyn io::Write> {
|
|
||||||
let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone());
|
|
||||||
Box::new(fake_stdout_writer)
|
|
||||||
}
|
|
||||||
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn read_from_stdin(&self) -> Vec<u8> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
|
||||||
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<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
|
|
||||||
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<Mutex<Vec<ClientToServerMsg>>>,
|
|
||||||
) -> Vec<Action> {
|
|
||||||
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<Mutex<Vec<ClientToServerMsg>>>,
|
|
||||||
) -> Vec<PixelDimensions> {
|
|
||||||
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![],);
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
409
zellij-client/src/unit/stdin_tests.rs
Normal file
409
zellij-client/src/unit/stdin_tests.rs
Normal file
|
|
@ -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<u8> {
|
||||||
|
let mut path_to_file = std::path::PathBuf::new();
|
||||||
|
path_to_file.push("../src");
|
||||||
|
path_to_file.push("tests");
|
||||||
|
path_to_file.push("fixtures");
|
||||||
|
path_to_file.push(fixture_name);
|
||||||
|
std::fs::read(path_to_file)
|
||||||
|
.unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Mutex<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
impl FakeStdoutWriter {
|
||||||
|
pub fn new(buffer: Arc<Mutex<Vec<u8>>>) -> Self {
|
||||||
|
FakeStdoutWriter { buffer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl io::Write for FakeStdoutWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||||
|
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<Mutex<Vec<ClientToServerMsg>>>,
|
||||||
|
command_is_executing: Arc<Mutex<CommandIsExecuting>>,
|
||||||
|
stdout_buffer: Arc<Mutex<Vec<u8>>>,
|
||||||
|
stdin_buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeClientOsApi {
|
||||||
|
pub fn new(
|
||||||
|
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
|
||||||
|
command_is_executing: CommandIsExecuting,
|
||||||
|
) -> Self {
|
||||||
|
// while command_is_executing itself is implemented with an Arc<Mutex>, we have to have an
|
||||||
|
// Arc<Mutex> 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<u8>) -> Self {
|
||||||
|
self.stdin_buffer = stdin_buffer;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn stdout_buffer(&self) -> Vec<u8> {
|
||||||
|
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<dyn io::Write> {
|
||||||
|
let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone());
|
||||||
|
Box::new(fake_stdout_writer)
|
||||||
|
}
|
||||||
|
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn read_from_stdin(&mut self) -> Vec<u8> {
|
||||||
|
self.stdin_buffer.drain(..).collect()
|
||||||
|
}
|
||||||
|
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||||
|
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<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
|
||||||
|
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<Mutex<Vec<ClientToServerMsg>>>,
|
||||||
|
) -> Vec<Action> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,9 @@ typetag = "0.1.7"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
close_fds = "0.3.2"
|
close_fds = "0.3.2"
|
||||||
sysinfo = "0.22.5"
|
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"] }
|
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::panes::selection::Selection;
|
||||||
use crate::panes::Row;
|
use crate::panes::Row;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
panes::sixel::SixelImageStore,
|
||||||
panes::terminal_character::{AnsiCode, CharacterStyles},
|
panes::terminal_character::{AnsiCode, CharacterStyles},
|
||||||
panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||||
ClientId,
|
ClientId,
|
||||||
|
|
@ -16,6 +17,7 @@ use std::{
|
||||||
str,
|
str,
|
||||||
};
|
};
|
||||||
use zellij_utils::pane_size::PaneGeom;
|
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) {
|
fn vte_goto_instruction(x_coords: usize, y_coords: usize, vte_output: &mut String) {
|
||||||
write!(
|
write!(
|
||||||
|
|
@ -54,7 +56,6 @@ fn write_changed_styles(
|
||||||
if let Some(new_styles) =
|
if let Some(new_styles) =
|
||||||
character_styles.update_and_return_diff(¤t_character_styles, chunk_changed_colors)
|
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) =
|
if let Some(osc8_link) =
|
||||||
link_handler.and_then(|l_h| l_h.output_osc8(new_styles.link_anchor))
|
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<CharacterChunk>,
|
character_chunks: Vec<CharacterChunk>,
|
||||||
|
sixel_chunks: Option<&Vec<SixelImageChunk>>,
|
||||||
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
||||||
|
sixel_image_store: &mut SixelImageStore,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut vte_output = String::new(); // TODO: preallocate character_chunks.len()?
|
let mut vte_output = String::new();
|
||||||
|
let mut sixel_vte: Option<String> = None;
|
||||||
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
||||||
for character_chunk in character_chunks {
|
for character_chunk in character_chunks {
|
||||||
let chunk_selection_and_background_color = character_chunk.selection_and_background_color();
|
let chunk_selection_and_background_color = character_chunk.selection_and_background_color();
|
||||||
|
|
@ -96,6 +100,32 @@ fn serialize_character_chunks(
|
||||||
}
|
}
|
||||||
character_styles.clear();
|
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
|
vte_output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,11 +178,24 @@ pub struct Output {
|
||||||
pre_vte_instructions: HashMap<ClientId, Vec<String>>,
|
pre_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||||
post_vte_instructions: HashMap<ClientId, Vec<String>>,
|
post_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||||
client_character_chunks: HashMap<ClientId, Vec<CharacterChunk>>,
|
client_character_chunks: HashMap<ClientId, Vec<CharacterChunk>>,
|
||||||
|
sixel_chunks: HashMap<ClientId, Vec<SixelImageChunk>>,
|
||||||
link_handler: Option<Rc<RefCell<LinkHandler>>>,
|
link_handler: Option<Rc<RefCell<LinkHandler>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
floating_panes_stack: Option<FloatingPanesStack>,
|
floating_panes_stack: Option<FloatingPanesStack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
|
pub fn new(
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
) -> Self {
|
||||||
|
Output {
|
||||||
|
sixel_image_store,
|
||||||
|
character_cell_size,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn add_clients(
|
pub fn add_clients(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_ids: &HashSet<ClientId>,
|
client_ids: &HashSet<ClientId>,
|
||||||
|
|
@ -240,6 +283,48 @@ impl Output {
|
||||||
.or_insert_with(Vec::new);
|
.or_insert_with(Vec::new);
|
||||||
entry.push(String::from(vte_instruction));
|
entry.push(String::from(vte_instruction));
|
||||||
}
|
}
|
||||||
|
pub fn add_sixel_image_chunks_to_client(
|
||||||
|
&mut self,
|
||||||
|
client_id: ClientId,
|
||||||
|
sixel_image_chunks: Vec<SixelImageChunk>,
|
||||||
|
z_index: Option<usize>,
|
||||||
|
) {
|
||||||
|
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<SixelImageChunk>,
|
||||||
|
client_ids: impl Iterator<Item = ClientId>,
|
||||||
|
z_index: Option<usize>,
|
||||||
|
) {
|
||||||
|
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<ClientId, String> {
|
pub fn serialize(&mut self) -> HashMap<ClientId, String> {
|
||||||
let mut serialized_render_instructions = HashMap::new();
|
let mut serialized_render_instructions = HashMap::new();
|
||||||
|
|
||||||
|
|
@ -256,9 +341,11 @@ impl Output {
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the actual vte
|
// append the actual vte
|
||||||
client_serialized_render_instructions.push_str(&serialize_character_chunks(
|
client_serialized_render_instructions.push_str(&serialize_chunks(
|
||||||
client_character_chunks,
|
client_character_chunks,
|
||||||
|
self.sixel_chunks.get(&client_id),
|
||||||
self.link_handler.as_mut(),
|
self.link_handler.as_mut(),
|
||||||
|
&mut self.sixel_image_store.borrow_mut(),
|
||||||
)); // TODO: less allocations?
|
)); // TODO: less allocations?
|
||||||
|
|
||||||
// append post-vte instructions for this client
|
// append post-vte instructions for this client
|
||||||
|
|
@ -319,6 +406,26 @@ impl FloatingPanesStack {
|
||||||
}
|
}
|
||||||
visible_chunks
|
visible_chunks
|
||||||
}
|
}
|
||||||
|
pub fn visible_sixel_image_chunks(
|
||||||
|
&self,
|
||||||
|
mut sixel_image_chunks: Vec<SixelImageChunk>,
|
||||||
|
z_index: Option<usize>,
|
||||||
|
character_cell_size: &SizeInPixels,
|
||||||
|
) -> Vec<SixelImageChunk> {
|
||||||
|
let z_index = z_index.unwrap_or(0);
|
||||||
|
let mut chunks_to_check: Vec<SixelImageChunk> = 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<SixelImageChunk> =
|
||||||
|
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(
|
fn remove_covered_parts(
|
||||||
&self,
|
&self,
|
||||||
pane_geom: &PaneGeom,
|
pane_geom: &PaneGeom,
|
||||||
|
|
@ -368,6 +475,165 @@ impl FloatingPanesStack {
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
fn remove_covered_sixel_parts(
|
||||||
|
&self,
|
||||||
|
pane_geom: &PaneGeom,
|
||||||
|
s_chunk: &SixelImageChunk,
|
||||||
|
character_cell_size: &SizeInPixels,
|
||||||
|
) -> Vec<SixelImageChunk> {
|
||||||
|
// 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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -379,6 +645,17 @@ pub struct CharacterChunk {
|
||||||
selection_and_background_color: Option<(Selection, AnsiCode)>,
|
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 {
|
impl CharacterChunk {
|
||||||
pub fn new(terminal_characters: Vec<TerminalCharacter>, x: usize, y: usize) -> Self {
|
pub fn new(terminal_characters: Vec<TerminalCharacter>, x: usize, y: usize) -> Self {
|
||||||
CharacterChunk {
|
CharacterChunk {
|
||||||
|
|
@ -480,8 +757,8 @@ impl CharacterChunk {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OutputBuffer {
|
pub struct OutputBuffer {
|
||||||
changed_lines: Vec<usize>, // line index
|
pub changed_lines: Vec<usize>, // line index
|
||||||
should_update_all_lines: bool,
|
pub should_update_all_lines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OutputBuffer {
|
impl Default for OutputBuffer {
|
||||||
|
|
@ -520,6 +797,7 @@ impl OutputBuffer {
|
||||||
for line_index in 0..viewport_height {
|
for line_index in 0..viewport_height {
|
||||||
let terminal_characters =
|
let terminal_characters =
|
||||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
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 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;
|
let y = line_index + y_offset;
|
||||||
changed_chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
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<usize, usize> {
|
||||||
|
// 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<usize, usize> = HashMap::new(); // <start_line_index, line_count>
|
||||||
|
let mut last_changed_line_index: Option<usize> = 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
use super::sixel::{PixelRect, SixelGrid, SixelImageStore};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
use zellij_utils::regex::Regex;
|
use zellij_utils::regex::Regex;
|
||||||
|
|
@ -24,7 +26,7 @@ pub const MAX_TITLE_STACK_SIZE: usize = 1000;
|
||||||
use vte::{Params, Perform};
|
use vte::{Params, Perform};
|
||||||
use zellij_utils::{consts::VERSION, shared::version_number};
|
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::alacritty_functions::{parse_number, xparse_color};
|
||||||
use crate::panes::link_handler::LinkHandler;
|
use crate::panes::link_handler::LinkHandler;
|
||||||
use crate::panes::selection::Selection;
|
use crate::panes::selection::Selection;
|
||||||
|
|
@ -112,6 +114,7 @@ fn get_top_canonical_row_and_wraps(rows: &mut Vec<Row>) -> Vec<Row> {
|
||||||
fn transfer_rows_from_lines_above_to_viewport(
|
fn transfer_rows_from_lines_above_to_viewport(
|
||||||
lines_above: &mut VecDeque<Row>,
|
lines_above: &mut VecDeque<Row>,
|
||||||
viewport: &mut Vec<Row>,
|
viewport: &mut Vec<Row>,
|
||||||
|
sixel_grid: &mut SixelGrid,
|
||||||
count: usize,
|
count: usize,
|
||||||
max_viewport_width: usize,
|
max_viewport_width: usize,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
|
|
@ -143,7 +146,7 @@ fn transfer_rows_from_lines_above_to_viewport(
|
||||||
}
|
}
|
||||||
if !next_lines.is_empty() {
|
if !next_lines.is_empty() {
|
||||||
let excess_row = Row::from_rows(next_lines, 0);
|
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) {
|
match usize::try_from(lines_added_to_viewport) {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
|
|
@ -154,6 +157,7 @@ fn transfer_rows_from_lines_above_to_viewport(
|
||||||
fn transfer_rows_from_viewport_to_lines_above(
|
fn transfer_rows_from_viewport_to_lines_above(
|
||||||
viewport: &mut Vec<Row>,
|
viewport: &mut Vec<Row>,
|
||||||
lines_above: &mut VecDeque<Row>,
|
lines_above: &mut VecDeque<Row>,
|
||||||
|
sixel_grid: &mut SixelGrid,
|
||||||
count: usize,
|
count: usize,
|
||||||
max_viewport_width: usize,
|
max_viewport_width: usize,
|
||||||
) -> isize {
|
) -> isize {
|
||||||
|
|
@ -176,7 +180,7 @@ fn transfer_rows_from_viewport_to_lines_above(
|
||||||
break; // no more rows
|
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 {
|
if let Some(width) = dropped_line_width {
|
||||||
transferred_rows_count -=
|
transferred_rows_count -=
|
||||||
calculate_row_display_height(width, max_viewport_width) as isize;
|
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<Row>, value: Row) -> Option<usize> {
|
fn bounded_push(vec: &mut VecDeque<Row>, sixel_grid: &mut SixelGrid, value: Row) -> Option<usize> {
|
||||||
let mut dropped_line_width = None;
|
let mut dropped_line_width = None;
|
||||||
if vec.len() >= *SCROLL_BUFFER_SIZE.get().unwrap() {
|
if vec.len() >= *SCROLL_BUFFER_SIZE.get().unwrap() {
|
||||||
let line = vec.pop_front();
|
let line = vec.pop_front();
|
||||||
if let Some(line) = line {
|
if let Some(line) = line {
|
||||||
|
sixel_grid.offset_grid_top();
|
||||||
dropped_line_width = Some(line.width());
|
dropped_line_width = Some(line.width());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +303,7 @@ pub struct Grid {
|
||||||
viewport: Vec<Row>,
|
viewport: Vec<Row>,
|
||||||
lines_below: Vec<Row>,
|
lines_below: Vec<Row>,
|
||||||
horizontal_tabstops: BTreeSet<usize>,
|
horizontal_tabstops: BTreeSet<usize>,
|
||||||
alternate_lines_above_viewport_and_cursor: Option<(VecDeque<Row>, Vec<Row>, Cursor)>,
|
alternate_screen_state: Option<AlternateScreenState>,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
saved_cursor_position: Option<Cursor>,
|
saved_cursor_position: Option<Cursor>,
|
||||||
// FIXME: change scroll_region to be (usize, usize) - where the top line is always the first
|
// 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,
|
active_charset: CharsetIndex,
|
||||||
preceding_char: Option<TerminalCharacter>,
|
preceding_char: Option<TerminalCharacter>,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
output_buffer: OutputBuffer,
|
output_buffer: OutputBuffer,
|
||||||
title_stack: Vec<String>,
|
title_stack: Vec<String>,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_grid: SixelGrid,
|
||||||
pub changed_colors: Option<[Option<AnsiCode>; 256]>,
|
pub changed_colors: Option<[Option<AnsiCode>; 256]>,
|
||||||
pub should_render: bool,
|
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. "[D")
|
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "[D")
|
||||||
pub bracketed_paste_mode: bool, // when set, paste instructions to the terminal should be escaped with a special sequence
|
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 erasure_mode: bool, // ERM
|
||||||
|
pub sixel_scrolling: bool, // DECSDM
|
||||||
pub insert_mode: bool,
|
pub insert_mode: bool,
|
||||||
pub disable_linewrap: bool,
|
pub disable_linewrap: bool,
|
||||||
pub clear_viewport_before_rendering: bool,
|
pub clear_viewport_before_rendering: bool,
|
||||||
|
|
@ -334,7 +342,41 @@ pub struct Grid {
|
||||||
|
|
||||||
impl Debug for Grid {
|
impl Debug for Grid {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
for (i, row) in self.viewport.iter().enumerate() {
|
let mut buffer: Vec<Row> = 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 {
|
if row.is_canonical {
|
||||||
writeln!(f, "{:02?} (C): {:?}", i, row)?;
|
writeln!(f, "{:02?} (C): {:?}", i, row)?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -350,9 +392,12 @@ impl Grid {
|
||||||
rows: usize,
|
rows: usize,
|
||||||
columns: usize,
|
columns: usize,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
link_handler: Rc<RefCell<LinkHandler>>,
|
link_handler: Rc<RefCell<LinkHandler>>,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
|
||||||
Grid {
|
Grid {
|
||||||
lines_above: VecDeque::with_capacity(
|
lines_above: VecDeque::with_capacity(
|
||||||
// .get_or_init() is used instead of .get().unwrap() to prevent
|
// .get_or_init() is used instead of .get().unwrap() to prevent
|
||||||
|
|
@ -372,13 +417,15 @@ impl Grid {
|
||||||
cursor_key_mode: false,
|
cursor_key_mode: false,
|
||||||
bracketed_paste_mode: false,
|
bracketed_paste_mode: false,
|
||||||
erasure_mode: false,
|
erasure_mode: false,
|
||||||
|
sixel_scrolling: false,
|
||||||
insert_mode: false,
|
insert_mode: false,
|
||||||
disable_linewrap: false,
|
disable_linewrap: false,
|
||||||
alternate_lines_above_viewport_and_cursor: None,
|
alternate_screen_state: None,
|
||||||
clear_viewport_before_rendering: false,
|
clear_viewport_before_rendering: false,
|
||||||
active_charset: Default::default(),
|
active_charset: Default::default(),
|
||||||
pending_messages_to_pty: vec![],
|
pending_messages_to_pty: vec![],
|
||||||
terminal_emulator_colors,
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
output_buffer: Default::default(),
|
output_buffer: Default::default(),
|
||||||
selection: Default::default(),
|
selection: Default::default(),
|
||||||
title_stack: vec![],
|
title_stack: vec![],
|
||||||
|
|
@ -390,6 +437,7 @@ impl Grid {
|
||||||
scrollback_buffer_lines: 0,
|
scrollback_buffer_lines: 0,
|
||||||
mouse_mode: false,
|
mouse_mode: false,
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
|
sixel_grid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render_full_viewport(&mut self) {
|
pub fn render_full_viewport(&mut self) {
|
||||||
|
|
@ -534,6 +582,7 @@ impl Grid {
|
||||||
let transferred_rows_height = transfer_rows_from_lines_above_to_viewport(
|
let transferred_rows_height = transfer_rows_from_lines_above_to_viewport(
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
|
&mut self.sixel_grid,
|
||||||
1,
|
1,
|
||||||
self.width,
|
self.width,
|
||||||
);
|
);
|
||||||
|
|
@ -560,7 +609,8 @@ impl Grid {
|
||||||
last_line_above
|
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 {
|
if let Some(width) = dropped_line_width {
|
||||||
let dropped_line_height = calculate_row_display_height(width, self.width);
|
let dropped_line_height = calculate_row_display_height(width, self.width);
|
||||||
|
|
||||||
|
|
@ -607,7 +657,8 @@ impl Grid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.selection.reset();
|
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);
|
self.horizontal_tabstops = create_horizontal_tabstops(new_columns);
|
||||||
let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
|
let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
|
||||||
let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line();
|
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(
|
transfer_rows_from_lines_above_to_viewport(
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
|
&mut self.sixel_grid,
|
||||||
row_count_to_transfer,
|
row_count_to_transfer,
|
||||||
new_columns,
|
new_columns,
|
||||||
);
|
);
|
||||||
|
|
@ -713,6 +765,7 @@ impl Grid {
|
||||||
transfer_rows_from_viewport_to_lines_above(
|
transfer_rows_from_viewport_to_lines_above(
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
|
&mut self.sixel_grid,
|
||||||
row_count_to_transfer,
|
row_count_to_transfer,
|
||||||
new_columns,
|
new_columns,
|
||||||
);
|
);
|
||||||
|
|
@ -725,9 +778,7 @@ impl Grid {
|
||||||
saved_cursor_position.y = new_cursor_y;
|
saved_cursor_position.y = new_cursor_y;
|
||||||
saved_cursor_position.x = new_cursor_x;
|
saved_cursor_position.x = new_cursor_x;
|
||||||
};
|
};
|
||||||
} else if new_columns != self.width
|
} else if new_columns != self.width && self.alternate_screen_state.is_some() {
|
||||||
&& self.alternate_lines_above_viewport_and_cursor.is_some()
|
|
||||||
{
|
|
||||||
// in alternate screen just truncate exceeding width
|
// in alternate screen just truncate exceeding width
|
||||||
for row in &mut self.viewport {
|
for row in &mut self.viewport {
|
||||||
if row.width() >= new_columns {
|
if row.width() >= new_columns {
|
||||||
|
|
@ -744,6 +795,7 @@ impl Grid {
|
||||||
transfer_rows_from_lines_above_to_viewport(
|
transfer_rows_from_lines_above_to_viewport(
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
|
&mut self.sixel_grid,
|
||||||
row_count_to_transfer,
|
row_count_to_transfer,
|
||||||
new_columns,
|
new_columns,
|
||||||
);
|
);
|
||||||
|
|
@ -763,13 +815,16 @@ impl Grid {
|
||||||
} else {
|
} else {
|
||||||
self.cursor.y -= row_count_to_transfer;
|
self.cursor.y -= row_count_to_transfer;
|
||||||
if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
|
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(
|
transfer_rows_from_viewport_to_lines_above(
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
|
&mut self.sixel_grid,
|
||||||
row_count_to_transfer,
|
row_count_to_transfer,
|
||||||
new_columns,
|
new_columns,
|
||||||
);
|
);
|
||||||
|
|
@ -813,16 +868,34 @@ impl Grid {
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
pub fn read_changes(&mut self, x_offset: usize, y_offset: usize) -> Vec<CharacterChunk> {
|
pub fn read_changes(
|
||||||
let changes = self.output_buffer.changed_chunks_in_viewport(
|
&mut self,
|
||||||
|
x_offset: usize,
|
||||||
|
y_offset: usize,
|
||||||
|
) -> (Vec<CharacterChunk>, Vec<SixelImageChunk>) {
|
||||||
|
let changed_character_chunks = self.output_buffer.changed_chunks_in_viewport(
|
||||||
&self.viewport,
|
&self.viewport,
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
x_offset,
|
x_offset,
|
||||||
y_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();
|
self.output_buffer.clear();
|
||||||
changes
|
|
||||||
|
(changed_character_chunks, changed_sixel_image_chunks)
|
||||||
}
|
}
|
||||||
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||||
if self.cursor.is_hidden {
|
if self.cursor.is_hidden {
|
||||||
|
|
@ -901,7 +974,7 @@ impl Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn fill_viewport(&mut self, character: TerminalCharacter) {
|
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();
|
self.viewport.clear();
|
||||||
} else {
|
} else {
|
||||||
self.transfer_rows_to_lines_above(self.viewport.len())
|
self.transfer_rows_to_lines_above(self.viewport.len())
|
||||||
|
|
@ -927,7 +1000,7 @@ impl Grid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if scroll_region_bottom == self.height - 1 && scroll_region_top == 0 {
|
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);
|
self.transfer_rows_to_lines_above(1);
|
||||||
} else {
|
} else {
|
||||||
self.viewport.remove(0);
|
self.viewport.remove(0);
|
||||||
|
|
@ -963,9 +1036,10 @@ impl Grid {
|
||||||
}
|
}
|
||||||
if self.cursor.y == self.height - 1 {
|
if self.cursor.y == self.height - 1 {
|
||||||
if self.scroll_region.is_none() {
|
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);
|
self.transfer_rows_to_lines_above(1);
|
||||||
} else {
|
} else {
|
||||||
|
self.sixel_grid.offset_grid_top();
|
||||||
self.viewport.remove(0);
|
self.viewport.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -997,6 +1071,27 @@ impl Grid {
|
||||||
} else {
|
} else {
|
||||||
row.add_character_at(terminal_character, self.cursor.x);
|
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);
|
self.output_buffer.update_line(self.cursor.y);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -1088,7 +1183,7 @@ impl Grid {
|
||||||
fn line_wrap(&mut self) {
|
fn line_wrap(&mut self) {
|
||||||
self.cursor.x = 0;
|
self.cursor.x = 0;
|
||||||
if self.cursor.y == self.height - 1 {
|
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);
|
self.transfer_rows_to_lines_above(1);
|
||||||
} else {
|
} else {
|
||||||
self.viewport.remove(0);
|
self.viewport.remove(0);
|
||||||
|
|
@ -1333,7 +1428,7 @@ impl Grid {
|
||||||
self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap());
|
self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap());
|
||||||
self.lines_below = vec![];
|
self.lines_below = vec![];
|
||||||
self.viewport = vec![Row::new(self.width).canonical()];
|
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.cursor_key_mode = false;
|
||||||
self.scroll_region = None;
|
self.scroll_region = None;
|
||||||
self.clear_viewport_before_rendering = true;
|
self.clear_viewport_before_rendering = true;
|
||||||
|
|
@ -1346,6 +1441,10 @@ impl Grid {
|
||||||
self.output_buffer.update_all_lines();
|
self.output_buffer.update_all_lines();
|
||||||
self.changed_colors = None;
|
self.changed_colors = None;
|
||||||
self.scrollback_buffer_lines = 0;
|
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) {
|
fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) {
|
||||||
self.preceding_char = Some(terminal_character);
|
self.preceding_char = Some(terminal_character);
|
||||||
|
|
@ -1486,6 +1585,7 @@ impl Grid {
|
||||||
let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
|
let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
|
&mut self.sixel_grid,
|
||||||
count,
|
count,
|
||||||
self.width,
|
self.width,
|
||||||
);
|
);
|
||||||
|
|
@ -1493,6 +1593,57 @@ impl Grid {
|
||||||
self.scrollback_buffer_lines =
|
self.scrollback_buffer_lines =
|
||||||
subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count);
|
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 {
|
impl Perform for Grid {
|
||||||
|
|
@ -1543,16 +1694,40 @@ impl Perform for Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) {
|
fn hook(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
|
||||||
// TBD
|
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) {
|
fn put(&mut self, byte: u8) {
|
||||||
// TBD
|
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) {
|
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) {
|
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);
|
self.changed_colors.as_mut().unwrap()[i as usize] = Some(c);
|
||||||
return;
|
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;
|
self.bracketed_paste_mode = false;
|
||||||
},
|
},
|
||||||
Some(1049) => {
|
Some(1049) => {
|
||||||
// leave alternate buffer
|
if let Some(mut alternate_screen_state) = self.alternate_screen_state.take()
|
||||||
if let Some((
|
|
||||||
alternative_lines_above,
|
|
||||||
alternative_viewport,
|
|
||||||
alternative_cursor,
|
|
||||||
)) = &mut self.alternate_lines_above_viewport_and_cursor
|
|
||||||
{
|
{
|
||||||
std::mem::swap(&mut self.lines_above, alternative_lines_above);
|
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
||||||
std::mem::swap(&mut self.viewport, alternative_viewport);
|
// reap images before dropping the alternate_screen_state contents
|
||||||
std::mem::swap(&mut self.cursor, alternative_cursor);
|
// - 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.clear_viewport_before_rendering = true;
|
||||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
|
|
@ -1844,6 +2035,9 @@ impl Perform for Grid {
|
||||||
Some(7) => {
|
Some(7) => {
|
||||||
self.disable_linewrap = true;
|
self.disable_linewrap = true;
|
||||||
},
|
},
|
||||||
|
Some(80) => {
|
||||||
|
self.sixel_scrolling = false;
|
||||||
|
},
|
||||||
Some(1006) => {
|
Some(1006) => {
|
||||||
self.mouse_mode = false;
|
self.mouse_mode = false;
|
||||||
},
|
},
|
||||||
|
|
@ -1878,8 +2072,17 @@ impl Perform for Grid {
|
||||||
vec![Row::new(self.width).canonical()],
|
vec![Row::new(self.width).canonical()],
|
||||||
);
|
);
|
||||||
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
||||||
self.alternate_lines_above_viewport_and_cursor =
|
let sixel_image_store = self.sixel_grid.sixel_image_store.clone();
|
||||||
Some((current_lines_above, current_viewport, current_cursor));
|
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.clear_viewport_before_rendering = true;
|
||||||
self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count();
|
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
|
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) => {
|
Some(7) => {
|
||||||
self.disable_linewrap = false;
|
self.disable_linewrap = false;
|
||||||
},
|
},
|
||||||
|
Some(80) => {
|
||||||
|
self.sixel_scrolling = true;
|
||||||
|
},
|
||||||
Some(1006) => {
|
Some(1006) => {
|
||||||
self.mouse_mode = true;
|
self.mouse_mode = true;
|
||||||
},
|
},
|
||||||
|
|
@ -1969,9 +2175,45 @@ impl Perform for Grid {
|
||||||
let line_count = next_param_or(1);
|
let line_count = next_param_or(1);
|
||||||
self.rotate_scroll_region_up(line_count as usize);
|
self.rotate_scroll_region_up(line_count as usize);
|
||||||
} else if c == 'S' {
|
} else if c == 'S' {
|
||||||
// move scroll up
|
let first_intermediate_is_questionmark = match intermediates.get(0) {
|
||||||
let count = next_param_or(1);
|
Some(b'?') => true,
|
||||||
self.rotate_scroll_region_down(count);
|
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' {
|
} else if c == 's' {
|
||||||
self.save_cursor_position();
|
self.save_cursor_position();
|
||||||
} else if c == 'u' {
|
} else if c == 'u' {
|
||||||
|
|
@ -2022,6 +2264,11 @@ impl Perform for Grid {
|
||||||
if let Some(cursor_shape) = shape {
|
if let Some(cursor_shape) = shape {
|
||||||
self.cursor.change_shape(cursor_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' {
|
} else if c == 'Z' {
|
||||||
for _ in 0..next_param_or(1) {
|
for _ in 0..next_param_or(1) {
|
||||||
|
|
@ -2033,7 +2280,7 @@ impl Perform for Grid {
|
||||||
match intermediates.get(0) {
|
match intermediates.get(0) {
|
||||||
None | Some(0) => {
|
None | Some(0) => {
|
||||||
// primary device attributes
|
// primary device attributes
|
||||||
let terminal_capabilities = "\u{1b}[?6c";
|
let terminal_capabilities = "\u{1b}[?64;4c";
|
||||||
self.pending_messages_to_pty
|
self.pending_messages_to_pty
|
||||||
.push(terminal_capabilities.as_bytes().to_vec());
|
.push(terminal_capabilities.as_bytes().to_vec());
|
||||||
},
|
},
|
||||||
|
|
@ -2175,6 +2422,41 @@ impl Perform for Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AlternateScreenState {
|
||||||
|
lines_above: VecDeque<Row>,
|
||||||
|
viewport: Vec<Row>,
|
||||||
|
cursor: Cursor,
|
||||||
|
sixel_grid: SixelGrid,
|
||||||
|
}
|
||||||
|
impl AlternateScreenState {
|
||||||
|
pub fn new(
|
||||||
|
lines_above: VecDeque<Row>,
|
||||||
|
viewport: Vec<Row>,
|
||||||
|
cursor: Cursor,
|
||||||
|
sixel_grid: SixelGrid,
|
||||||
|
) -> Self {
|
||||||
|
AlternateScreenState {
|
||||||
|
lines_above,
|
||||||
|
viewport,
|
||||||
|
cursor,
|
||||||
|
sixel_grid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn apply_contents_to(
|
||||||
|
&mut self,
|
||||||
|
lines_above: &mut VecDeque<Row>,
|
||||||
|
viewport: &mut Vec<Row>,
|
||||||
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Row {
|
pub struct Row {
|
||||||
pub columns: VecDeque<TerminalCharacter>,
|
pub columns: VecDeque<TerminalCharacter>,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ pub mod grid;
|
||||||
pub mod link_handler;
|
pub mod link_handler;
|
||||||
mod plugin_pane;
|
mod plugin_pane;
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
|
pub mod sixel;
|
||||||
pub mod terminal_character;
|
pub mod terminal_character;
|
||||||
mod terminal_pane;
|
mod terminal_pane;
|
||||||
mod tiled_panes;
|
mod tiled_panes;
|
||||||
|
|
@ -13,6 +14,7 @@ pub use floating_panes::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
pub use link_handler::*;
|
pub use link_handler::*;
|
||||||
pub(crate) use plugin_pane::*;
|
pub(crate) use plugin_pane::*;
|
||||||
|
pub use sixel::*;
|
||||||
pub(crate) use terminal_character::*;
|
pub(crate) use terminal_character::*;
|
||||||
pub use terminal_pane::*;
|
pub use terminal_pane::*;
|
||||||
pub use tiled_panes::*;
|
pub use tiled_panes::*;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::fmt::Write;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::output::CharacterChunk;
|
use crate::output::{CharacterChunk, SixelImageChunk};
|
||||||
use crate::panes::PaneId;
|
use crate::panes::PaneId;
|
||||||
use crate::pty::VteBytes;
|
use crate::pty::VteBytes;
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
|
|
@ -141,7 +141,7 @@ impl Pane for PluginPane {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: Option<ClientId>,
|
client_id: Option<ClientId>,
|
||||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
) -> Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)> {
|
||||||
// this is a bit of a hack but works in a pinch
|
// this is a bit of a hack but works in a pinch
|
||||||
client_id?;
|
client_id?;
|
||||||
let client_id = client_id.unwrap();
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
466
zellij-server/src/panes/sixel.rs
Normal file
466
zellij-server/src/panes/sixel.rs
Normal file
|
|
@ -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<PixelRect> {
|
||||||
|
// 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<usize, PixelRect>,
|
||||||
|
previous_cell_size: Option<SizeInPixels>,
|
||||||
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
currently_parsing: Option<SixelDeserializer>,
|
||||||
|
image_ids_to_reap: Vec<usize>,
|
||||||
|
sixel_parser: Option<sixel_tokenizer::Parser>,
|
||||||
|
pub sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SixelGrid {
|
||||||
|
pub fn new(
|
||||||
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
|
) -> 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<usize>,
|
||||||
|
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<SixelImage> {
|
||||||
|
// 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<Item = (usize, &PixelRect)> {
|
||||||
|
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<Vec<(usize, PixelRect)>> {
|
||||||
|
// 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<Vec<usize>> {
|
||||||
|
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<Vec<usize>> {
|
||||||
|
// returns image ids to reap
|
||||||
|
let mut image_ids: Vec<usize> = 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<usize>) {
|
||||||
|
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<usize, usize>,
|
||||||
|
scrollback_size_in_lines: usize,
|
||||||
|
viewport_width_in_cells: usize,
|
||||||
|
viewport_x_offset: usize,
|
||||||
|
viewport_y_offset: usize,
|
||||||
|
) -> Vec<SixelImageChunk> {
|
||||||
|
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<PixelRect, String>;
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct SixelImageStore {
|
||||||
|
sixel_images: HashMap<usize, (SixelImage, SixelImageCache)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SixelImageStore {
|
||||||
|
pub fn serialize_image(
|
||||||
|
&mut self,
|
||||||
|
image_id: usize,
|
||||||
|
pixel_x: usize,
|
||||||
|
pixel_y: usize,
|
||||||
|
pixel_width: usize,
|
||||||
|
pixel_height: usize,
|
||||||
|
) -> Option<String> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::output::CharacterChunk;
|
use crate::output::{CharacterChunk, SixelImageChunk};
|
||||||
|
use crate::panes::sixel::SixelImageStore;
|
||||||
use crate::panes::{
|
use crate::panes::{
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
terminal_character::{CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
terminal_character::{CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||||
|
|
@ -99,10 +100,10 @@ impl Pane for TerminalPane {
|
||||||
self.reflow_lines();
|
self.reflow_lines();
|
||||||
}
|
}
|
||||||
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
|
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
|
||||||
|
self.set_should_render(true);
|
||||||
for &byte in &bytes {
|
for &byte in &bytes {
|
||||||
self.vte_parser.advance(&mut self.grid, byte);
|
self.vte_parser.advance(&mut self.grid, byte);
|
||||||
}
|
}
|
||||||
self.set_should_render(true);
|
|
||||||
}
|
}
|
||||||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||||
// (x, y)
|
// (x, y)
|
||||||
|
|
@ -205,13 +206,14 @@ impl Pane for TerminalPane {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
_client_id: Option<ClientId>,
|
_client_id: Option<ClientId>,
|
||||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
) -> Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)> {
|
||||||
if self.should_render() {
|
if self.should_render() {
|
||||||
let mut raw_vte_output = String::new();
|
let mut raw_vte_output = String::new();
|
||||||
let content_x = self.get_content_x();
|
let content_x = self.get_content_x();
|
||||||
let content_y = self.get_content_y();
|
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() {
|
for character_chunk in character_chunks.iter_mut() {
|
||||||
character_chunk.add_changed_colors(self.grid.changed_colors);
|
character_chunk.add_changed_colors(self.grid.changed_colors);
|
||||||
if self
|
if self
|
||||||
|
|
@ -237,7 +239,7 @@ impl Pane for TerminalPane {
|
||||||
self.grid.ring_bell = false;
|
self.grid.ring_bell = false;
|
||||||
}
|
}
|
||||||
self.set_should_render(false);
|
self.set_should_render(false);
|
||||||
Some((character_chunks, Some(raw_vte_output)))
|
Some((character_chunks, Some(raw_vte_output), sixel_image_chunks))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -509,15 +511,19 @@ impl TerminalPane {
|
||||||
pane_name: String,
|
pane_name: String,
|
||||||
link_handler: Rc<RefCell<LinkHandler>>,
|
link_handler: Rc<RefCell<LinkHandler>>,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
) -> TerminalPane {
|
) -> TerminalPane {
|
||||||
let initial_pane_title = format!("Pane #{}", pane_index);
|
let initial_pane_title = format!("Pane #{}", pane_index);
|
||||||
let grid = Grid::new(
|
let grid = Grid::new(
|
||||||
position_and_size.rows.as_usize(),
|
position_and_size.rows.as_usize(),
|
||||||
position_and_size.cols.as_usize(),
|
position_and_size.cols.as_usize(),
|
||||||
terminal_emulator_colors,
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
link_handler,
|
link_handler,
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
|
sixel_image_store,
|
||||||
);
|
);
|
||||||
TerminalPane {
|
TerminalPane {
|
||||||
frame: HashMap::new(),
|
frame: HashMap::new(),
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
assertion_line: 1810
|
assertion_line: 2189
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
---
|
---
|
||||||
00 (C): f oo
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,34 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1552
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/mosaic on main ⨯ bash 16:00:06
|
01 (C): ⋊> ~/c/mosaic on main ⨯ bash 16:00:06
|
||||||
02 (C): [aram@green mosaic]$ 12345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345
|
02 (C): [aram@green mosaic]$ 12345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345
|
||||||
03 (W):
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 875
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/zellij on wide-char ⨯ bash 15:35:20
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,34 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1486
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22
|
01 (C): ⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22
|
||||||
02 (C): ⋊> ~/c/mosaic on main ⨯ 15:07:29
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 655
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): ffffff
|
00 (C): ffffff
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 677
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): foo
|
00 (C): foo
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 699
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): 12345678foo234567890
|
00 (C): 12345678foo234567890
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 831
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): [aram@green zellij]$ 🏠🏠🏠
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 809
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): [aram@green zellij]$ 🏠 def abc
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 941
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): 12 4
|
00 (C): 12 4
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 897
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Hi
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 919
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): i
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1508
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C):
|
00 (C):
|
||||||
01 (C): OS: 5.9.13-arch1-1 GNU/Linux
|
01 (C): OS: 5.9.13-arch1-1 GNU/Linux
|
||||||
|
|
@ -18,4 +18,17 @@ expression: "format!(\"{:?}\", grid)"
|
||||||
12 (C): wlp2s0 192.168.0.3
|
12 (C): wlp2s0 192.168.0.3
|
||||||
13 (C):
|
13 (C):
|
||||||
14 (C): [I] [20:07] kingdom:mosaic (main) |
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,66 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1644
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): ➜ mosaic git:(mosaic#130) emacs
|
00 (C): ➜ mosaic git:(mosaic#130) emacs
|
||||||
01 (C): ➜ mosaic git:(mosaic#130) emacs -nw
|
01 (C): ➜ mosaic git:(mosaic#130) emacs -nw
|
||||||
02 (C): ➜ mosaic git:(mosaic#130) exit
|
02 (C): ➜ mosaic git:(mosaic#130) exit
|
||||||
03 (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):
|
||||||
|
51 (C):
|
||||||
|
52 (C):
|
||||||
|
53 (C):
|
||||||
|
54 (C):
|
||||||
|
55 (C):
|
||||||
|
56 (C):
|
||||||
|
57 (C):
|
||||||
|
58 (C):
|
||||||
|
59 (C):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1576
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C):
|
00 (C):
|
||||||
01 (C): OS: 5.9.14-arch1-1 GNU/Linux
|
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/' | \
|
18 (W): )$/\\\\e[0;33m\1\\\\e[0m/' | \
|
||||||
19 (C): paste -sd ''\
|
19 (C): paste -sd ''\
|
||||||
20 (C): )
|
20 (C): )
|
||||||
|
21 (C):
|
||||||
|
22 (C):
|
||||||
|
23 (C):
|
||||||
|
24 (C):
|
||||||
|
25 (C):
|
||||||
|
26 (C):
|
||||||
|
27 (C):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,34 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1275
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo badblocks 11:32:23
|
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo badblocks 11:32:23
|
||||||
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
||||||
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
||||||
04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,34 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 1247
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo bandwhich 11:18:26
|
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo bandwhich 11:18:26
|
||||||
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
||||||
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
||||||
04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 853
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 787
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): [aram@green zellij]$ 🏠 xdef abc
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 2018
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): 👨y x🔭
|
00 (C): 👨y x🔭
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 2044
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): 👨👨 🔭
|
00 (C): 👨👨 🔭
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 963
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): 12 4
|
00 (C): 12 4
|
||||||
01 (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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
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]]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 743
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHH 15:19:10
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
|
assertion_line: 765
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
00 (C): Welcome to fish, the friendly interactive shell
|
00 (C): Welcome to fish, the friendly interactive shell
|
||||||
01 (C): ⋊> ~/c/zellij on main ⨯ bash 15:50:11
|
01 (C): ⋊> ~/c/zellij on main ⨯ bash 15:50:11
|
||||||
02 (C): [aram@green zellij]$ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
|
02 (C): [aram@green zellij]$ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
|
||||||
03 (W): HHHHH
|
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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
use super::super::TerminalPane;
|
use super::super::TerminalPane;
|
||||||
|
use crate::panes::sixel::SixelImageStore;
|
||||||
use crate::panes::LinkHandler;
|
use crate::panes::LinkHandler;
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
data::{Palette, Style},
|
data::{Palette, Style},
|
||||||
pane_size::PaneGeom,
|
pane_size::{PaneGeom, SizeInPixels},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||||||
|
let mut path_to_file = std::path::PathBuf::new();
|
||||||
|
path_to_file.push("../src");
|
||||||
|
path_to_file.push("tests");
|
||||||
|
path_to_file.push("fixtures");
|
||||||
|
path_to_file.push(fixture_name);
|
||||||
|
std::fs::read(path_to_file)
|
||||||
|
.unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn scrolling_inside_a_pane() {
|
pub fn scrolling_inside_a_pane() {
|
||||||
let fake_client_id = 1;
|
let fake_client_id = 1;
|
||||||
|
|
@ -20,6 +32,9 @@ pub fn scrolling_inside_a_pane() {
|
||||||
|
|
||||||
let pid = 1;
|
let pid = 1;
|
||||||
let style = Style::default();
|
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(
|
let mut terminal_pane = TerminalPane::new(
|
||||||
pid,
|
pid,
|
||||||
fake_win_size,
|
fake_win_size,
|
||||||
|
|
@ -28,7 +43,9 @@ pub fn scrolling_inside_a_pane() {
|
||||||
String::new(),
|
String::new(),
|
||||||
Rc::new(RefCell::new(LinkHandler::new())),
|
Rc::new(RefCell::new(LinkHandler::new())),
|
||||||
Rc::new(RefCell::new(None)),
|
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
|
); // 0 is the pane index
|
||||||
let mut text_to_fill_pane = String::new();
|
let mut text_to_fill_pane = String::new();
|
||||||
for i in 0..30 {
|
for i in 0..30 {
|
||||||
|
|
@ -42,3 +59,335 @@ pub fn scrolling_inside_a_pane() {
|
||||||
terminal_pane.clear_scroll();
|
terminal_pane.clear_scroll();
|
||||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
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));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,24 @@ fn route_action(
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
session
|
|
||||||
.senders
|
// forward the action to plugins unless it is a mousehold
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
// this is a bit of a hack around the unfortunate architecture we use with plugins
|
||||||
None,
|
// this will change as soon as we refactor
|
||||||
Some(client_id),
|
match action {
|
||||||
Event::InputReceived,
|
Action::MouseHold(_) => {},
|
||||||
))
|
_ => {
|
||||||
.unwrap();
|
session
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
Some(client_id),
|
||||||
|
Event::InputReceived,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::ToggleTab => {
|
Action::ToggleTab => {
|
||||||
session
|
session
|
||||||
|
|
@ -493,6 +503,16 @@ pub(crate) fn route_thread_main(
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
|
ClientToServerMsg::ColorRegisters(color_registers) => {
|
||||||
|
rlocked_sessions
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::TerminalColorRegisters(
|
||||||
|
color_registers,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
ClientToServerMsg::NewClient(
|
ClientToServerMsg::NewClient(
|
||||||
client_attributes,
|
client_attributes,
|
||||||
cli_args,
|
cli_args,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Things related to [`Screen`]s.
|
//! Things related to [`Screen`]s.
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
@ -15,6 +15,7 @@ use crate::panes::terminal_character::AnsiCode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
output::Output,
|
output::Output,
|
||||||
|
panes::sixel::SixelImageStore,
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
||||||
tab::Tab,
|
tab::Tab,
|
||||||
|
|
@ -95,6 +96,7 @@ pub enum ScreenInstruction {
|
||||||
TerminalPixelDimensions(PixelDimensions),
|
TerminalPixelDimensions(PixelDimensions),
|
||||||
TerminalBackgroundColor(String),
|
TerminalBackgroundColor(String),
|
||||||
TerminalForegroundColor(String),
|
TerminalForegroundColor(String),
|
||||||
|
TerminalColorRegisters(Vec<(usize, String)>),
|
||||||
ChangeMode(ModeInfo, ClientId),
|
ChangeMode(ModeInfo, ClientId),
|
||||||
LeftClick(Position, ClientId),
|
LeftClick(Position, ClientId),
|
||||||
RightClick(Position, ClientId),
|
RightClick(Position, ClientId),
|
||||||
|
|
@ -184,6 +186,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::TerminalForegroundColor(..) => {
|
ScreenInstruction::TerminalForegroundColor(..) => {
|
||||||
ScreenContext::TerminalForegroundColor
|
ScreenContext::TerminalForegroundColor
|
||||||
},
|
},
|
||||||
|
ScreenInstruction::TerminalColorRegisters(..) => ScreenContext::TerminalColorRegisters,
|
||||||
ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode,
|
ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode,
|
||||||
ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab,
|
ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab,
|
||||||
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
||||||
|
|
@ -247,9 +250,11 @@ pub(crate) struct Screen {
|
||||||
size: Size,
|
size: Size,
|
||||||
pixel_dimensions: PixelDimensions,
|
pixel_dimensions: PixelDimensions,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
/// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`]
|
/// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`]
|
||||||
overlay: OverlayWindow,
|
overlay: OverlayWindow,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||||
/// The indices of this [`Screen`]'s active [`Tab`]s.
|
/// The indices of this [`Screen`]'s active [`Tab`]s.
|
||||||
active_tab_indices: BTreeMap<ClientId, usize>,
|
active_tab_indices: BTreeMap<ClientId, usize>,
|
||||||
|
|
@ -279,12 +284,14 @@ impl Screen {
|
||||||
size: client_attributes.size,
|
size: client_attributes.size,
|
||||||
pixel_dimensions: Default::default(),
|
pixel_dimensions: Default::default(),
|
||||||
character_cell_size: Rc::new(RefCell::new(None)),
|
character_cell_size: Rc::new(RefCell::new(None)),
|
||||||
|
sixel_image_store: Rc::new(RefCell::new(SixelImageStore::default())),
|
||||||
style: client_attributes.style,
|
style: client_attributes.style,
|
||||||
connected_clients: Rc::new(RefCell::new(HashSet::new())),
|
connected_clients: Rc::new(RefCell::new(HashSet::new())),
|
||||||
active_tab_indices: BTreeMap::new(),
|
active_tab_indices: BTreeMap::new(),
|
||||||
tabs: BTreeMap::new(),
|
tabs: BTreeMap::new(),
|
||||||
overlay: OverlayWindow::default(),
|
overlay: OverlayWindow::default(),
|
||||||
terminal_emulator_colors: Rc::new(RefCell::new(Palette::default())),
|
terminal_emulator_colors: Rc::new(RefCell::new(Palette::default())),
|
||||||
|
terminal_emulator_color_codes: Rc::new(RefCell::new(HashMap::new())),
|
||||||
tab_history: BTreeMap::new(),
|
tab_history: BTreeMap::new(),
|
||||||
mode_info: BTreeMap::new(),
|
mode_info: BTreeMap::new(),
|
||||||
default_mode_info: mode_info,
|
default_mode_info: mode_info,
|
||||||
|
|
@ -518,10 +525,19 @@ impl Screen {
|
||||||
self.terminal_emulator_colors.borrow_mut().fg = fg_palette_color;
|
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`].
|
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||||
pub fn render(&mut self) {
|
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 mut tabs_to_close = vec![];
|
||||||
let size = self.size;
|
let size = self.size;
|
||||||
let overlay = self.overlay.clone();
|
let overlay = self.overlay.clone();
|
||||||
|
|
@ -599,6 +615,7 @@ impl Screen {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.size,
|
self.size,
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.bus.os_input.as_ref().unwrap().clone(),
|
self.bus.os_input.as_ref().unwrap().clone(),
|
||||||
self.bus.senders.clone(),
|
self.bus.senders.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
|
|
@ -610,6 +627,7 @@ impl Screen {
|
||||||
client_id,
|
client_id,
|
||||||
self.copy_options.clone(),
|
self.copy_options.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
||||||
if self.session_is_mirrored {
|
if self.session_is_mirrored {
|
||||||
|
|
@ -793,7 +811,6 @@ impl Screen {
|
||||||
pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) {
|
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 let Some(active_tab) = self.get_active_tab_mut(client_id) {
|
||||||
if !active_tab.move_focus_left(client_id) {
|
if !active_tab.move_focus_left(client_id) {
|
||||||
println!("can has true");
|
|
||||||
self.switch_tab_prev(client_id);
|
self.switch_tab_prev(client_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1236,6 +1253,9 @@ pub(crate) fn screen_thread_main(
|
||||||
ScreenInstruction::TerminalForegroundColor(background_color_instruction) => {
|
ScreenInstruction::TerminalForegroundColor(background_color_instruction) => {
|
||||||
screen.update_terminal_foreground_color(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) => {
|
ScreenInstruction::ChangeMode(mode_info, client_id) => {
|
||||||
screen.change_mode(mode_info, client_id);
|
screen.change_mode(mode_info, client_id);
|
||||||
screen.render();
|
screen.render();
|
||||||
|
|
@ -1264,8 +1284,9 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.render();
|
screen.render();
|
||||||
},
|
},
|
||||||
ScreenInstruction::MouseHold(point, client_id) => {
|
ScreenInstruction::MouseHold(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
active_tab!(screen, client_id, |tab: &mut Tab| {
|
||||||
.handle_mouse_hold(&point, client_id));
|
tab.handle_mouse_hold(&point, client_id);
|
||||||
|
});
|
||||||
screen.render();
|
screen.render();
|
||||||
},
|
},
|
||||||
ScreenInstruction::Copy(client_id) => {
|
ScreenInstruction::Copy(client_id) => {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ use crate::ui::pane_boundaries_frame::FrameParams;
|
||||||
use self::clipboard::ClipboardProvider;
|
use self::clipboard::ClipboardProvider;
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi,
|
os_input_output::ServerOsApi,
|
||||||
output::{CharacterChunk, Output},
|
output::{CharacterChunk, Output, SixelImageChunk},
|
||||||
|
panes::sixel::SixelImageStore,
|
||||||
panes::{FloatingPanes, TiledPanes},
|
panes::{FloatingPanes, TiledPanes},
|
||||||
panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
|
panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
|
||||||
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
||||||
|
|
@ -77,6 +78,7 @@ pub(crate) struct Tab {
|
||||||
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
|
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
|
||||||
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
|
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
os_api: Box<dyn ServerOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
pub senders: ThreadSenders,
|
pub senders: ThreadSenders,
|
||||||
synchronize_is_active: bool,
|
synchronize_is_active: bool,
|
||||||
|
|
@ -96,6 +98,7 @@ pub(crate) struct Tab {
|
||||||
copy_on_select: bool,
|
copy_on_select: bool,
|
||||||
last_mouse_hold_position: Option<Position>,
|
last_mouse_hold_position: Option<Position>,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
|
|
@ -135,7 +138,7 @@ pub trait Pane {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: Option<ClientId>,
|
client_id: Option<ClientId>,
|
||||||
) -> Option<(Vec<CharacterChunk>, Option<String>)>; // TODO: better
|
) -> Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>; // TODO: better
|
||||||
fn render_frame(
|
fn render_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
|
|
@ -287,6 +290,7 @@ impl Tab {
|
||||||
name: String,
|
name: String,
|
||||||
display_area: Size,
|
display_area: Size,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
os_api: Box<dyn ServerOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
senders: ThreadSenders,
|
senders: ThreadSenders,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
|
|
@ -298,6 +302,7 @@ impl Tab {
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
copy_options: CopyOptions,
|
copy_options: CopyOptions,
|
||||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||||
|
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = if name.is_empty() {
|
let name = if name.is_empty() {
|
||||||
format!("Tab #{}", index + 1)
|
format!("Tab #{}", index + 1)
|
||||||
|
|
@ -354,6 +359,7 @@ impl Tab {
|
||||||
viewport,
|
viewport,
|
||||||
display_area,
|
display_area,
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
|
sixel_image_store,
|
||||||
synchronize_is_active: false,
|
synchronize_is_active: false,
|
||||||
os_api,
|
os_api,
|
||||||
senders,
|
senders,
|
||||||
|
|
@ -371,6 +377,7 @@ impl Tab {
|
||||||
copy_on_select: copy_options.copy_on_select,
|
copy_on_select: copy_options.copy_on_select,
|
||||||
last_mouse_hold_position: None,
|
last_mouse_hold_position: None,
|
||||||
terminal_emulator_colors,
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,7 +445,9 @@ impl Tab {
|
||||||
layout.pane_name.clone().unwrap_or_default(),
|
layout.pane_name.clone().unwrap_or_default(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
new_pane.set_borderless(layout.borderless);
|
new_pane.set_borderless(layout.borderless);
|
||||||
self.tiled_panes
|
self.tiled_panes
|
||||||
|
|
@ -668,7 +677,9 @@ impl Tab {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.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
|
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
|
||||||
resize_pty!(new_pane, self.os_api);
|
resize_pty!(new_pane, self.os_api);
|
||||||
|
|
@ -691,7 +702,9 @@ impl Tab {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
|
self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
|
||||||
self.should_clear_display_before_rendering = true;
|
self.should_clear_display_before_rendering = true;
|
||||||
|
|
@ -717,7 +730,9 @@ impl Tab {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
let replaced_pane = if self.floating_panes.panes_are_visible() {
|
let replaced_pane = if self.floating_panes.panes_are_visible() {
|
||||||
self.floating_panes
|
self.floating_panes
|
||||||
|
|
@ -762,7 +777,9 @@ impl Tab {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
self.tiled_panes
|
self.tiled_panes
|
||||||
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
||||||
|
|
@ -790,7 +807,9 @@ impl Tab {
|
||||||
String::new(),
|
String::new(),
|
||||||
self.link_handler.clone(),
|
self.link_handler.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.sixel_image_store.clone(),
|
||||||
self.terminal_emulator_colors.clone(),
|
self.terminal_emulator_colors.clone(),
|
||||||
|
self.terminal_emulator_color_codes.clone(),
|
||||||
);
|
);
|
||||||
self.tiled_panes
|
self.tiled_panes
|
||||||
.split_pane_vertically(pid, Box::new(new_terminal), client_id);
|
.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
|
// determine if event is repeated to enable smooth scrolling
|
||||||
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
||||||
position_on_screen == &last_position
|
position_on_screen == &last_position
|
||||||
|
|
@ -1805,7 +1829,8 @@ impl Tab {
|
||||||
.move_pane_with_mouse(*position_on_screen, search_selectable)
|
.move_pane_with_mouse(*position_on_screen, search_selectable)
|
||||||
{
|
{
|
||||||
self.set_force_render();
|
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;
|
let selecting = self.selecting_with_mouse;
|
||||||
|
|
@ -1825,10 +1850,13 @@ impl Tab {
|
||||||
|
|
||||||
let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line);
|
let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line);
|
||||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
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 {
|
} else if selecting {
|
||||||
active_pane.update_selection(&relative_position, client_id);
|
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) {
|
pub fn copy_selection(&self, client_id: ClientId) {
|
||||||
|
|
|
||||||
|
|
@ -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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
@ -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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Output, Tab};
|
use super::{Output, Tab};
|
||||||
|
use crate::panes::sixel::SixelImageStore;
|
||||||
use crate::screen::CopyOptions;
|
use crate::screen::CopyOptions;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
|
|
@ -13,12 +14,11 @@ use std::path::PathBuf;
|
||||||
use zellij_utils::envs::set_session_name;
|
use zellij_utils::envs::set_session_name;
|
||||||
use zellij_utils::input::layout::LayoutTemplate;
|
use zellij_utils::input::layout::LayoutTemplate;
|
||||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||||
use zellij_utils::pane_size::Size;
|
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||||
use zellij_utils::position::Position;
|
use zellij_utils::position::Position;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::rc::Rc;
|
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 character_cell_info = Rc::new(RefCell::new(None));
|
||||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||||
let copy_options = CopyOptions::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(
|
let mut tab = Tab::new(
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
size,
|
size,
|
||||||
character_cell_info,
|
character_cell_info,
|
||||||
|
sixel_image_store,
|
||||||
os_api,
|
os_api,
|
||||||
senders,
|
senders,
|
||||||
max_panes,
|
max_panes,
|
||||||
|
|
@ -136,6 +139,66 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
client_id,
|
client_id,
|
||||||
copy_options,
|
copy_options,
|
||||||
terminal_emulator_colors,
|
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<RefCell<SixelImageStore>>,
|
||||||
|
) -> 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(
|
tab.apply_layout(
|
||||||
LayoutTemplate::default().try_into().unwrap(),
|
LayoutTemplate::default().try_into().unwrap(),
|
||||||
|
|
@ -162,12 +225,48 @@ use ::insta::assert_snapshot;
|
||||||
use zellij_utils::vte;
|
use zellij_utils::vte;
|
||||||
|
|
||||||
fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String {
|
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(
|
let mut grid = Grid::new(
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
Rc::new(RefCell::new(palette)),
|
Rc::new(RefCell::new(palette)),
|
||||||
|
terminal_emulator_color_codes,
|
||||||
Rc::new(RefCell::new(LinkHandler::new())),
|
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<RefCell<SixelImageStore>>,
|
||||||
|
) -> 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();
|
let mut vte_parser = vte::Parser::new();
|
||||||
for &byte in ansi_instructions.as_bytes() {
|
for &byte in ansi_instructions.as_bytes() {
|
||||||
|
|
@ -183,12 +282,16 @@ fn take_snapshot_and_cursor_position(
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
) -> (String, Option<(usize, usize)>) {
|
) -> (String, Option<(usize, usize)>) {
|
||||||
// snapshot, x_coordinates, y_coordinates
|
// 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(
|
let mut grid = Grid::new(
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
Rc::new(RefCell::new(palette)),
|
Rc::new(RefCell::new(palette)),
|
||||||
|
terminal_emulator_color_codes,
|
||||||
Rc::new(RefCell::new(LinkHandler::new())),
|
Rc::new(RefCell::new(LinkHandler::new())),
|
||||||
Rc::new(RefCell::new(None)),
|
Rc::new(RefCell::new(None)),
|
||||||
|
sixel_image_store,
|
||||||
);
|
);
|
||||||
let mut vte_parser = vte::Parser::new();
|
let mut vte_parser = vte::Parser::new();
|
||||||
for &byte in ansi_instructions.as_bytes() {
|
for &byte in ansi_instructions.as_bytes() {
|
||||||
|
|
@ -1233,6 +1336,76 @@ fn save_cursor_position_across_resizes() {
|
||||||
assert_snapshot!(snapshot);
|
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]
|
#[test]
|
||||||
fn suppress_tiled_pane() {
|
fn suppress_tiled_pane() {
|
||||||
let size = Size {
|
let size = Size {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::Tab;
|
use super::Tab;
|
||||||
|
use crate::panes::sixel::SixelImageStore;
|
||||||
use crate::screen::CopyOptions;
|
use crate::screen::CopyOptions;
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||||
|
|
@ -13,7 +14,7 @@ use zellij_utils::ipc::IpcReceiverWithContext;
|
||||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::rc::Rc;
|
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 connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||||
let copy_options = CopyOptions::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(
|
let mut tab = Tab::new(
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
size,
|
size,
|
||||||
character_cell_info,
|
character_cell_info,
|
||||||
|
sixel_image_store,
|
||||||
os_api,
|
os_api,
|
||||||
senders,
|
senders,
|
||||||
max_panes,
|
max_panes,
|
||||||
|
|
@ -122,6 +126,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
client_id,
|
client_id,
|
||||||
copy_options,
|
copy_options,
|
||||||
terminal_emulator_colors,
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
);
|
);
|
||||||
tab.apply_layout(
|
tab.apply_layout(
|
||||||
LayoutTemplate::default().try_into().unwrap(),
|
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 connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||||
let copy_options = CopyOptions::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(
|
let mut tab = Tab::new(
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
name,
|
name,
|
||||||
size,
|
size,
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
|
sixel_image_store,
|
||||||
os_api,
|
os_api,
|
||||||
senders,
|
senders,
|
||||||
max_panes,
|
max_panes,
|
||||||
|
|
@ -169,6 +177,7 @@ fn create_new_tab_with_cell_size(
|
||||||
client_id,
|
client_id,
|
||||||
copy_options,
|
copy_options,
|
||||||
terminal_emulator_colors,
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
);
|
);
|
||||||
tab.apply_layout(
|
tab.apply_layout(
|
||||||
LayoutTemplate::default().try_into().unwrap(),
|
LayoutTemplate::default().try_into().unwrap(),
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,19 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
clients: impl Iterator<Item = ClientId>,
|
clients: impl Iterator<Item = ClientId>,
|
||||||
) {
|
) {
|
||||||
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<ClientId> = clients.collect();
|
let clients: Vec<ClientId> = clients.collect();
|
||||||
self.output.add_character_chunks_to_multiple_clients(
|
self.output.add_character_chunks_to_multiple_clients(
|
||||||
character_chunks,
|
character_chunks,
|
||||||
clients.iter().copied(),
|
clients.iter().copied(),
|
||||||
self.z_index,
|
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 {
|
if let Some(raw_vte_output) = raw_vte_output {
|
||||||
self.output.add_post_vte_instruction_to_multiple_clients(
|
self.output.add_post_vte_instruction_to_multiple_clients(
|
||||||
clients.iter().copied(),
|
clients.iter().copied(),
|
||||||
|
|
@ -65,9 +71,16 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) {
|
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
|
self.output
|
||||||
.add_character_chunks_to_client(client_id, character_chunks, self.z_index);
|
.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 {
|
if let Some(raw_vte_output) = raw_vte_output {
|
||||||
self.output.add_post_vte_instruction_to_client(
|
self.output.add_post_vte_instruction_to_client(
|
||||||
client_id,
|
client_id,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ pub fn set_session_name(v: String) {
|
||||||
set_var(SESSION_NAME_ENV_KEY, v);
|
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 const SOCKET_DIR_ENV_KEY: &str = "ZELLIJ_SOCKET_DIR";
|
||||||
pub fn get_socket_dir() -> Result<String> {
|
pub fn get_socket_dir() -> Result<String> {
|
||||||
Ok(var(SOCKET_DIR_ENV_KEY)?)
|
Ok(var(SOCKET_DIR_ENV_KEY)?)
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,7 @@ pub enum ScreenContext {
|
||||||
TerminalPixelDimensions,
|
TerminalPixelDimensions,
|
||||||
TerminalBackgroundColor,
|
TerminalBackgroundColor,
|
||||||
TerminalForegroundColor,
|
TerminalForegroundColor,
|
||||||
|
TerminalColorRegisters,
|
||||||
ChangeMode,
|
ChangeMode,
|
||||||
LeftClick,
|
LeftClick,
|
||||||
RightClick,
|
RightClick,
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ pub enum ClientToServerMsg {
|
||||||
TerminalPixelDimensions(PixelDimensions),
|
TerminalPixelDimensions(PixelDimensions),
|
||||||
BackgroundColor(String),
|
BackgroundColor(String),
|
||||||
ForegroundColor(String),
|
ForegroundColor(String),
|
||||||
|
ColorRegisters(Vec<(usize, String)>),
|
||||||
TerminalResize(Size),
|
TerminalResize(Size),
|
||||||
NewClient(
|
NewClient(
|
||||||
ClientAttributes,
|
ClientAttributes,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue