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"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.6.1"
|
||||
|
|
@ -1144,9 +1150,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.14.1"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc3e639bcba360d9237acabd22014c16f3df772db463b7446cd81b070714767"
|
||||
checksum = "689960f187c43c01650c805fb6bc6f55ab944499d86d4ffe9474ad78991d8e94"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
|
|
@ -2213,6 +2219,25 @@ version = "0.3.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "sixel-image"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84880b5da046b87741b9bad6ee916919d5548cca182f009b3a240e20a7f7c99f"
|
||||
dependencies = [
|
||||
"sixel-tokenizer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sixel-tokenizer"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71b7f6629ac964d60179c1fb00dfb80da265fc3465a17245e26c1eaf678d476"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
|
|
@ -2764,7 +2789,7 @@ version = "0.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"arrayvec 0.5.2",
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
|
@ -3273,6 +3298,7 @@ name = "zellij-server"
|
|||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"arrayvec 0.7.2",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"byteorder",
|
||||
|
|
@ -3284,6 +3310,8 @@ dependencies = [
|
|||
"insta",
|
||||
"log",
|
||||
"serde_json",
|
||||
"sixel-image",
|
||||
"sixel-tokenizer",
|
||||
"sysinfo",
|
||||
"typetag",
|
||||
"unicode-width",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ pub fn get_cached_tip_name() -> String {
|
|||
}
|
||||
|
||||
let quicknav_show_count = local_cache.get_cached_data().get("quicknav").unwrap_or(&0);
|
||||
eprintln!("quicknav_show_count: {:?}", quicknav_show_count);
|
||||
if quicknav_show_count <= &MAX_CACHE_HITS {
|
||||
let _ = local_cache.caching("quicknav");
|
||||
return String::from("quicknav");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use zellij_server::panes::sixel::SixelImageStore;
|
||||
use zellij_server::panes::{LinkHandler, TerminalPane};
|
||||
use zellij_utils::data::{Palette, Style};
|
||||
use zellij_utils::pane_size::{Dimension, PaneGeom, Size};
|
||||
use zellij_utils::pane_size::{Dimension, PaneGeom, Size, SizeInPixels};
|
||||
use zellij_utils::vte;
|
||||
|
||||
use ssh2::Session;
|
||||
|
|
@ -76,6 +78,7 @@ fn start_zellij(channel: &mut ssh2::Channel) {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
|
||||
|
|
@ -90,6 +93,7 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirrored: bool) {
|
||||
|
|
@ -108,6 +112,7 @@ fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirr
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) {
|
||||
|
|
@ -121,6 +126,7 @@ fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
|
||||
|
|
@ -135,6 +141,7 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
|
||||
|
|
@ -153,6 +160,7 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn read_from_channel(
|
||||
|
|
@ -174,6 +182,11 @@ fn read_from_channel(
|
|||
let mut retries_left = 3;
|
||||
let mut should_sleep = false;
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
height: 21,
|
||||
width: 8,
|
||||
})));
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let mut terminal_output = TerminalPane::new(
|
||||
0,
|
||||
pane_geom,
|
||||
|
|
@ -181,8 +194,10 @@ fn read_from_channel(
|
|||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
Rc::new(RefCell::new(Palette::default())),
|
||||
Rc::new(RefCell::new(HashMap::new())),
|
||||
); // 0 is the pane index
|
||||
loop {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
|
|
@ -322,6 +337,7 @@ impl RemoteTerminal {
|
|||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
pub fn load_fixture(&mut self, name: &str) {
|
||||
let mut channel = self.channel.lock().unwrap();
|
||||
|
|
|
|||
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.
|
||||
//! Multiple actions at the same time can be dispatched.
|
||||
use log::debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fs, path::PathBuf, thread};
|
||||
|
||||
use crate::{
|
||||
command_is_executing::CommandIsExecuting, input_handler::input_actions,
|
||||
os_input_output::ClientOsApi, stdin_handler::stdin_loop, ClientInfo, ClientInstruction,
|
||||
InputInstruction,
|
||||
os_input_output::ClientOsApi, stdin_ansi_parser::StdinAnsiParser, stdin_handler::stdin_loop,
|
||||
ClientInfo, ClientInstruction, InputInstruction,
|
||||
};
|
||||
use zellij_utils::{
|
||||
channels::{self, ChannelWithContext, SenderWithContext},
|
||||
|
|
@ -82,12 +83,13 @@ pub fn start_fake_client(
|
|||
})
|
||||
});
|
||||
|
||||
let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
|
||||
let _stdin_thread = thread::Builder::new()
|
||||
.name("stdin_handler".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let send_input_instructions = send_input_instructions.clone();
|
||||
move || stdin_loop(os_input, send_input_instructions)
|
||||
move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser)
|
||||
});
|
||||
|
||||
let clients: Vec<ClientId>;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
//! Main input logic.
|
||||
use crate::{
|
||||
os_input_output::ClientOsApi,
|
||||
stdin_ansi_parser::{AnsiStdinInstructionOrKeys, StdinAnsiParser},
|
||||
ClientId, ClientInstruction, CommandIsExecuting, InputInstruction,
|
||||
os_input_output::ClientOsApi, stdin_ansi_parser::AnsiStdinInstruction, ClientId,
|
||||
ClientInstruction, CommandIsExecuting, InputInstruction,
|
||||
};
|
||||
use zellij_utils::{
|
||||
channels::{Receiver, SenderWithContext, OPENCALLS},
|
||||
|
|
@ -69,19 +68,6 @@ impl InputHandler {
|
|||
if self.options.mouse_mode.unwrap_or(true) {
|
||||
self.os_input.enable_mouse();
|
||||
}
|
||||
// <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 {
|
||||
if self.should_exit {
|
||||
break;
|
||||
|
|
@ -91,13 +77,7 @@ impl InputHandler {
|
|||
match input_event {
|
||||
InputEvent::Key(key_event) => {
|
||||
let key = cast_termwiz_key(key_event, &raw_bytes);
|
||||
if ansi_stdin_parser.expected_instructions() > 0 {
|
||||
self.handle_possible_pixel_instruction(
|
||||
ansi_stdin_parser.parse(key, raw_bytes),
|
||||
);
|
||||
} else {
|
||||
self.handle_key(&key, raw_bytes);
|
||||
}
|
||||
},
|
||||
InputEvent::Mouse(mouse_event) => {
|
||||
let mouse_event =
|
||||
|
|
@ -126,13 +106,13 @@ impl InputHandler {
|
|||
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
|
||||
self.mode = input_mode;
|
||||
},
|
||||
Ok((InputInstruction::PossiblePixelRatioChange, _error_context)) => {
|
||||
let _ = self
|
||||
.os_input
|
||||
.get_stdout_writer()
|
||||
.write(get_cell_pixel_info.as_bytes())
|
||||
.unwrap();
|
||||
ansi_stdin_parser.increment_expected_ansi_instructions(4);
|
||||
Ok((
|
||||
InputInstruction::AnsiStdinInstructions(ansi_stdin_instructions),
|
||||
_error_context,
|
||||
)) => {
|
||||
for ansi_instruction in ansi_stdin_instructions {
|
||||
self.handle_stdin_ansi_instruction(ansi_instruction);
|
||||
}
|
||||
},
|
||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||
}
|
||||
|
|
@ -147,33 +127,28 @@ impl InputHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
fn handle_possible_pixel_instruction(
|
||||
&mut self,
|
||||
pixel_instruction_or_keys: Option<AnsiStdinInstructionOrKeys>,
|
||||
) {
|
||||
match pixel_instruction_or_keys {
|
||||
Some(AnsiStdinInstructionOrKeys::PixelDimensions(pixel_dimensions)) => {
|
||||
fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) {
|
||||
match ansi_stdin_instructions {
|
||||
AnsiStdinInstruction::PixelDimensions(pixel_dimensions) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
|
||||
},
|
||||
Some(AnsiStdinInstructionOrKeys::BackgroundColor(background_color_instruction)) => {
|
||||
AnsiStdinInstruction::BackgroundColor(background_color_instruction) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::BackgroundColor(
|
||||
background_color_instruction,
|
||||
));
|
||||
},
|
||||
Some(AnsiStdinInstructionOrKeys::ForegroundColor(foreground_color_instruction)) => {
|
||||
AnsiStdinInstruction::ForegroundColor(foreground_color_instruction) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::ForegroundColor(
|
||||
foreground_color_instruction,
|
||||
));
|
||||
},
|
||||
Some(AnsiStdinInstructionOrKeys::Keys(keys)) => {
|
||||
for (key, raw_bytes) in keys {
|
||||
self.handle_key(&key, raw_bytes);
|
||||
}
|
||||
AnsiStdinInstruction::ColorRegisters(color_registers) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::ColorRegisters(color_registers));
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
|
||||
|
|
@ -352,7 +327,6 @@ pub(crate) fn input_loop(
|
|||
)
|
||||
.handle_input();
|
||||
}
|
||||
|
||||
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
||||
/// its [`InputHandler::handle_input()`] loop.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
@ -379,7 +353,3 @@ pub(crate) fn input_actions(
|
|||
)
|
||||
.handle_actions(actions, &session_name, clients);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./unit/input_handler_tests.rs"]
|
||||
mod input_handler_tests;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ use std::env::current_exe;
|
|||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser};
|
||||
use crate::{
|
||||
command_is_executing::CommandIsExecuting, input_handler::input_loop,
|
||||
os_input_output::ClientOsApi, stdin_handler::stdin_loop,
|
||||
|
|
@ -114,7 +116,7 @@ impl ClientInfo {
|
|||
pub(crate) enum InputInstruction {
|
||||
KeyEvent(InputEvent, Vec<u8>),
|
||||
SwitchToMode(InputMode),
|
||||
PossiblePixelRatioChange,
|
||||
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
|
||||
}
|
||||
|
||||
pub fn start_client(
|
||||
|
|
@ -126,7 +128,7 @@ pub fn start_client(
|
|||
layout: Option<LayoutFromYaml>,
|
||||
) {
|
||||
info!("Starting Zellij client!");
|
||||
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
||||
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
||||
let take_snapshot = "\u{1b}[?1049h";
|
||||
let bracketed_paste = "\u{1b}[?2004h";
|
||||
os_input.unset_raw_mode(0).unwrap();
|
||||
|
|
@ -162,11 +164,13 @@ pub fn start_client(
|
|||
let first_msg = match info {
|
||||
ClientInfo::Attach(name, config_options) => {
|
||||
envs::set_session_name(name);
|
||||
envs::set_initial_environment_vars();
|
||||
|
||||
ClientToServerMsg::AttachClient(client_attributes, config_options)
|
||||
},
|
||||
ClientInfo::New(name) => {
|
||||
envs::set_session_name(name);
|
||||
envs::set_initial_environment_vars();
|
||||
|
||||
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
|
||||
|
||||
|
|
@ -214,13 +218,15 @@ pub fn start_client(
|
|||
});
|
||||
|
||||
let on_force_close = config_options.on_force_close.unwrap_or_default();
|
||||
let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
|
||||
|
||||
let _stdin_thread = thread::Builder::new()
|
||||
.name("stdin_handler".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let send_input_instructions = send_input_instructions.clone();
|
||||
move || stdin_loop(os_input, send_input_instructions)
|
||||
let stdin_ansi_parser = stdin_ansi_parser.clone();
|
||||
move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser)
|
||||
});
|
||||
|
||||
let _input_thread = thread::Builder::new()
|
||||
|
|
@ -246,7 +252,6 @@ pub fn start_client(
|
|||
let _signal_thread = thread::Builder::new()
|
||||
.name("signal_listener".to_string())
|
||||
.spawn({
|
||||
let send_input_instructions = send_input_instructions.clone();
|
||||
let os_input = os_input.clone();
|
||||
move || {
|
||||
os_input.handle_signals(
|
||||
|
|
@ -256,8 +261,16 @@ pub fn start_client(
|
|||
os_api.send_to_server(ClientToServerMsg::TerminalResize(
|
||||
os_api.get_terminal_size_using_fd(0),
|
||||
));
|
||||
let _ = send_input_instructions
|
||||
.send(InputInstruction::PossiblePixelRatioChange);
|
||||
// send a query to the terminal emulator in case the font size changed
|
||||
// as well - we'll parse the response through STDIN
|
||||
let terminal_emulator_query_string = stdin_ansi_parser
|
||||
.lock()
|
||||
.unwrap()
|
||||
.window_size_change_query_string();
|
||||
let _ = os_api
|
||||
.get_stdout_writer()
|
||||
.write(terminal_emulator_query_string.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
}),
|
||||
Box::new({
|
||||
|
|
@ -385,3 +398,7 @@ pub fn start_client(
|
|||
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./unit/stdin_tests.rs"]
|
||||
mod stdin_tests;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ pub trait ClientOsApi: Send + Sync {
|
|||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
|
||||
/// 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.
|
||||
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
||||
/// Sends a message to the server.
|
||||
|
|
@ -126,7 +126,7 @@ impl ClientOsApi for ClientOsInputOutput {
|
|||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||
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 mut stdin = stdin.lock();
|
||||
let buffer = stdin.fill_buf().unwrap();
|
||||
|
|
|
|||
|
|
@ -1,134 +1,131 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
const STARTUP_PARSE_DEADLINE_MS: u64 = 500;
|
||||
const SIGWINCH_PARSE_DEADLINE_MS: u64 = 200;
|
||||
use zellij_utils::{
|
||||
data::{CharOrArrow, Key},
|
||||
ipc::PixelDimensions,
|
||||
lazy_static::lazy_static,
|
||||
pane_size::SizeInPixels,
|
||||
regex::Regex,
|
||||
ipc::PixelDimensions, lazy_static::lazy_static, pane_size::SizeInPixels, regex::Regex,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StdinAnsiParser {
|
||||
expected_ansi_instructions: usize,
|
||||
current_buffer: Vec<(Key, Vec<u8>)>,
|
||||
raw_buffer: Vec<u8>,
|
||||
pending_color_sequences: Vec<(usize, String)>,
|
||||
pending_events: Vec<AnsiStdinInstruction>,
|
||||
parse_deadline: Option<Instant>,
|
||||
}
|
||||
|
||||
impl StdinAnsiParser {
|
||||
pub fn new() -> Self {
|
||||
StdinAnsiParser {
|
||||
expected_ansi_instructions: 0,
|
||||
current_buffer: vec![],
|
||||
raw_buffer: vec![],
|
||||
pending_color_sequences: vec![],
|
||||
pending_events: vec![],
|
||||
parse_deadline: None,
|
||||
}
|
||||
}
|
||||
pub fn increment_expected_ansi_instructions(&mut self, to: usize) {
|
||||
self.expected_ansi_instructions += to;
|
||||
pub fn terminal_emulator_query_string(&mut self) -> String {
|
||||
// note that this assumes the String will be sent to the terminal emulator and so starts a
|
||||
// deadline timeout (self.parse_deadline)
|
||||
|
||||
// <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));
|
||||
}
|
||||
pub fn decrement_expected_ansi_instructions(&mut self, by: usize) {
|
||||
self.expected_ansi_instructions = self.expected_ansi_instructions.saturating_sub(by);
|
||||
self.parse_deadline =
|
||||
Some(Instant::now() + Duration::from_millis(STARTUP_PARSE_DEADLINE_MS));
|
||||
query_string
|
||||
}
|
||||
pub fn expected_instructions(&self) -> usize {
|
||||
self.expected_ansi_instructions
|
||||
pub fn window_size_change_query_string(&mut self) -> String {
|
||||
// note that this assumes the String will be sent to the terminal emulator and so starts a
|
||||
// deadline timeout (self.parse_deadline)
|
||||
|
||||
// <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 parse(&mut self, key: Key, raw_bytes: Vec<u8>) -> Option<AnsiStdinInstructionOrKeys> {
|
||||
if self.current_buffer.is_empty()
|
||||
&& (key != Key::Esc && key != Key::Alt(CharOrArrow::Char(']')))
|
||||
fn drain_pending_events(&mut self) -> Vec<AnsiStdinInstruction> {
|
||||
let mut events = vec![];
|
||||
events.append(&mut self.pending_events);
|
||||
if let Some(color_registers) =
|
||||
AnsiStdinInstruction::color_registers_from_bytes(&mut self.pending_color_sequences)
|
||||
{
|
||||
// the first key of a sequence is always Esc, but termwiz interprets esc + ] as Alt+]
|
||||
self.current_buffer.push((key, raw_bytes));
|
||||
self.expected_ansi_instructions = 0;
|
||||
return Some(AnsiStdinInstructionOrKeys::Keys(
|
||||
self.current_buffer.drain(..).collect(),
|
||||
));
|
||||
events.push(color_registers);
|
||||
}
|
||||
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)
|
||||
events
|
||||
}
|
||||
pub fn should_parse(&self) -> bool {
|
||||
if let Some(parse_deadline) = self.parse_deadline {
|
||||
if parse_deadline >= Instant::now() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn parse(&mut self, mut raw_bytes: Vec<u8>) -> Vec<AnsiStdinInstruction> {
|
||||
for byte in raw_bytes.drain(..) {
|
||||
self.parse_byte(byte);
|
||||
}
|
||||
self.drain_pending_events()
|
||||
}
|
||||
fn parse_byte(&mut self, byte: u8) {
|
||||
if byte == b't' {
|
||||
self.raw_buffer.push(byte);
|
||||
match AnsiStdinInstruction::pixel_dimensions_from_bytes(&self.raw_buffer) {
|
||||
Ok(ansi_sequence) => {
|
||||
self.pending_events.push(ansi_sequence);
|
||||
self.raw_buffer.clear();
|
||||
},
|
||||
Err(_) => {
|
||||
self.expected_ansi_instructions = 0;
|
||||
Some(AnsiStdinInstructionOrKeys::Keys(
|
||||
self.current_buffer.drain(..).collect(),
|
||||
))
|
||||
self.raw_buffer.clear();
|
||||
},
|
||||
}
|
||||
} 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 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.current_buffer.push((key, raw_bytes));
|
||||
self.expected_ansi_instructions = 0;
|
||||
Some(AnsiStdinInstructionOrKeys::Keys(
|
||||
self.current_buffer.drain(..).collect(),
|
||||
))
|
||||
self.raw_buffer.clear();
|
||||
}
|
||||
}
|
||||
fn key_is_valid(&self, key: Key) -> bool {
|
||||
match key {
|
||||
Key::Esc => {
|
||||
// this is a UX improvement
|
||||
// in case the user's terminal doesn't support one or more of these signals,
|
||||
// if they spam ESC they need to be able to get back to normal mode and not "us
|
||||
// waiting for ansi instructions" mode
|
||||
!self.current_buffer.iter().any(|(key, _)| *key == Key::Esc)
|
||||
},
|
||||
Key::Char(';')
|
||||
| Key::Char('[')
|
||||
| Key::Char(']')
|
||||
| Key::Char('r')
|
||||
| Key::Char('g')
|
||||
| Key::Char('b')
|
||||
| Key::Char('\\')
|
||||
| Key::Char(':')
|
||||
| Key::Char('/') => true,
|
||||
Key::Alt(CharOrArrow::Char(']')) => true,
|
||||
Key::Alt(CharOrArrow::Char('\\')) => true,
|
||||
Key::Char(c) => {
|
||||
matches!(c, '0'..='9' | 'a'..='f')
|
||||
},
|
||||
_ => false,
|
||||
} else {
|
||||
self.raw_buffer.push(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnsiStdinInstructionOrKeys {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AnsiStdinInstruction {
|
||||
PixelDimensions(PixelDimensions),
|
||||
BackgroundColor(String),
|
||||
ForegroundColor(String),
|
||||
Keys(Vec<(Key, Vec<u8>)>),
|
||||
ColorRegisters(Vec<(usize, String)>),
|
||||
}
|
||||
|
||||
impl AnsiStdinInstructionOrKeys {
|
||||
pub fn pixel_dimensions_from_keys(keys: &[(Key, Vec<u8>)]) -> Result<Self, &'static str> {
|
||||
impl AnsiStdinInstruction {
|
||||
pub fn pixel_dimensions_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
|
||||
// eg. <ESC>[4;21;8t
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"^\u{1b}\[(\d+);(\d+);(\d+)t$").unwrap();
|
||||
}
|
||||
let key_sequence: Vec<Option<char>> = keys
|
||||
.iter()
|
||||
.map(|(key, _)| match key {
|
||||
Key::Char(c) => Some(*c),
|
||||
Key::Esc => Some('\u{1b}'),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
if key_sequence.iter().all(|k| k.is_some()) {
|
||||
let key_string: String = key_sequence.iter().map(|k| k.unwrap()).collect();
|
||||
let key_string = String::from_utf8_lossy(bytes); // TODO: handle error
|
||||
let captures = RE
|
||||
.captures_iter(&key_string)
|
||||
.next()
|
||||
|
|
@ -142,59 +139,96 @@ impl AnsiStdinInstructionOrKeys {
|
|||
match csi_index {
|
||||
Ok(4) => {
|
||||
// text area size
|
||||
Ok(AnsiStdinInstructionOrKeys::PixelDimensions(
|
||||
PixelDimensions {
|
||||
Ok(AnsiStdinInstruction::PixelDimensions(PixelDimensions {
|
||||
character_cell_size: None,
|
||||
text_area_size: Some(SizeInPixels {
|
||||
height: first_field.unwrap(),
|
||||
width: second_field.unwrap(),
|
||||
}),
|
||||
},
|
||||
))
|
||||
}))
|
||||
},
|
||||
Ok(6) => {
|
||||
// character cell size
|
||||
Ok(AnsiStdinInstructionOrKeys::PixelDimensions(
|
||||
PixelDimensions {
|
||||
Ok(AnsiStdinInstruction::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! {
|
||||
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! {
|
||||
static ref FOREGROUND_RE: Regex = Regex::new(r"10;(.*)$").unwrap();
|
||||
static ref FOREGROUND_RE: Regex = Regex::new(r"\]10;(.*)\u{1b}\\$").unwrap();
|
||||
}
|
||||
let key_string = keys.iter().fold(String::new(), |mut acc, (key, _)| {
|
||||
if let Key::Char(c) = key {
|
||||
acc.push(*c)
|
||||
};
|
||||
acc
|
||||
});
|
||||
let key_string = String::from_utf8_lossy(bytes);
|
||||
if let Some(captures) = BACKGROUND_RE.captures_iter(&key_string).next() {
|
||||
let background_query_response = captures[1].parse::<String>();
|
||||
Ok(AnsiStdinInstructionOrKeys::BackgroundColor(
|
||||
background_query_response.unwrap(),
|
||||
))
|
||||
match background_query_response {
|
||||
Ok(background_query_response) => Ok(AnsiStdinInstruction::BackgroundColor(
|
||||
background_query_response,
|
||||
)),
|
||||
_ => Err("invalid_instruction"),
|
||||
}
|
||||
} else if let Some(captures) = FOREGROUND_RE.captures_iter(&key_string).next() {
|
||||
let foreground_query_response = captures[1].parse::<String>();
|
||||
Ok(AnsiStdinInstructionOrKeys::ForegroundColor(
|
||||
foreground_query_response.unwrap(),
|
||||
))
|
||||
match foreground_query_response {
|
||||
Ok(foreground_query_response) => Ok(AnsiStdinInstruction::ForegroundColor(
|
||||
foreground_query_response,
|
||||
)),
|
||||
_ => Err("invalid_instruction"),
|
||||
}
|
||||
} else {
|
||||
Err("invalid_instruction")
|
||||
}
|
||||
}
|
||||
pub fn color_registers_from_bytes(color_sequences: &mut Vec<(usize, String)>) -> Option<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::stdin_ansi_parser::StdinAnsiParser;
|
||||
use crate::InputInstruction;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use zellij_utils::channels::SenderWithContext;
|
||||
use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons};
|
||||
|
||||
pub(crate) fn stdin_loop(
|
||||
os_input: Box<dyn ClientOsApi>,
|
||||
mut os_input: Box<dyn ClientOsApi>,
|
||||
send_input_instructions: SenderWithContext<InputInstruction>,
|
||||
stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>,
|
||||
) {
|
||||
let mut holding_mouse = false;
|
||||
let mut input_parser = InputParser::new();
|
||||
let mut current_buffer = vec![];
|
||||
// on startup we send a query to the terminal emulator for stuff like the pixel size and colors
|
||||
// we get a response through STDIN, so it makes sense to do this here
|
||||
let terminal_emulator_query_string = stdin_ansi_parser
|
||||
.lock()
|
||||
.unwrap()
|
||||
.terminal_emulator_query_string();
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(terminal_emulator_query_string.as_bytes())
|
||||
.unwrap();
|
||||
loop {
|
||||
let buf = os_input.read_from_stdin();
|
||||
{
|
||||
// here we check if we need to parse specialized ANSI instructions sent over STDIN
|
||||
// this happens either on startup (see above) or on SIGWINCH
|
||||
//
|
||||
// if we need to parse them, we do so with an internal timeout - anything else we
|
||||
// receive on STDIN during that timeout is unceremoniously dropped
|
||||
let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
|
||||
if stdin_ansi_parser.should_parse() {
|
||||
let events = stdin_ansi_parser.parse(buf);
|
||||
if !events.is_empty() {
|
||||
let _ = send_input_instructions
|
||||
.send(InputInstruction::AnsiStdinInstructions(events));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
current_buffer.append(&mut buf.to_vec());
|
||||
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
||||
let mut events = vec![];
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
close_fds = "0.3.2"
|
||||
sysinfo = "0.22.5"
|
||||
sixel-tokenizer = "0.1.0"
|
||||
sixel-image = "0.1.0"
|
||||
arrayvec = "0.7.2"
|
||||
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::panes::selection::Selection;
|
|||
use crate::panes::Row;
|
||||
|
||||
use crate::{
|
||||
panes::sixel::SixelImageStore,
|
||||
panes::terminal_character::{AnsiCode, CharacterStyles},
|
||||
panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
ClientId,
|
||||
|
|
@ -16,6 +17,7 @@ use std::{
|
|||
str,
|
||||
};
|
||||
use zellij_utils::pane_size::PaneGeom;
|
||||
use zellij_utils::pane_size::SizeInPixels;
|
||||
|
||||
fn vte_goto_instruction(x_coords: usize, y_coords: usize, vte_output: &mut String) {
|
||||
write!(
|
||||
|
|
@ -54,7 +56,6 @@ fn write_changed_styles(
|
|||
if let Some(new_styles) =
|
||||
character_styles.update_and_return_diff(¤t_character_styles, chunk_changed_colors)
|
||||
{
|
||||
// if let Some(osc8_link) = link_handler.as_ref().and_then(|l_h| l_h.borrow().output_osc8(new_styles.link_anchor)) {
|
||||
if let Some(osc8_link) =
|
||||
link_handler.and_then(|l_h| l_h.output_osc8(new_styles.link_anchor))
|
||||
{
|
||||
|
|
@ -65,11 +66,14 @@ fn write_changed_styles(
|
|||
}
|
||||
}
|
||||
|
||||
fn serialize_character_chunks(
|
||||
fn serialize_chunks(
|
||||
character_chunks: Vec<CharacterChunk>,
|
||||
sixel_chunks: Option<&Vec<SixelImageChunk>>,
|
||||
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
||||
sixel_image_store: &mut SixelImageStore,
|
||||
) -> String {
|
||||
let mut vte_output = String::new(); // TODO: preallocate character_chunks.len()?
|
||||
let mut vte_output = String::new();
|
||||
let mut sixel_vte: Option<String> = None;
|
||||
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
||||
for character_chunk in character_chunks {
|
||||
let chunk_selection_and_background_color = character_chunk.selection_and_background_color();
|
||||
|
|
@ -96,6 +100,32 @@ fn serialize_character_chunks(
|
|||
}
|
||||
character_styles.clear();
|
||||
}
|
||||
if let Some(sixel_chunks) = sixel_chunks {
|
||||
for sixel_chunk in sixel_chunks {
|
||||
let serialized_sixel_image = sixel_image_store.serialize_image(
|
||||
sixel_chunk.sixel_image_id,
|
||||
sixel_chunk.sixel_image_pixel_x,
|
||||
sixel_chunk.sixel_image_pixel_y,
|
||||
sixel_chunk.sixel_image_pixel_width,
|
||||
sixel_chunk.sixel_image_pixel_height,
|
||||
);
|
||||
if let Some(serialized_sixel_image) = serialized_sixel_image {
|
||||
let sixel_vte = sixel_vte.get_or_insert_with(String::new);
|
||||
vte_goto_instruction(sixel_chunk.cell_x, sixel_chunk.cell_y, sixel_vte);
|
||||
sixel_vte.push_str(&serialized_sixel_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref sixel_vte) = sixel_vte {
|
||||
// we do this at the end because of the implied z-index,
|
||||
// images should be above text unless the text was explicitly inserted after them (the
|
||||
// latter being a case we handle in our own internal state and not in the output)
|
||||
let save_cursor_position = "\u{1b}[s";
|
||||
let restore_cursor_position = "\u{1b}[u";
|
||||
vte_output.push_str(save_cursor_position);
|
||||
vte_output.push_str(sixel_vte);
|
||||
vte_output.push_str(restore_cursor_position);
|
||||
}
|
||||
vte_output
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +178,24 @@ pub struct Output {
|
|||
pre_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||
post_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||
client_character_chunks: HashMap<ClientId, Vec<CharacterChunk>>,
|
||||
sixel_chunks: HashMap<ClientId, Vec<SixelImageChunk>>,
|
||||
link_handler: Option<Rc<RefCell<LinkHandler>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
floating_panes_stack: Option<FloatingPanesStack>,
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
client_ids: &HashSet<ClientId>,
|
||||
|
|
@ -240,6 +283,48 @@ impl Output {
|
|||
.or_insert_with(Vec::new);
|
||||
entry.push(String::from(vte_instruction));
|
||||
}
|
||||
pub fn add_sixel_image_chunks_to_client(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
sixel_image_chunks: Vec<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> {
|
||||
let mut serialized_render_instructions = HashMap::new();
|
||||
|
||||
|
|
@ -256,9 +341,11 @@ impl Output {
|
|||
}
|
||||
|
||||
// append the actual vte
|
||||
client_serialized_render_instructions.push_str(&serialize_character_chunks(
|
||||
client_serialized_render_instructions.push_str(&serialize_chunks(
|
||||
client_character_chunks,
|
||||
self.sixel_chunks.get(&client_id),
|
||||
self.link_handler.as_mut(),
|
||||
&mut self.sixel_image_store.borrow_mut(),
|
||||
)); // TODO: less allocations?
|
||||
|
||||
// append post-vte instructions for this client
|
||||
|
|
@ -319,6 +406,26 @@ impl FloatingPanesStack {
|
|||
}
|
||||
visible_chunks
|
||||
}
|
||||
pub fn visible_sixel_image_chunks(
|
||||
&self,
|
||||
mut sixel_image_chunks: Vec<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(
|
||||
&self,
|
||||
pane_geom: &PaneGeom,
|
||||
|
|
@ -368,6 +475,165 @@ impl FloatingPanesStack {
|
|||
};
|
||||
None
|
||||
}
|
||||
fn remove_covered_sixel_parts(
|
||||
&self,
|
||||
pane_geom: &PaneGeom,
|
||||
s_chunk: &SixelImageChunk,
|
||||
character_cell_size: &SizeInPixels,
|
||||
) -> Vec<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)]
|
||||
|
|
@ -379,6 +645,17 @@ pub struct CharacterChunk {
|
|||
selection_and_background_color: Option<(Selection, AnsiCode)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct SixelImageChunk {
|
||||
pub cell_x: usize,
|
||||
pub cell_y: usize,
|
||||
pub sixel_image_pixel_x: usize,
|
||||
pub sixel_image_pixel_y: usize,
|
||||
pub sixel_image_pixel_width: usize,
|
||||
pub sixel_image_pixel_height: usize,
|
||||
pub sixel_image_id: usize,
|
||||
}
|
||||
|
||||
impl CharacterChunk {
|
||||
pub fn new(terminal_characters: Vec<TerminalCharacter>, x: usize, y: usize) -> Self {
|
||||
CharacterChunk {
|
||||
|
|
@ -480,8 +757,8 @@ impl CharacterChunk {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OutputBuffer {
|
||||
changed_lines: Vec<usize>, // line index
|
||||
should_update_all_lines: bool,
|
||||
pub changed_lines: Vec<usize>, // line index
|
||||
pub should_update_all_lines: bool,
|
||||
}
|
||||
|
||||
impl Default for OutputBuffer {
|
||||
|
|
@ -520,6 +797,7 @@ impl OutputBuffer {
|
|||
for line_index in 0..viewport_height {
|
||||
let terminal_characters =
|
||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
||||
|
||||
let x = x_offset; // right now we only buffer full lines as this doesn't seem to have a huge impact on performance, but the infra is here if we want to change this
|
||||
let y = line_index + y_offset;
|
||||
changed_chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
||||
|
|
@ -568,4 +846,42 @@ impl OutputBuffer {
|
|||
},
|
||||
}
|
||||
}
|
||||
pub fn changed_rects_in_viewport(&self, viewport_height: usize) -> HashMap<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::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
use zellij_utils::regex::Regex;
|
||||
|
|
@ -24,7 +26,7 @@ pub const MAX_TITLE_STACK_SIZE: usize = 1000;
|
|||
use vte::{Params, Perform};
|
||||
use zellij_utils::{consts::VERSION, shared::version_number};
|
||||
|
||||
use crate::output::{CharacterChunk, OutputBuffer};
|
||||
use crate::output::{CharacterChunk, OutputBuffer, SixelImageChunk};
|
||||
use crate::panes::alacritty_functions::{parse_number, xparse_color};
|
||||
use crate::panes::link_handler::LinkHandler;
|
||||
use crate::panes::selection::Selection;
|
||||
|
|
@ -112,6 +114,7 @@ fn get_top_canonical_row_and_wraps(rows: &mut Vec<Row>) -> Vec<Row> {
|
|||
fn transfer_rows_from_lines_above_to_viewport(
|
||||
lines_above: &mut VecDeque<Row>,
|
||||
viewport: &mut Vec<Row>,
|
||||
sixel_grid: &mut SixelGrid,
|
||||
count: usize,
|
||||
max_viewport_width: usize,
|
||||
) -> usize {
|
||||
|
|
@ -143,7 +146,7 @@ fn transfer_rows_from_lines_above_to_viewport(
|
|||
}
|
||||
if !next_lines.is_empty() {
|
||||
let excess_row = Row::from_rows(next_lines, 0);
|
||||
bounded_push(lines_above, excess_row);
|
||||
bounded_push(lines_above, sixel_grid, excess_row);
|
||||
}
|
||||
match usize::try_from(lines_added_to_viewport) {
|
||||
Ok(n) => n,
|
||||
|
|
@ -154,6 +157,7 @@ fn transfer_rows_from_lines_above_to_viewport(
|
|||
fn transfer_rows_from_viewport_to_lines_above(
|
||||
viewport: &mut Vec<Row>,
|
||||
lines_above: &mut VecDeque<Row>,
|
||||
sixel_grid: &mut SixelGrid,
|
||||
count: usize,
|
||||
max_viewport_width: usize,
|
||||
) -> isize {
|
||||
|
|
@ -176,7 +180,7 @@ fn transfer_rows_from_viewport_to_lines_above(
|
|||
break; // no more rows
|
||||
}
|
||||
}
|
||||
let dropped_line_width = bounded_push(lines_above, next_lines.remove(0));
|
||||
let dropped_line_width = bounded_push(lines_above, sixel_grid, next_lines.remove(0));
|
||||
if let Some(width) = dropped_line_width {
|
||||
transferred_rows_count -=
|
||||
calculate_row_display_height(width, max_viewport_width) as isize;
|
||||
|
|
@ -232,11 +236,12 @@ fn transfer_rows_from_lines_below_to_viewport(
|
|||
}
|
||||
}
|
||||
|
||||
fn bounded_push(vec: &mut VecDeque<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;
|
||||
if vec.len() >= *SCROLL_BUFFER_SIZE.get().unwrap() {
|
||||
let line = vec.pop_front();
|
||||
if let Some(line) = line {
|
||||
sixel_grid.offset_grid_top();
|
||||
dropped_line_width = Some(line.width());
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +303,7 @@ pub struct Grid {
|
|||
viewport: Vec<Row>,
|
||||
lines_below: Vec<Row>,
|
||||
horizontal_tabstops: BTreeSet<usize>,
|
||||
alternate_lines_above_viewport_and_cursor: Option<(VecDeque<Row>, Vec<Row>, Cursor)>,
|
||||
alternate_screen_state: Option<AlternateScreenState>,
|
||||
cursor: Cursor,
|
||||
saved_cursor_position: Option<Cursor>,
|
||||
// FIXME: change scroll_region to be (usize, usize) - where the top line is always the first
|
||||
|
|
@ -309,14 +314,17 @@ pub struct Grid {
|
|||
active_charset: CharsetIndex,
|
||||
preceding_char: Option<TerminalCharacter>,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
output_buffer: OutputBuffer,
|
||||
title_stack: Vec<String>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_grid: SixelGrid,
|
||||
pub changed_colors: Option<[Option<AnsiCode>; 256]>,
|
||||
pub should_render: bool,
|
||||
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "[D")
|
||||
pub bracketed_paste_mode: bool, // when set, paste instructions to the terminal should be escaped with a special sequence
|
||||
pub erasure_mode: bool, // ERM
|
||||
pub sixel_scrolling: bool, // DECSDM
|
||||
pub insert_mode: bool,
|
||||
pub disable_linewrap: bool,
|
||||
pub clear_viewport_before_rendering: bool,
|
||||
|
|
@ -334,7 +342,41 @@ pub struct Grid {
|
|||
|
||||
impl Debug for Grid {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
for (i, row) in self.viewport.iter().enumerate() {
|
||||
let mut buffer: Vec<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 {
|
||||
writeln!(f, "{:02?} (C): {:?}", i, row)?;
|
||||
} else {
|
||||
|
|
@ -350,9 +392,12 @@ impl Grid {
|
|||
rows: usize,
|
||||
columns: usize,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
) -> Self {
|
||||
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
|
||||
Grid {
|
||||
lines_above: VecDeque::with_capacity(
|
||||
// .get_or_init() is used instead of .get().unwrap() to prevent
|
||||
|
|
@ -372,13 +417,15 @@ impl Grid {
|
|||
cursor_key_mode: false,
|
||||
bracketed_paste_mode: false,
|
||||
erasure_mode: false,
|
||||
sixel_scrolling: false,
|
||||
insert_mode: false,
|
||||
disable_linewrap: false,
|
||||
alternate_lines_above_viewport_and_cursor: None,
|
||||
alternate_screen_state: None,
|
||||
clear_viewport_before_rendering: false,
|
||||
active_charset: Default::default(),
|
||||
pending_messages_to_pty: vec![],
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
output_buffer: Default::default(),
|
||||
selection: Default::default(),
|
||||
title_stack: vec![],
|
||||
|
|
@ -390,6 +437,7 @@ impl Grid {
|
|||
scrollback_buffer_lines: 0,
|
||||
mouse_mode: false,
|
||||
character_cell_size,
|
||||
sixel_grid,
|
||||
}
|
||||
}
|
||||
pub fn render_full_viewport(&mut self) {
|
||||
|
|
@ -534,6 +582,7 @@ impl Grid {
|
|||
let transferred_rows_height = transfer_rows_from_lines_above_to_viewport(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.sixel_grid,
|
||||
1,
|
||||
self.width,
|
||||
);
|
||||
|
|
@ -560,7 +609,8 @@ impl Grid {
|
|||
last_line_above
|
||||
};
|
||||
|
||||
let dropped_line_width = bounded_push(&mut self.lines_above, line_to_push_up);
|
||||
let dropped_line_width =
|
||||
bounded_push(&mut self.lines_above, &mut self.sixel_grid, line_to_push_up);
|
||||
if let Some(width) = dropped_line_width {
|
||||
let dropped_line_height = calculate_row_display_height(width, self.width);
|
||||
|
||||
|
|
@ -607,7 +657,8 @@ impl Grid {
|
|||
return;
|
||||
}
|
||||
self.selection.reset();
|
||||
if new_columns != self.width && self.alternate_lines_above_viewport_and_cursor.is_none() {
|
||||
self.sixel_grid.character_cell_size_possibly_changed();
|
||||
if new_columns != self.width && self.alternate_screen_state.is_none() {
|
||||
self.horizontal_tabstops = create_horizontal_tabstops(new_columns);
|
||||
let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
|
||||
let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line();
|
||||
|
|
@ -697,6 +748,7 @@ impl Grid {
|
|||
transfer_rows_from_lines_above_to_viewport(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.sixel_grid,
|
||||
row_count_to_transfer,
|
||||
new_columns,
|
||||
);
|
||||
|
|
@ -713,6 +765,7 @@ impl Grid {
|
|||
transfer_rows_from_viewport_to_lines_above(
|
||||
&mut self.viewport,
|
||||
&mut self.lines_above,
|
||||
&mut self.sixel_grid,
|
||||
row_count_to_transfer,
|
||||
new_columns,
|
||||
);
|
||||
|
|
@ -725,9 +778,7 @@ impl Grid {
|
|||
saved_cursor_position.y = new_cursor_y;
|
||||
saved_cursor_position.x = new_cursor_x;
|
||||
};
|
||||
} else if new_columns != self.width
|
||||
&& self.alternate_lines_above_viewport_and_cursor.is_some()
|
||||
{
|
||||
} else if new_columns != self.width && self.alternate_screen_state.is_some() {
|
||||
// in alternate screen just truncate exceeding width
|
||||
for row in &mut self.viewport {
|
||||
if row.width() >= new_columns {
|
||||
|
|
@ -744,6 +795,7 @@ impl Grid {
|
|||
transfer_rows_from_lines_above_to_viewport(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.sixel_grid,
|
||||
row_count_to_transfer,
|
||||
new_columns,
|
||||
);
|
||||
|
|
@ -763,13 +815,16 @@ impl Grid {
|
|||
} else {
|
||||
self.cursor.y -= row_count_to_transfer;
|
||||
if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
|
||||
saved_cursor_position.y -= row_count_to_transfer
|
||||
saved_cursor_position.y = saved_cursor_position
|
||||
.y
|
||||
.saturating_sub(row_count_to_transfer);
|
||||
};
|
||||
}
|
||||
if self.alternate_lines_above_viewport_and_cursor.is_none() {
|
||||
if self.alternate_screen_state.is_none() {
|
||||
transfer_rows_from_viewport_to_lines_above(
|
||||
&mut self.viewport,
|
||||
&mut self.lines_above,
|
||||
&mut self.sixel_grid,
|
||||
row_count_to_transfer,
|
||||
new_columns,
|
||||
);
|
||||
|
|
@ -813,16 +868,34 @@ impl Grid {
|
|||
}
|
||||
lines
|
||||
}
|
||||
pub fn read_changes(&mut self, x_offset: usize, y_offset: usize) -> Vec<CharacterChunk> {
|
||||
let changes = self.output_buffer.changed_chunks_in_viewport(
|
||||
pub fn read_changes(
|
||||
&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.width,
|
||||
self.height,
|
||||
x_offset,
|
||||
y_offset,
|
||||
);
|
||||
let changed_rects = self
|
||||
.output_buffer
|
||||
.changed_rects_in_viewport(self.viewport.len());
|
||||
let changed_sixel_image_chunks = self.sixel_grid.changed_sixel_chunks_in_viewport(
|
||||
changed_rects,
|
||||
self.lines_above.len(),
|
||||
self.width,
|
||||
x_offset,
|
||||
y_offset,
|
||||
);
|
||||
if let Some(image_ids_to_reap) = self.sixel_grid.drain_image_ids_to_reap() {
|
||||
self.sixel_grid.reap_images(image_ids_to_reap);
|
||||
}
|
||||
self.output_buffer.clear();
|
||||
changes
|
||||
|
||||
(changed_character_chunks, changed_sixel_image_chunks)
|
||||
}
|
||||
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||
if self.cursor.is_hidden {
|
||||
|
|
@ -901,7 +974,7 @@ impl Grid {
|
|||
}
|
||||
}
|
||||
pub fn fill_viewport(&mut self, character: TerminalCharacter) {
|
||||
if self.alternate_lines_above_viewport_and_cursor.is_some() {
|
||||
if self.alternate_screen_state.is_some() {
|
||||
self.viewport.clear();
|
||||
} else {
|
||||
self.transfer_rows_to_lines_above(self.viewport.len())
|
||||
|
|
@ -927,7 +1000,7 @@ impl Grid {
|
|||
return;
|
||||
}
|
||||
if scroll_region_bottom == self.height - 1 && scroll_region_top == 0 {
|
||||
if self.alternate_lines_above_viewport_and_cursor.is_none() {
|
||||
if self.alternate_screen_state.is_none() {
|
||||
self.transfer_rows_to_lines_above(1);
|
||||
} else {
|
||||
self.viewport.remove(0);
|
||||
|
|
@ -963,9 +1036,10 @@ impl Grid {
|
|||
}
|
||||
if self.cursor.y == self.height - 1 {
|
||||
if self.scroll_region.is_none() {
|
||||
if self.alternate_lines_above_viewport_and_cursor.is_none() {
|
||||
if self.alternate_screen_state.is_none() {
|
||||
self.transfer_rows_to_lines_above(1);
|
||||
} else {
|
||||
self.sixel_grid.offset_grid_top();
|
||||
self.viewport.remove(0);
|
||||
}
|
||||
|
||||
|
|
@ -997,6 +1071,27 @@ impl Grid {
|
|||
} else {
|
||||
row.add_character_at(terminal_character, self.cursor.x);
|
||||
}
|
||||
if let Some(character_cell_size) = *self.character_cell_size.borrow() {
|
||||
let scrollback_size_in_pixels =
|
||||
self.lines_above.len() * character_cell_size.height;
|
||||
let absolute_x_in_pixels = self.cursor.x * character_cell_size.width;
|
||||
let absolute_y_in_pixels =
|
||||
scrollback_size_in_pixels + (self.cursor.y * character_cell_size.height);
|
||||
let rect_to_cut_out = PixelRect {
|
||||
x: absolute_x_in_pixels,
|
||||
y: absolute_y_in_pixels as isize,
|
||||
width: character_cell_size.width,
|
||||
height: character_cell_size.height,
|
||||
};
|
||||
if let Some(images_to_cut_out) =
|
||||
self.sixel_grid.cut_off_rect_from_images(rect_to_cut_out)
|
||||
{
|
||||
for (image_id, rect_in_image_to_cut_out) in images_to_cut_out {
|
||||
self.sixel_grid
|
||||
.remove_pixels_from_image(image_id, rect_in_image_to_cut_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.output_buffer.update_line(self.cursor.y);
|
||||
},
|
||||
None => {
|
||||
|
|
@ -1088,7 +1183,7 @@ impl Grid {
|
|||
fn line_wrap(&mut self) {
|
||||
self.cursor.x = 0;
|
||||
if self.cursor.y == self.height - 1 {
|
||||
if self.alternate_lines_above_viewport_and_cursor.is_none() {
|
||||
if self.alternate_screen_state.is_none() {
|
||||
self.transfer_rows_to_lines_above(1);
|
||||
} else {
|
||||
self.viewport.remove(0);
|
||||
|
|
@ -1333,7 +1428,7 @@ impl Grid {
|
|||
self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap());
|
||||
self.lines_below = vec![];
|
||||
self.viewport = vec![Row::new(self.width).canonical()];
|
||||
self.alternate_lines_above_viewport_and_cursor = None;
|
||||
self.alternate_screen_state = None;
|
||||
self.cursor_key_mode = false;
|
||||
self.scroll_region = None;
|
||||
self.clear_viewport_before_rendering = true;
|
||||
|
|
@ -1346,6 +1441,10 @@ impl Grid {
|
|||
self.output_buffer.update_all_lines();
|
||||
self.changed_colors = None;
|
||||
self.scrollback_buffer_lines = 0;
|
||||
self.sixel_scrolling = false;
|
||||
if let Some(images_to_reap) = self.sixel_grid.clear() {
|
||||
self.sixel_grid.reap_images(images_to_reap);
|
||||
}
|
||||
}
|
||||
fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) {
|
||||
self.preceding_char = Some(terminal_character);
|
||||
|
|
@ -1486,6 +1585,7 @@ impl Grid {
|
|||
let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
|
||||
&mut self.viewport,
|
||||
&mut self.lines_above,
|
||||
&mut self.sixel_grid,
|
||||
count,
|
||||
self.width,
|
||||
);
|
||||
|
|
@ -1493,6 +1593,57 @@ impl Grid {
|
|||
self.scrollback_buffer_lines =
|
||||
subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count);
|
||||
}
|
||||
fn move_cursor_down_by_pixels(&mut self, pixel_count: usize) {
|
||||
if let Some(character_cell_size) = {
|
||||
let c = *self.character_cell_size.borrow();
|
||||
c
|
||||
} {
|
||||
// thanks borrow checker
|
||||
let pixel_height = character_cell_size.height;
|
||||
let to_move = (pixel_count as f64 / pixel_height as f64).ceil() as usize;
|
||||
for _ in 0..to_move {
|
||||
self.add_canonical_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn current_cursor_pixel_coordinates(&self) -> Option<(usize, usize)> {
|
||||
// (x, y)
|
||||
if let Some(character_cell_size) = *self.character_cell_size.borrow() {
|
||||
let line_count_in_scrollback = self.lines_above.len();
|
||||
let y_coordinates =
|
||||
(line_count_in_scrollback + self.cursor.y) * character_cell_size.height;
|
||||
let x_coordinates = self.cursor.x * character_cell_size.width;
|
||||
Some((x_coordinates, y_coordinates))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn create_sixel_image(&mut self) {
|
||||
if let Some((x_pixel_coordinates, y_pixel_coordinates)) =
|
||||
self.current_cursor_pixel_coordinates()
|
||||
{
|
||||
let (x_pixel_coordinates, y_pixel_coordinates) = if self.sixel_scrolling {
|
||||
let scrollback_pixel_height =
|
||||
self.lines_above.len() * self.character_cell_size.borrow().unwrap().height;
|
||||
(0, scrollback_pixel_height)
|
||||
} else {
|
||||
(x_pixel_coordinates, y_pixel_coordinates)
|
||||
};
|
||||
let new_image_id = self.sixel_grid.next_image_id();
|
||||
let new_sixel_image =
|
||||
self.sixel_grid
|
||||
.end_image(new_image_id, x_pixel_coordinates, y_pixel_coordinates);
|
||||
if let Some(new_sixel_image) = new_sixel_image {
|
||||
let (image_pixel_height, _image_pixel_width) = new_sixel_image.pixel_size();
|
||||
self.sixel_grid
|
||||
.new_sixel_image(new_image_id, new_sixel_image);
|
||||
if !self.sixel_scrolling {
|
||||
self.move_cursor_down_by_pixels(image_pixel_height);
|
||||
}
|
||||
self.render_full_viewport(); // TODO: this could be optimized if it's a performance bottleneck
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform for Grid {
|
||||
|
|
@ -1543,16 +1694,40 @@ impl Perform for Grid {
|
|||
}
|
||||
}
|
||||
|
||||
fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) {
|
||||
// TBD
|
||||
fn hook(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
|
||||
if c == 'q' {
|
||||
// we only process sixel images if we know the pixel size of each character cell,
|
||||
// otherwise we can't reliably display them
|
||||
if self.current_cursor_pixel_coordinates().is_some() {
|
||||
let max_sixel_height_in_pixels = if self.sixel_scrolling {
|
||||
let character_cell_height = self.character_cell_size.borrow().unwrap().height; // unwrap here is safe because `current_cursor_pixel_coordinates` above is only Some if it exists
|
||||
Some(self.height * character_cell_height)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.sixel_grid.start_image(
|
||||
max_sixel_height_in_pixels,
|
||||
intermediates.iter().collect(),
|
||||
params.iter().collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn put(&mut self, _byte: u8) {
|
||||
// TBD
|
||||
fn put(&mut self, byte: u8) {
|
||||
if self.sixel_grid.is_parsing() {
|
||||
self.sixel_grid.handle_byte(byte);
|
||||
// we explicitly set this to false here because in the context of Sixel, we only render the
|
||||
// image when it's done, i.e. in the unhook method
|
||||
self.should_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn unhook(&mut self) {
|
||||
// TBD
|
||||
if self.sixel_grid.is_parsing() {
|
||||
self.create_sixel_image();
|
||||
}
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
|
||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
|
|
@ -1588,6 +1763,18 @@ impl Perform for Grid {
|
|||
}
|
||||
self.changed_colors.as_mut().unwrap()[i as usize] = Some(c);
|
||||
return;
|
||||
} else if chunk.get(1).as_ref().and_then(|c| c.get(0)) == Some(&b'?') {
|
||||
if let Some(index) = index {
|
||||
let terminal_emulator_color_codes =
|
||||
self.terminal_emulator_color_codes.borrow();
|
||||
let color = terminal_emulator_color_codes.get(&(index as usize));
|
||||
if let Some(color) = color {
|
||||
let color_response_message =
|
||||
format!("\u{1b}]4;{};{}{}", index, color, terminator);
|
||||
self.pending_messages_to_pty
|
||||
.push(color_response_message.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1808,18 +1995,22 @@ impl Perform for Grid {
|
|||
self.bracketed_paste_mode = false;
|
||||
},
|
||||
Some(1049) => {
|
||||
// leave alternate buffer
|
||||
if let Some((
|
||||
alternative_lines_above,
|
||||
alternative_viewport,
|
||||
alternative_cursor,
|
||||
)) = &mut self.alternate_lines_above_viewport_and_cursor
|
||||
if let Some(mut alternate_screen_state) = self.alternate_screen_state.take()
|
||||
{
|
||||
std::mem::swap(&mut self.lines_above, alternative_lines_above);
|
||||
std::mem::swap(&mut self.viewport, alternative_viewport);
|
||||
std::mem::swap(&mut self.cursor, alternative_cursor);
|
||||
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
||||
// reap images before dropping the alternate_screen_state contents
|
||||
// - we can't implement a drop method for this because the store is
|
||||
// outside of the alternate_screen_state struct
|
||||
self.sixel_grid.reap_images(image_ids_to_reap);
|
||||
}
|
||||
self.alternate_lines_above_viewport_and_cursor = None;
|
||||
alternate_screen_state.apply_contents_to(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.cursor,
|
||||
&mut self.sixel_grid,
|
||||
);
|
||||
}
|
||||
self.alternate_screen_state = None;
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.mark_for_rerender();
|
||||
|
|
@ -1844,6 +2035,9 @@ impl Perform for Grid {
|
|||
Some(7) => {
|
||||
self.disable_linewrap = true;
|
||||
},
|
||||
Some(80) => {
|
||||
self.sixel_scrolling = false;
|
||||
},
|
||||
Some(1006) => {
|
||||
self.mouse_mode = false;
|
||||
},
|
||||
|
|
@ -1878,8 +2072,17 @@ impl Perform for Grid {
|
|||
vec![Row::new(self.width).canonical()],
|
||||
);
|
||||
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
||||
self.alternate_lines_above_viewport_and_cursor =
|
||||
Some((current_lines_above, current_viewport, current_cursor));
|
||||
let sixel_image_store = self.sixel_grid.sixel_image_store.clone();
|
||||
let alternate_sixelgrid = std::mem::replace(
|
||||
&mut self.sixel_grid,
|
||||
SixelGrid::new(self.character_cell_size.clone(), sixel_image_store),
|
||||
);
|
||||
self.alternate_screen_state = Some(AlternateScreenState::new(
|
||||
current_lines_above,
|
||||
current_viewport,
|
||||
current_cursor,
|
||||
alternate_sixelgrid,
|
||||
));
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count();
|
||||
self.output_buffer.update_all_lines(); // make sure the screen gets cleared in the next render
|
||||
|
|
@ -1900,6 +2103,9 @@ impl Perform for Grid {
|
|||
Some(7) => {
|
||||
self.disable_linewrap = false;
|
||||
},
|
||||
Some(80) => {
|
||||
self.sixel_scrolling = true;
|
||||
},
|
||||
Some(1006) => {
|
||||
self.mouse_mode = true;
|
||||
},
|
||||
|
|
@ -1969,9 +2175,45 @@ impl Perform for Grid {
|
|||
let line_count = next_param_or(1);
|
||||
self.rotate_scroll_region_up(line_count as usize);
|
||||
} else if c == 'S' {
|
||||
let first_intermediate_is_questionmark = match intermediates.get(0) {
|
||||
Some(b'?') => true,
|
||||
None => false,
|
||||
_ => false,
|
||||
};
|
||||
if first_intermediate_is_questionmark {
|
||||
let query_type = params_iter.next();
|
||||
let is_query = params_iter.next() == Some(&[1]);
|
||||
if is_query {
|
||||
// XTSMGRAPHICS
|
||||
match query_type {
|
||||
Some(&[1]) => {
|
||||
// number of color registers
|
||||
let response = "\u{1b}[?1;0;65536S";
|
||||
self.pending_messages_to_pty
|
||||
.push(response.as_bytes().to_vec());
|
||||
},
|
||||
Some(&[2]) => {
|
||||
// Sixel graphics geometry in pixels
|
||||
if let Some(character_cell_size) = *self.character_cell_size.borrow() {
|
||||
let sixel_area_geometry = format!(
|
||||
"\u{1b}[?2;0;{};{}S",
|
||||
character_cell_size.width * self.width,
|
||||
character_cell_size.height * self.height,
|
||||
);
|
||||
self.pending_messages_to_pty
|
||||
.push(sixel_area_geometry.as_bytes().to_vec());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// unsupported (eg. ReGIS graphics geometry)
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// move scroll up
|
||||
let count = next_param_or(1);
|
||||
self.rotate_scroll_region_down(count);
|
||||
}
|
||||
} else if c == 's' {
|
||||
self.save_cursor_position();
|
||||
} else if c == 'u' {
|
||||
|
|
@ -2022,6 +2264,11 @@ impl Perform for Grid {
|
|||
if let Some(cursor_shape) = shape {
|
||||
self.cursor.change_shape(cursor_shape);
|
||||
}
|
||||
} else if matches!(intermediates.get(0), Some(b'>')) {
|
||||
let version = version_number(VERSION);
|
||||
let xtversion = format!("\u{1b}P>|Zellij({})\u{1b}\\", version);
|
||||
self.pending_messages_to_pty
|
||||
.push(xtversion.as_bytes().to_vec());
|
||||
}
|
||||
} else if c == 'Z' {
|
||||
for _ in 0..next_param_or(1) {
|
||||
|
|
@ -2033,7 +2280,7 @@ impl Perform for Grid {
|
|||
match intermediates.get(0) {
|
||||
None | Some(0) => {
|
||||
// primary device attributes
|
||||
let terminal_capabilities = "\u{1b}[?6c";
|
||||
let terminal_capabilities = "\u{1b}[?64;4c";
|
||||
self.pending_messages_to_pty
|
||||
.push(terminal_capabilities.as_bytes().to_vec());
|
||||
},
|
||||
|
|
@ -2175,6 +2422,41 @@ impl Perform for Grid {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AlternateScreenState {
|
||||
lines_above: VecDeque<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)]
|
||||
pub struct Row {
|
||||
pub columns: VecDeque<TerminalCharacter>,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ pub mod grid;
|
|||
pub mod link_handler;
|
||||
mod plugin_pane;
|
||||
pub mod selection;
|
||||
pub mod sixel;
|
||||
pub mod terminal_character;
|
||||
mod terminal_pane;
|
||||
mod tiled_panes;
|
||||
|
|
@ -13,6 +14,7 @@ pub use floating_panes::*;
|
|||
pub use grid::*;
|
||||
pub use link_handler::*;
|
||||
pub(crate) use plugin_pane::*;
|
||||
pub use sixel::*;
|
||||
pub(crate) use terminal_character::*;
|
||||
pub use terminal_pane::*;
|
||||
pub use tiled_panes::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::fmt::Write;
|
|||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::output::CharacterChunk;
|
||||
use crate::output::{CharacterChunk, SixelImageChunk};
|
||||
use crate::panes::PaneId;
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
|
|
@ -141,7 +141,7 @@ impl Pane for PluginPane {
|
|||
fn render(
|
||||
&mut self,
|
||||
client_id: Option<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
|
||||
client_id?;
|
||||
let client_id = client_id.unwrap();
|
||||
|
|
@ -214,7 +214,7 @@ impl Pane for PluginPane {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some((vec![], Some(vte_output))) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer
|
||||
Some((vec![], Some(vte_output), vec![])) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
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::{
|
||||
grid::Grid,
|
||||
terminal_character::{CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
|
|
@ -99,10 +100,10 @@ impl Pane for TerminalPane {
|
|||
self.reflow_lines();
|
||||
}
|
||||
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
|
||||
self.set_should_render(true);
|
||||
for &byte in &bytes {
|
||||
self.vte_parser.advance(&mut self.grid, byte);
|
||||
}
|
||||
self.set_should_render(true);
|
||||
}
|
||||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||
// (x, y)
|
||||
|
|
@ -205,13 +206,14 @@ impl Pane for TerminalPane {
|
|||
fn render(
|
||||
&mut self,
|
||||
_client_id: Option<ClientId>,
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)> {
|
||||
if self.should_render() {
|
||||
let mut raw_vte_output = String::new();
|
||||
let content_x = self.get_content_x();
|
||||
let content_y = self.get_content_y();
|
||||
|
||||
let mut character_chunks = self.grid.read_changes(content_x, content_y);
|
||||
let (mut character_chunks, sixel_image_chunks) =
|
||||
self.grid.read_changes(content_x, content_y);
|
||||
for character_chunk in character_chunks.iter_mut() {
|
||||
character_chunk.add_changed_colors(self.grid.changed_colors);
|
||||
if self
|
||||
|
|
@ -237,7 +239,7 @@ impl Pane for TerminalPane {
|
|||
self.grid.ring_bell = false;
|
||||
}
|
||||
self.set_should_render(false);
|
||||
Some((character_chunks, Some(raw_vte_output)))
|
||||
Some((character_chunks, Some(raw_vte_output), sixel_image_chunks))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -509,15 +511,19 @@ impl TerminalPane {
|
|||
pane_name: String,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title = format!("Pane #{}", pane_index);
|
||||
let grid = Grid::new(
|
||||
position_and_size.rows.as_usize(),
|
||||
position_and_size.cols.as_usize(),
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
link_handler,
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
);
|
||||
TerminalPane {
|
||||
frame: HashMap::new(),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1810
|
||||
assertion_line: 2189
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
---
|
||||
00 (C): f oo
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,34 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1552
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/mosaic on main ⨯ bash 16:00:06
|
||||
02 (C): [aram@green mosaic]$ 12345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345
|
||||
03 (W):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 875
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/zellij on wide-char ⨯ bash 15:35:20
|
||||
02 (C): [aram@green zellij]$ HHHHHH
|
||||
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
|
||||
assertion_line: 1486
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22
|
||||
02 (C): ⋊> ~/c/mosaic on main ⨯ 15:07:29
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 655
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): ffffff
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 677
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): foo
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 699
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 12345678foo234567890
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 831
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): [aram@green zellij]$ 🏠🏠🏠
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 809
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): [aram@green zellij]$ 🏠 def abc
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 941
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 12 4
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 897
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Hi
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 919
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): i
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1508
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C):
|
||||
01 (C): OS: 5.9.13-arch1-1 GNU/Linux
|
||||
|
|
@ -18,4 +18,17 @@ expression: "format!(\"{:?}\", grid)"
|
|||
12 (C): wlp2s0 192.168.0.3
|
||||
13 (C):
|
||||
14 (C): [I] [20:07] kingdom:mosaic (main) |
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,66 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1644
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): ➜ mosaic git:(mosaic#130) emacs
|
||||
01 (C): ➜ mosaic git:(mosaic#130) emacs -nw
|
||||
02 (C): ➜ mosaic git:(mosaic#130) exit
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
51 (C):
|
||||
52 (C):
|
||||
53 (C):
|
||||
54 (C):
|
||||
55 (C):
|
||||
56 (C):
|
||||
57 (C):
|
||||
58 (C):
|
||||
59 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1576
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C):
|
||||
01 (C): OS: 5.9.14-arch1-1 GNU/Linux
|
||||
|
|
@ -24,4 +24,11 @@ expression: "format!(\"{:?}\", grid)"
|
|||
18 (W): )$/\\\\e[0;33m\1\\\\e[0m/' | \
|
||||
19 (C): paste -sd ''\
|
||||
20 (C): )
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,34 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1275
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo badblocks 11:32:23
|
||||
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
||||
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
||||
04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,34 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 1247
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/mosaic on main ⨯ sudo bandwhich 11:18:26
|
||||
02 (C): badblocks (Executable, 33kB) base64 (Executable, 42kB) bash (Executable, 906kB)
|
||||
03 (C): bandwhich (Executable, 3.0MB) basename (Executable, 38kB) bashbug (Executable, 6.8kB)
|
||||
04 (C): base32 (Executable, 42kB) basenc (Executable, 50kB) bass
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 853
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
|
||||
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
|
||||
assertion_line: 787
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): [aram@green zellij]$ 🏠 xdef abc
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 2018
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 👨y x🔭
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 2044
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 👨👨 🔭
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 963
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 12 4
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
assertion_line: 743
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/zellij on main ⨯ HHHHHHH 15:19:10
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
---
|
||||
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||
assertion_line: 765
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): Welcome to fish, the friendly interactive shell
|
||||
01 (C): ⋊> ~/c/zellij on main ⨯ bash 15:50:11
|
||||
02 (C): [aram@green zellij]$ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
|
||||
03 (W): HHHHH
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -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 crate::panes::sixel::SixelImageStore;
|
||||
use crate::panes::LinkHandler;
|
||||
use crate::tab::Pane;
|
||||
use ::insta::assert_snapshot;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use zellij_utils::{
|
||||
data::{Palette, Style},
|
||||
pane_size::PaneGeom,
|
||||
pane_size::{PaneGeom, SizeInPixels},
|
||||
};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
fn read_fixture(fixture_name: &str) -> Vec<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]
|
||||
pub fn scrolling_inside_a_pane() {
|
||||
let fake_client_id = 1;
|
||||
|
|
@ -20,6 +32,9 @@ pub fn scrolling_inside_a_pane() {
|
|||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -28,7 +43,9 @@ pub fn scrolling_inside_a_pane() {
|
|||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
Rc::new(RefCell::new(Palette::default())),
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
|
@ -42,3 +59,335 @@ pub fn scrolling_inside_a_pane() {
|
|||
terminal_pane.clear_scroll();
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn sixel_image_inside_terminal_pane() {
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let sixel_image_bytes = "\u{1b}Pq
|
||||
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
|
||||
#1~~@@vv@@~~@@~~$
|
||||
#2??}}GG}}??}}??-
|
||||
#1!14@
|
||||
\u{1b}\\";
|
||||
|
||||
terminal_pane.handle_pty_bytes(Vec::from(sixel_image_bytes.as_bytes()));
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn partial_sixel_image_inside_terminal_pane() {
|
||||
// here we test to make sure we partially render an image that is partially hidden in the
|
||||
// scrollbuffer
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn overflowing_sixel_image_inside_terminal_pane() {
|
||||
// here we test to make sure we properly render an image that overflows both in the width and
|
||||
// height of the pane
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(50);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn scrolling_through_a_sixel_image() {
|
||||
let fake_client_id = 1;
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
}
|
||||
writeln!(&mut text_to_fill_pane, "\r").unwrap();
|
||||
let pane_sixel_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes());
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content);
|
||||
terminal_pane.scroll_up(10, fake_client_id);
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
terminal_pane.scroll_down(3, fake_client_id);
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
terminal_pane.clear_scroll();
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn multiple_sixel_images_in_pane() {
|
||||
let fake_client_id = 1;
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
}
|
||||
writeln!(&mut text_to_fill_pane, "\r").unwrap();
|
||||
let pane_sixel_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content.clone()); // one image above text
|
||||
terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes());
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content); // one image below text
|
||||
terminal_pane.scroll_up(20, fake_client_id); // scroll up to see both images
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn resizing_pane_with_sixel_images() {
|
||||
// here we test, for example, that sixel images don't wrap with other lines
|
||||
let fake_client_id = 1;
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
}
|
||||
writeln!(&mut text_to_fill_pane, "\r").unwrap();
|
||||
let pane_sixel_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content.clone());
|
||||
terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes());
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content);
|
||||
let mut new_win_size = PaneGeom::default();
|
||||
new_win_size.cols.set_inner(100);
|
||||
new_win_size.rows.set_inner(20);
|
||||
terminal_pane.set_geom(new_win_size);
|
||||
terminal_pane.scroll_up(20, fake_client_id); // scroll up to see both images
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn changing_character_cell_size_with_sixel_images() {
|
||||
let fake_client_id = 1;
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size.clone(),
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
}
|
||||
writeln!(&mut text_to_fill_pane, "\r").unwrap();
|
||||
let pane_sixel_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content.clone());
|
||||
terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes());
|
||||
terminal_pane.handle_pty_bytes(pane_sixel_content);
|
||||
// here the new_win_size is the same as the old one, we just update the character_cell_size
|
||||
// which will be picked up upon resize (which is why we're doing set_geom below)
|
||||
let mut new_win_size = PaneGeom::default();
|
||||
new_win_size.cols.set_inner(121);
|
||||
new_win_size.rows.set_inner(20);
|
||||
*character_cell_size.borrow_mut() = Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 18,
|
||||
});
|
||||
terminal_pane.set_geom(new_win_size);
|
||||
terminal_pane.scroll_up(10, fake_client_id); // scroll up to see both images
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn keep_working_after_corrupted_sixel_image() {
|
||||
let mut fake_win_size = PaneGeom::default();
|
||||
fake_win_size.cols.set_inner(121);
|
||||
fake_win_size.rows.set_inner(20);
|
||||
|
||||
let pid = 1;
|
||||
let style = Style::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
style,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
); // 0 is the pane index
|
||||
|
||||
let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq
|
||||
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
|
||||
#1~~@@vv@@~~@@~~$
|
||||
#2??}}GG}}??}}??-
|
||||
#1!14@
|
||||
\u{1b}\\";
|
||||
|
||||
terminal_pane.handle_pty_bytes(Vec::from(sixel_image_bytes.as_bytes()));
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
}
|
||||
terminal_pane.handle_pty_bytes(text_to_fill_pane.into_bytes());
|
||||
assert_snapshot!(format!("{:?}", terminal_pane.grid));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ fn route_action(
|
|||
client_id: ClientId,
|
||||
) -> bool {
|
||||
let mut should_break = false;
|
||||
|
||||
// forward the action to plugins unless it is a mousehold
|
||||
// this is a bit of a hack around the unfortunate architecture we use with plugins
|
||||
// this will change as soon as we refactor
|
||||
match action {
|
||||
Action::MouseHold(_) => {},
|
||||
_ => {
|
||||
session
|
||||
.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
|
|
@ -36,6 +43,9 @@ fn route_action(
|
|||
Event::InputReceived,
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
}
|
||||
|
||||
match action {
|
||||
Action::ToggleTab => {
|
||||
session
|
||||
|
|
@ -493,6 +503,16 @@ pub(crate) fn route_thread_main(
|
|||
))
|
||||
.unwrap();
|
||||
},
|
||||
ClientToServerMsg::ColorRegisters(color_registers) => {
|
||||
rlocked_sessions
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::TerminalColorRegisters(
|
||||
color_registers,
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
ClientToServerMsg::NewClient(
|
||||
client_attributes,
|
||||
cli_args,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Things related to [`Screen`]s.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
|
@ -15,6 +15,7 @@ use crate::panes::terminal_character::AnsiCode;
|
|||
|
||||
use crate::{
|
||||
output::Output,
|
||||
panes::sixel::SixelImageStore,
|
||||
panes::PaneId,
|
||||
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
||||
tab::Tab,
|
||||
|
|
@ -95,6 +96,7 @@ pub enum ScreenInstruction {
|
|||
TerminalPixelDimensions(PixelDimensions),
|
||||
TerminalBackgroundColor(String),
|
||||
TerminalForegroundColor(String),
|
||||
TerminalColorRegisters(Vec<(usize, String)>),
|
||||
ChangeMode(ModeInfo, ClientId),
|
||||
LeftClick(Position, ClientId),
|
||||
RightClick(Position, ClientId),
|
||||
|
|
@ -184,6 +186,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::TerminalForegroundColor(..) => {
|
||||
ScreenContext::TerminalForegroundColor
|
||||
},
|
||||
ScreenInstruction::TerminalColorRegisters(..) => ScreenContext::TerminalColorRegisters,
|
||||
ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode,
|
||||
ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab,
|
||||
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
||||
|
|
@ -247,9 +250,11 @@ pub(crate) struct Screen {
|
|||
size: Size,
|
||||
pixel_dimensions: PixelDimensions,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
/// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`]
|
||||
overlay: OverlayWindow,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||
/// The indices of this [`Screen`]'s active [`Tab`]s.
|
||||
active_tab_indices: BTreeMap<ClientId, usize>,
|
||||
|
|
@ -279,12 +284,14 @@ impl Screen {
|
|||
size: client_attributes.size,
|
||||
pixel_dimensions: Default::default(),
|
||||
character_cell_size: Rc::new(RefCell::new(None)),
|
||||
sixel_image_store: Rc::new(RefCell::new(SixelImageStore::default())),
|
||||
style: client_attributes.style,
|
||||
connected_clients: Rc::new(RefCell::new(HashSet::new())),
|
||||
active_tab_indices: BTreeMap::new(),
|
||||
tabs: BTreeMap::new(),
|
||||
overlay: OverlayWindow::default(),
|
||||
terminal_emulator_colors: Rc::new(RefCell::new(Palette::default())),
|
||||
terminal_emulator_color_codes: Rc::new(RefCell::new(HashMap::new())),
|
||||
tab_history: BTreeMap::new(),
|
||||
mode_info: BTreeMap::new(),
|
||||
default_mode_info: mode_info,
|
||||
|
|
@ -518,10 +525,19 @@ impl Screen {
|
|||
self.terminal_emulator_colors.borrow_mut().fg = fg_palette_color;
|
||||
}
|
||||
}
|
||||
pub fn update_terminal_color_registers(&mut self, color_registers: Vec<(usize, String)>) {
|
||||
let mut terminal_emulator_color_codes = self.terminal_emulator_color_codes.borrow_mut();
|
||||
for (color_register, color_sequence) in color_registers {
|
||||
terminal_emulator_color_codes.insert(color_register, color_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||
pub fn render(&mut self) {
|
||||
let mut output = Output::default();
|
||||
let mut output = Output::new(
|
||||
self.sixel_image_store.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
let mut tabs_to_close = vec![];
|
||||
let size = self.size;
|
||||
let overlay = self.overlay.clone();
|
||||
|
|
@ -599,6 +615,7 @@ impl Screen {
|
|||
String::new(),
|
||||
self.size,
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.max_panes,
|
||||
|
|
@ -610,6 +627,7 @@ impl Screen {
|
|||
client_id,
|
||||
self.copy_options.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
||||
if self.session_is_mirrored {
|
||||
|
|
@ -793,7 +811,6 @@ impl Screen {
|
|||
pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) {
|
||||
if let Some(active_tab) = self.get_active_tab_mut(client_id) {
|
||||
if !active_tab.move_focus_left(client_id) {
|
||||
println!("can has true");
|
||||
self.switch_tab_prev(client_id);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1236,6 +1253,9 @@ pub(crate) fn screen_thread_main(
|
|||
ScreenInstruction::TerminalForegroundColor(background_color_instruction) => {
|
||||
screen.update_terminal_foreground_color(background_color_instruction);
|
||||
},
|
||||
ScreenInstruction::TerminalColorRegisters(color_registers) => {
|
||||
screen.update_terminal_color_registers(color_registers);
|
||||
},
|
||||
ScreenInstruction::ChangeMode(mode_info, client_id) => {
|
||||
screen.change_mode(mode_info, client_id);
|
||||
screen.render();
|
||||
|
|
@ -1264,8 +1284,9 @@ pub(crate) fn screen_thread_main(
|
|||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseHold(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||
.handle_mouse_hold(&point, client_id));
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| {
|
||||
tab.handle_mouse_hold(&point, client_id);
|
||||
});
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::Copy(client_id) => {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ use crate::ui::pane_boundaries_frame::FrameParams;
|
|||
use self::clipboard::ClipboardProvider;
|
||||
use crate::{
|
||||
os_input_output::ServerOsApi,
|
||||
output::{CharacterChunk, Output},
|
||||
output::{CharacterChunk, Output, SixelImageChunk},
|
||||
panes::sixel::SixelImageStore,
|
||||
panes::{FloatingPanes, TiledPanes},
|
||||
panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
|
||||
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
||||
|
|
@ -77,6 +78,7 @@ pub(crate) struct Tab {
|
|||
viewport: Rc<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)
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
pub senders: ThreadSenders,
|
||||
synchronize_is_active: bool,
|
||||
|
|
@ -96,6 +98,7 @@ pub(crate) struct Tab {
|
|||
copy_on_select: bool,
|
||||
last_mouse_hold_position: Option<Position>,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
|
|
@ -135,7 +138,7 @@ pub trait Pane {
|
|||
fn render(
|
||||
&mut self,
|
||||
client_id: Option<ClientId>,
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)>; // TODO: better
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>; // TODO: better
|
||||
fn render_frame(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
|
|
@ -287,6 +290,7 @@ impl Tab {
|
|||
name: String,
|
||||
display_area: Size,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
senders: ThreadSenders,
|
||||
max_panes: Option<usize>,
|
||||
|
|
@ -298,6 +302,7 @@ impl Tab {
|
|||
client_id: ClientId,
|
||||
copy_options: CopyOptions,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
) -> Self {
|
||||
let name = if name.is_empty() {
|
||||
format!("Tab #{}", index + 1)
|
||||
|
|
@ -354,6 +359,7 @@ impl Tab {
|
|||
viewport,
|
||||
display_area,
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
synchronize_is_active: false,
|
||||
os_api,
|
||||
senders,
|
||||
|
|
@ -371,6 +377,7 @@ impl Tab {
|
|||
copy_on_select: copy_options.copy_on_select,
|
||||
last_mouse_hold_position: None,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -438,7 +445,9 @@ impl Tab {
|
|||
layout.pane_name.clone().unwrap_or_default(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
new_pane.set_borderless(layout.borderless);
|
||||
self.tiled_panes
|
||||
|
|
@ -668,7 +677,9 @@ impl Tab {
|
|||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
|
||||
resize_pty!(new_pane, self.os_api);
|
||||
|
|
@ -691,7 +702,9 @@ impl Tab {
|
|||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
|
||||
self.should_clear_display_before_rendering = true;
|
||||
|
|
@ -717,7 +730,9 @@ impl Tab {
|
|||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
let replaced_pane = if self.floating_panes.panes_are_visible() {
|
||||
self.floating_panes
|
||||
|
|
@ -762,7 +777,9 @@ impl Tab {
|
|||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
||||
|
|
@ -790,7 +807,9 @@ impl Tab {
|
|||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_vertically(pid, Box::new(new_terminal), client_id);
|
||||
|
|
@ -1787,7 +1806,12 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_mouse_hold(&mut self, position_on_screen: &Position, client_id: ClientId) {
|
||||
pub fn handle_mouse_hold(
|
||||
&mut self,
|
||||
position_on_screen: &Position,
|
||||
client_id: ClientId,
|
||||
) -> bool {
|
||||
// return value indicates whether we should trigger a render
|
||||
// determine if event is repeated to enable smooth scrolling
|
||||
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
||||
position_on_screen == &last_position
|
||||
|
|
@ -1805,7 +1829,8 @@ impl Tab {
|
|||
.move_pane_with_mouse(*position_on_screen, search_selectable)
|
||||
{
|
||||
self.set_force_render();
|
||||
return;
|
||||
return !is_repeated; // we don't need to re-render in this case if the pane did not move
|
||||
// return;
|
||||
}
|
||||
|
||||
let selecting = self.selecting_with_mouse;
|
||||
|
|
@ -1825,10 +1850,13 @@ impl Tab {
|
|||
|
||||
let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line);
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
return true; // we need to re-render in this case so the selection disappears
|
||||
} else if selecting {
|
||||
active_pane.update_selection(&relative_position, client_id);
|
||||
return true; // we need to re-render in this case so the selection is updated
|
||||
}
|
||||
}
|
||||
false // we shouldn't even get here, but might as well not needlessly render if we do
|
||||
}
|
||||
|
||||
pub fn copy_selection(&self, client_id: ClientId) {
|
||||
|
|
|
|||
|
|
@ -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 crate::panes::sixel::SixelImageStore;
|
||||
use crate::screen::CopyOptions;
|
||||
use crate::Arc;
|
||||
use crate::Mutex;
|
||||
|
|
@ -13,12 +14,11 @@ use std::path::PathBuf;
|
|||
use zellij_utils::envs::set_session_name;
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::position::Position;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -119,12 +119,15 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
let character_cell_info = Rc::new(RefCell::new(None));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let copy_options = CopyOptions::default();
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_info,
|
||||
sixel_image_store,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
|
|
@ -136,6 +139,66 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
client_id,
|
||||
copy_options,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
vec![1],
|
||||
index,
|
||||
client_id,
|
||||
);
|
||||
tab
|
||||
}
|
||||
|
||||
fn create_new_tab_with_sixel_support(
|
||||
size: Size,
|
||||
sixel_image_store: Rc<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(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
|
|
@ -162,12 +225,48 @@ use ::insta::assert_snapshot;
|
|||
use zellij_utils::vte;
|
||||
|
||||
fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String {
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut grid = Grid::new(
|
||||
rows,
|
||||
columns,
|
||||
Rc::new(RefCell::new(palette)),
|
||||
terminal_emulator_color_codes,
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
format!("{:?}", grid)
|
||||
}
|
||||
|
||||
fn take_snapshot_with_sixel(
|
||||
ansi_instructions: &str,
|
||||
rows: usize,
|
||||
columns: usize,
|
||||
palette: Palette,
|
||||
sixel_image_store: Rc<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();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
|
|
@ -183,12 +282,16 @@ fn take_snapshot_and_cursor_position(
|
|||
palette: Palette,
|
||||
) -> (String, Option<(usize, usize)>) {
|
||||
// snapshot, x_coordinates, y_coordinates
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut grid = Grid::new(
|
||||
rows,
|
||||
columns,
|
||||
Rc::new(RefCell::new(palette)),
|
||||
terminal_emulator_color_codes,
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
sixel_image_store,
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
|
|
@ -1233,6 +1336,76 @@ fn save_cursor_position_across_resizes() {
|
|||
assert_snapshot!(snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_floating_pane_with_sixel_image() {
|
||||
let new_pane_id = PaneId::Terminal(2);
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone());
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
||||
|
||||
tab.toggle_floating_panes(client_id, None);
|
||||
tab.new_pane(new_pane_id, Some(client_id));
|
||||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(2, fixture);
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(7, 75), client_id);
|
||||
|
||||
tab.render(&mut output, None);
|
||||
let snapshot = take_snapshot_with_sixel(
|
||||
output.serialize().get(&client_id).unwrap(),
|
||||
size.rows,
|
||||
size.cols,
|
||||
Palette::default(),
|
||||
sixel_image_store,
|
||||
);
|
||||
|
||||
assert_snapshot!(snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_pane_above_sixel_image() {
|
||||
let new_pane_id = PaneId::Terminal(2);
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone());
|
||||
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
width: 8,
|
||||
height: 21,
|
||||
})));
|
||||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
||||
|
||||
tab.toggle_floating_panes(client_id, None);
|
||||
tab.new_pane(new_pane_id, Some(client_id));
|
||||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(1, fixture);
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(7, 75), client_id);
|
||||
|
||||
tab.render(&mut output, None);
|
||||
let snapshot = take_snapshot_with_sixel(
|
||||
output.serialize().get(&client_id).unwrap(),
|
||||
size.rows,
|
||||
size.cols,
|
||||
Palette::default(),
|
||||
sixel_image_store,
|
||||
);
|
||||
|
||||
assert_snapshot!(snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppress_tiled_pane() {
|
||||
let size = Size {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::Tab;
|
||||
use crate::panes::sixel::SixelImageStore;
|
||||
use crate::screen::CopyOptions;
|
||||
use crate::{
|
||||
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||
|
|
@ -13,7 +14,7 @@ use zellij_utils::ipc::IpcReceiverWithContext;
|
|||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -105,12 +106,15 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let copy_options = CopyOptions::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_info,
|
||||
sixel_image_store,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
|
|
@ -122,6 +126,7 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
client_id,
|
||||
copy_options,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
|
|
@ -152,12 +157,15 @@ fn create_new_tab_with_cell_size(
|
|||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let copy_options = CopyOptions::default();
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
|
|
@ -169,6 +177,7 @@ fn create_new_tab_with_cell_size(
|
|||
client_id,
|
||||
copy_options,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
|
|
|
|||
|
|
@ -44,13 +44,19 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
&mut self,
|
||||
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();
|
||||
self.output.add_character_chunks_to_multiple_clients(
|
||||
character_chunks,
|
||||
clients.iter().copied(),
|
||||
self.z_index,
|
||||
);
|
||||
self.output.add_sixel_image_chunks_to_multiple_clients(
|
||||
sixel_image_chunks,
|
||||
clients.iter().copied(),
|
||||
self.z_index,
|
||||
);
|
||||
if let Some(raw_vte_output) = raw_vte_output {
|
||||
self.output.add_post_vte_instruction_to_multiple_clients(
|
||||
clients.iter().copied(),
|
||||
|
|
@ -65,9 +71,16 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
}
|
||||
}
|
||||
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) {
|
||||
if let Some((character_chunks, raw_vte_output)) = self.pane.render(Some(client_id)) {
|
||||
if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) =
|
||||
self.pane.render(Some(client_id))
|
||||
{
|
||||
self.output
|
||||
.add_character_chunks_to_client(client_id, character_chunks, self.z_index);
|
||||
self.output.add_sixel_image_chunks_to_client(
|
||||
client_id,
|
||||
sixel_image_chunks,
|
||||
self.z_index,
|
||||
);
|
||||
if let Some(raw_vte_output) = raw_vte_output {
|
||||
self.output.add_post_vte_instruction_to_client(
|
||||
client_id,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ pub fn set_session_name(v: String) {
|
|||
set_var(SESSION_NAME_ENV_KEY, v);
|
||||
}
|
||||
|
||||
pub fn set_initial_environment_vars() {
|
||||
set_var("COLORTERM", "24bit");
|
||||
}
|
||||
|
||||
pub const SOCKET_DIR_ENV_KEY: &str = "ZELLIJ_SOCKET_DIR";
|
||||
pub fn get_socket_dir() -> Result<String> {
|
||||
Ok(var(SOCKET_DIR_ENV_KEY)?)
|
||||
|
|
|
|||
|
|
@ -277,6 +277,7 @@ pub enum ScreenContext {
|
|||
TerminalPixelDimensions,
|
||||
TerminalBackgroundColor,
|
||||
TerminalForegroundColor,
|
||||
TerminalColorRegisters,
|
||||
ChangeMode,
|
||||
LeftClick,
|
||||
RightClick,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ pub enum ClientToServerMsg {
|
|||
TerminalPixelDimensions(PixelDimensions),
|
||||
BackgroundColor(String),
|
||||
ForegroundColor(String),
|
||||
ColorRegisters(Vec<(usize, String)>),
|
||||
TerminalResize(Size),
|
||||
NewClient(
|
||||
ClientAttributes,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue