mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-25 02:06:19 +03:00
feat(signals): support XTWINOPS 14 and 16 (and query the terminal for them on startup and SIGWINCH) (#1316)
* feat(signals): get pixel info from terminal emulator * feat(signals): query for pixel info on sigwinch * feat(signals): reply to csi 14t and csi 16t * style(fmt): rustfmt * style(comments): remove outdated
This commit is contained in:
parent
028885c822
commit
19adb29be5
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -2804,9 +2804,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vtparse"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f41c9314c4dde1f43dd0c46c67bb5ae73850ce11eebaf7d8b912e178bda5401"
|
||||
checksum = "36ce903972602c84dd48f488cdce39edcba03a93b7ca67b146ae862568f48c5c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
@ -3298,6 +3298,7 @@ dependencies = [
|
||||
"miette",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
@ -161,6 +161,7 @@ fn read_from_channel(
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
); // 0 is the pane index
|
||||
loop {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
|
1
src/tests/fixtures/terminal_pixel_size_reports
vendored
Normal file
1
src/tests/fixtures/terminal_pixel_size_reports
vendored
Normal file
@ -0,0 +1 @@
|
||||
[14t;[16t
|
@ -1,5 +1,4 @@
|
||||
//! Main input logic.
|
||||
|
||||
use zellij_utils::{
|
||||
input::{
|
||||
mouse::{MouseButton, MouseEvent},
|
||||
@ -10,7 +9,9 @@ use zellij_utils::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting, InputInstruction,
|
||||
os_input_output::ClientOsApi,
|
||||
pixel_csi_parser::{PixelCsiParser, PixelDimensionsOrKeys},
|
||||
ClientInstruction, CommandIsExecuting, InputInstruction,
|
||||
};
|
||||
use zellij_utils::{
|
||||
channels::{Receiver, SenderWithContext, OPENCALLS},
|
||||
@ -70,6 +71,15 @@ 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
|
||||
let get_cell_pixel_info = "\u{1b}[14t\u{1b}[16t";
|
||||
let _ = self
|
||||
.os_input
|
||||
.get_stdout_writer()
|
||||
.write(get_cell_pixel_info.as_bytes())
|
||||
.unwrap();
|
||||
let mut pixel_csi_parser = PixelCsiParser::new();
|
||||
pixel_csi_parser.increment_expected_csi_instructions(2);
|
||||
loop {
|
||||
if self.should_exit {
|
||||
break;
|
||||
@ -79,7 +89,13 @@ impl InputHandler {
|
||||
match input_event {
|
||||
InputEvent::Key(key_event) => {
|
||||
let key = cast_termwiz_key(key_event, &raw_bytes);
|
||||
self.handle_key(&key, raw_bytes);
|
||||
if pixel_csi_parser.expected_instructions() > 0 {
|
||||
self.handle_possible_pixel_instruction(
|
||||
pixel_csi_parser.parse(key, raw_bytes),
|
||||
);
|
||||
} else {
|
||||
self.handle_key(&key, raw_bytes);
|
||||
}
|
||||
}
|
||||
InputEvent::Mouse(mouse_event) => {
|
||||
let mouse_event =
|
||||
@ -101,6 +117,14 @@ 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();
|
||||
pixel_csi_parser.increment_expected_csi_instructions(2);
|
||||
}
|
||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||
}
|
||||
}
|
||||
@ -114,6 +138,23 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_possible_pixel_instruction(
|
||||
&mut self,
|
||||
pixel_instruction_or_keys: Option<PixelDimensionsOrKeys>,
|
||||
) {
|
||||
match pixel_instruction_or_keys {
|
||||
Some(PixelDimensionsOrKeys::PixelDimensions(pixel_dimensions)) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
|
||||
}
|
||||
Some(PixelDimensionsOrKeys::Keys(keys)) => {
|
||||
for (key, raw_bytes) in keys {
|
||||
self.handle_key(&key, raw_bytes);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
|
||||
match *mouse_event {
|
||||
MouseEvent::Press(button, point) => match button {
|
||||
|
@ -2,6 +2,7 @@ pub mod os_input_output;
|
||||
|
||||
mod command_is_executing;
|
||||
mod input_handler;
|
||||
mod pixel_csi_parser;
|
||||
mod stdin_handler;
|
||||
|
||||
use log::info;
|
||||
@ -108,6 +109,7 @@ impl ClientInfo {
|
||||
pub(crate) enum InputInstruction {
|
||||
KeyEvent(InputEvent, Vec<u8>),
|
||||
SwitchToMode(InputMode),
|
||||
PossiblePixelRatioChange,
|
||||
}
|
||||
|
||||
pub fn start_client(
|
||||
@ -237,6 +239,7 @@ 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(
|
||||
@ -246,6 +249,8 @@ 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);
|
||||
}
|
||||
}),
|
||||
Box::new({
|
||||
|
146
zellij-client/src/pixel_csi_parser.rs
Normal file
146
zellij-client/src/pixel_csi_parser.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use zellij_utils::pane_size::SizeInPixels;
|
||||
|
||||
use zellij_utils::{ipc::PixelDimensions, lazy_static::lazy_static, regex::Regex};
|
||||
|
||||
use zellij_tile::data::Key;
|
||||
|
||||
pub struct PixelCsiParser {
|
||||
expected_pixel_csi_instructions: usize,
|
||||
current_buffer: Vec<(Key, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl PixelCsiParser {
|
||||
pub fn new() -> Self {
|
||||
PixelCsiParser {
|
||||
expected_pixel_csi_instructions: 0,
|
||||
current_buffer: vec![],
|
||||
}
|
||||
}
|
||||
pub fn increment_expected_csi_instructions(&mut self, by: usize) {
|
||||
self.expected_pixel_csi_instructions += by;
|
||||
}
|
||||
pub fn decrement_expected_csi_instructions(&mut self, by: usize) {
|
||||
self.expected_pixel_csi_instructions =
|
||||
self.expected_pixel_csi_instructions.saturating_sub(by);
|
||||
}
|
||||
pub fn expected_instructions(&self) -> usize {
|
||||
self.expected_pixel_csi_instructions
|
||||
}
|
||||
pub fn parse(&mut self, key: Key, raw_bytes: Vec<u8>) -> Option<PixelDimensionsOrKeys> {
|
||||
if let Key::Char('t') = key {
|
||||
self.current_buffer.push((key, raw_bytes));
|
||||
match PixelDimensionsOrKeys::pixel_dimensions_from_keys(&self.current_buffer) {
|
||||
Ok(pixel_instruction) => {
|
||||
self.decrement_expected_csi_instructions(1);
|
||||
self.current_buffer.clear();
|
||||
Some(pixel_instruction)
|
||||
}
|
||||
Err(_) => {
|
||||
self.expected_pixel_csi_instructions = 0;
|
||||
Some(PixelDimensionsOrKeys::Keys(
|
||||
self.current_buffer.drain(..).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else if self.key_is_valid(key) {
|
||||
self.current_buffer.push((key, raw_bytes));
|
||||
None
|
||||
} else {
|
||||
self.current_buffer.push((key, raw_bytes));
|
||||
self.expected_pixel_csi_instructions = 0;
|
||||
Some(PixelDimensionsOrKeys::Keys(
|
||||
self.current_buffer.drain(..).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
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 pixel instructions" mode
|
||||
if self
|
||||
.current_buffer
|
||||
.iter()
|
||||
.find(|(key, _)| *key == Key::Esc)
|
||||
.is_none()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Key::Char(';') | Key::Char('[') => true,
|
||||
Key::Char(c) => {
|
||||
if let '0'..='9' = c {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PixelDimensionsOrKeys {
|
||||
// TODO: rename to PixelDimensionsOrKeys
|
||||
PixelDimensions(PixelDimensions),
|
||||
Keys(Vec<(Key, Vec<u8>)>),
|
||||
}
|
||||
|
||||
impl PixelDimensionsOrKeys {
|
||||
pub fn pixel_dimensions_from_keys(keys: &Vec<(Key, Vec<u8>)>) -> Result<Self, &'static str> {
|
||||
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 captures = RE
|
||||
.captures_iter(&key_string)
|
||||
.next()
|
||||
.ok_or("invalid_instruction")?;
|
||||
let csi_index = captures[1].parse::<usize>();
|
||||
let first_field = captures[2].parse::<usize>();
|
||||
let second_field = captures[3].parse::<usize>();
|
||||
if csi_index.is_err() || first_field.is_err() || second_field.is_err() {
|
||||
return Err("invalid_instruction");
|
||||
}
|
||||
match csi_index {
|
||||
Ok(4) => {
|
||||
// text area size
|
||||
Ok(PixelDimensionsOrKeys::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(PixelDimensionsOrKeys::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")
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use super::input_loop;
|
||||
use zellij_utils::input::actions::{Action, Direction};
|
||||
use zellij_utils::input::config::Config;
|
||||
use zellij_utils::input::options::Options;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
|
||||
use zellij_utils::zellij_tile::data::Palette;
|
||||
|
||||
@ -22,7 +22,7 @@ use std::sync::{Arc, Mutex};
|
||||
use zellij_tile::data::InputMode;
|
||||
use zellij_utils::{
|
||||
errors::ErrorContext,
|
||||
ipc::{ClientToServerMsg, ServerToClientMsg},
|
||||
ipc::{ClientToServerMsg, PixelDimensions, ServerToClientMsg},
|
||||
};
|
||||
|
||||
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
|
||||
@ -71,9 +71,30 @@ pub mod commands {
|
||||
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, mut buf: &[u8]) -> Result<usize, io::Error> {
|
||||
self.buffer.lock().unwrap().extend_from_slice(&mut 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 {
|
||||
@ -85,11 +106,16 @@ impl FakeClientOsApi {
|
||||
// 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 {
|
||||
@ -103,7 +129,8 @@ impl ClientOsApi for FakeClientOsApi {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
||||
unimplemented!()
|
||||
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!()
|
||||
@ -155,6 +182,18 @@ fn extract_actions_sent_to_server(
|
||||
})
|
||||
}
|
||||
|
||||
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.clone());
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn quit_breaks_input_loop() {
|
||||
let stdin_events = vec![(
|
||||
@ -267,3 +306,432 @@ pub fn move_focus_left_in_normal_mode() {
|
||||
"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.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;
|
||||
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")),
|
||||
);
|
||||
}
|
||||
|
||||
#[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.clone());
|
||||
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.clone());
|
||||
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.clone());
|
||||
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![],);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use std::{
|
||||
|
||||
use zellij_utils::{
|
||||
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
|
||||
pane_size::SizeInPixels,
|
||||
position::Position,
|
||||
vte, zellij_tile,
|
||||
};
|
||||
@ -289,6 +290,7 @@ pub struct Grid {
|
||||
colors: Palette,
|
||||
output_buffer: OutputBuffer,
|
||||
title_stack: Vec<String>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
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")
|
||||
@ -328,6 +330,7 @@ impl Grid {
|
||||
columns: usize,
|
||||
colors: Palette,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
) -> Self {
|
||||
Grid {
|
||||
lines_above: VecDeque::with_capacity(
|
||||
@ -365,6 +368,7 @@ impl Grid {
|
||||
ring_bell: false,
|
||||
scrollback_buffer_lines: 0,
|
||||
mouse_mode: false,
|
||||
character_cell_size,
|
||||
}
|
||||
}
|
||||
pub fn render_full_viewport(&mut self) {
|
||||
@ -2009,9 +2013,25 @@ impl Perform for Grid {
|
||||
} else if c == 't' {
|
||||
match next_param_or(1) as usize {
|
||||
14 => {
|
||||
// TODO: report text area size in pixels, currently unimplemented
|
||||
// to solve this we probably need to query the user's terminal for the cursor
|
||||
// size and then use it as a multiplier
|
||||
if let Some(character_cell_size) = *self.character_cell_size.borrow() {
|
||||
let text_area_pixel_size_report = format!(
|
||||
"\x1b[4;{};{}t",
|
||||
character_cell_size.height * self.height,
|
||||
character_cell_size.width * self.width
|
||||
);
|
||||
self.pending_messages_to_pty
|
||||
.push(text_area_pixel_size_report.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
16 => {
|
||||
if let Some(character_cell_size) = *self.character_cell_size.borrow() {
|
||||
let character_cell_size_report = format!(
|
||||
"\x1b[6;{};{}t",
|
||||
character_cell_size.height, character_cell_size.width
|
||||
);
|
||||
self.pending_messages_to_pty
|
||||
.push(character_cell_size_report.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
18 => {
|
||||
// report text area
|
||||
|
@ -16,6 +16,7 @@ use std::time::{self, Instant};
|
||||
use zellij_tile::prelude::Style;
|
||||
use zellij_utils::pane_size::Offset;
|
||||
use zellij_utils::{
|
||||
pane_size::SizeInPixels,
|
||||
pane_size::{Dimension, PaneGeom},
|
||||
position::Position,
|
||||
shared::make_terminal_title,
|
||||
@ -487,6 +488,7 @@ impl TerminalPane {
|
||||
pane_index: usize,
|
||||
pane_name: String,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title = format!("Pane #{}", pane_index);
|
||||
let grid = Grid::new(
|
||||
@ -494,6 +496,7 @@ impl TerminalPane {
|
||||
position_and_size.cols.as_usize(),
|
||||
style.colors,
|
||||
link_handler,
|
||||
character_cell_size,
|
||||
);
|
||||
TerminalPane {
|
||||
frame: HashMap::new(),
|
||||
|
@ -18,7 +18,7 @@ use std::time::Instant;
|
||||
use zellij_tile::data::ModeInfo;
|
||||
use zellij_utils::{
|
||||
input::layout::Direction,
|
||||
pane_size::{Offset, PaneGeom, Size, Viewport},
|
||||
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
|
||||
};
|
||||
|
||||
macro_rules! resize_pty {
|
||||
@ -61,6 +61,7 @@ pub struct TiledPanes {
|
||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
|
||||
mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
default_mode_info: ModeInfo,
|
||||
style: Style,
|
||||
session_is_mirrored: bool,
|
||||
@ -79,6 +80,7 @@ impl TiledPanes {
|
||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
|
||||
mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
session_is_mirrored: bool,
|
||||
draw_pane_frames: bool,
|
||||
default_mode_info: ModeInfo,
|
||||
@ -92,6 +94,7 @@ impl TiledPanes {
|
||||
connected_clients,
|
||||
connected_clients_in_app,
|
||||
mode_info,
|
||||
character_cell_size,
|
||||
default_mode_info,
|
||||
style,
|
||||
session_is_mirrored,
|
||||
@ -106,12 +109,14 @@ impl TiledPanes {
|
||||
self.panes.insert(pane_id, pane);
|
||||
}
|
||||
pub fn insert_pane(&mut self, pane_id: PaneId, mut pane: Box<dyn Pane>) {
|
||||
let cursor_height_width_ratio = self.cursor_height_width_ratio();
|
||||
let pane_grid = TiledPaneGrid::new(
|
||||
&mut self.panes,
|
||||
*self.display_area.borrow(),
|
||||
*self.viewport.borrow(),
|
||||
);
|
||||
let pane_id_and_split_direction = pane_grid.find_room_for_new_pane();
|
||||
let pane_id_and_split_direction =
|
||||
pane_grid.find_room_for_new_pane(cursor_height_width_ratio);
|
||||
if let Some((pane_id_to_split, split_direction)) = pane_id_and_split_direction {
|
||||
// this unwrap is safe because floating panes should not be visible if there are no floating panes
|
||||
let pane_to_split = self.panes.get_mut(&pane_id_to_split).unwrap();
|
||||
@ -125,12 +130,15 @@ impl TiledPanes {
|
||||
}
|
||||
}
|
||||
pub fn has_room_for_new_pane(&mut self) -> bool {
|
||||
let cursor_height_width_ratio = self.cursor_height_width_ratio();
|
||||
let pane_grid = TiledPaneGrid::new(
|
||||
&mut self.panes,
|
||||
*self.display_area.borrow(),
|
||||
*self.viewport.borrow(),
|
||||
);
|
||||
pane_grid.find_room_for_new_pane().is_some()
|
||||
pane_grid
|
||||
.find_room_for_new_pane(cursor_height_width_ratio)
|
||||
.is_some()
|
||||
}
|
||||
pub fn fixed_pane_geoms(&self) -> Vec<Viewport> {
|
||||
self.panes
|
||||
@ -528,6 +536,12 @@ impl TiledPanes {
|
||||
pane.set_active_at(Instant::now());
|
||||
}
|
||||
}
|
||||
pub fn cursor_height_width_ratio(&self) -> Option<usize> {
|
||||
let character_cell_size = self.character_cell_size.borrow();
|
||||
character_cell_size.map(|size_in_pixels| {
|
||||
(size_in_pixels.height as f64 / size_in_pixels.width as f64).round() as usize
|
||||
})
|
||||
}
|
||||
pub fn move_focus_left(&mut self, client_id: ClientId) -> bool {
|
||||
match self.get_active_pane_id(client_id) {
|
||||
Some(active_pane_id) => {
|
||||
|
@ -13,7 +13,7 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
const RESIZE_PERCENT: f64 = 5.0;
|
||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||
const DEFAULT_CURSOR_HEIGHT_WIDTH_RATIO: usize = 4;
|
||||
|
||||
type BorderAndPaneIds = (usize, Vec<PaneId>);
|
||||
|
||||
@ -1606,7 +1606,10 @@ impl<'a> TiledPaneGrid<'a> {
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn find_room_for_new_pane(&self) -> Option<(PaneId, Direction)> {
|
||||
pub fn find_room_for_new_pane(
|
||||
&self,
|
||||
cursor_height_width_ratio: Option<usize>,
|
||||
) -> Option<(PaneId, Direction)> {
|
||||
let panes = self.panes.borrow();
|
||||
let pane_sequence: Vec<(&PaneId, &&mut Box<dyn Pane>)> =
|
||||
panes.iter().filter(|(_, p)| p.selectable()).collect();
|
||||
@ -1614,8 +1617,9 @@ impl<'a> TiledPaneGrid<'a> {
|
||||
(0, None),
|
||||
|(current_largest_pane_size, current_pane_id_to_split), id_and_pane_to_check| {
|
||||
let (id_of_pane_to_check, pane_to_check) = id_and_pane_to_check;
|
||||
let pane_size =
|
||||
(pane_to_check.rows() * CURSOR_HEIGHT_WIDTH_RATIO) * pane_to_check.cols();
|
||||
let pane_size = (pane_to_check.rows()
|
||||
* cursor_height_width_ratio.unwrap_or(DEFAULT_CURSOR_HEIGHT_WIDTH_RATIO))
|
||||
* pane_to_check.cols();
|
||||
let pane_can_be_split = pane_to_check.cols() >= MIN_TERMINAL_WIDTH
|
||||
&& pane_to_check.rows() >= MIN_TERMINAL_HEIGHT
|
||||
&& ((pane_to_check.cols() > pane_to_check.min_width() * 2)
|
||||
@ -1629,7 +1633,8 @@ impl<'a> TiledPaneGrid<'a> {
|
||||
);
|
||||
pane_id_to_split.and_then(|t_id_to_split| {
|
||||
let pane_to_split = panes.get(t_id_to_split).unwrap();
|
||||
let direction = if pane_to_split.rows() * CURSOR_HEIGHT_WIDTH_RATIO
|
||||
let direction = if pane_to_split.rows()
|
||||
* cursor_height_width_ratio.unwrap_or(DEFAULT_CURSOR_HEIGHT_WIDTH_RATIO)
|
||||
> pane_to_split.cols()
|
||||
&& pane_to_split.rows() > pane_to_split.min_height() * 2
|
||||
{
|
||||
|
@ -3,7 +3,7 @@ use crate::panes::link_handler::LinkHandler;
|
||||
use ::insta::assert_snapshot;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use zellij_utils::{position::Position, vte, zellij_tile::data::Palette};
|
||||
use zellij_utils::{pane_size::SizeInPixels, position::Position, vte, zellij_tile::data::Palette};
|
||||
|
||||
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||||
let mut path_to_file = std::path::PathBuf::new();
|
||||
@ -23,6 +23,7 @@ fn vttest1_0() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-0";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -40,6 +41,7 @@ fn vttest1_1() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-1";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -57,6 +59,7 @@ fn vttest1_2() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-2";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -74,6 +77,7 @@ fn vttest1_3() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-3";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -91,6 +95,7 @@ fn vttest1_4() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-4";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -108,6 +113,7 @@ fn vttest1_5() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest1-5";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -125,6 +131,7 @@ fn vttest2_0() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-0";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -142,6 +149,7 @@ fn vttest2_1() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-1";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -159,6 +167,7 @@ fn vttest2_2() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-2";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -176,6 +185,7 @@ fn vttest2_3() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-3";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -193,6 +203,7 @@ fn vttest2_4() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-4";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -210,6 +221,7 @@ fn vttest2_5() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-5";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -227,6 +239,7 @@ fn vttest2_6() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-6";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -244,6 +257,7 @@ fn vttest2_7() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-7";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -261,6 +275,7 @@ fn vttest2_8() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-8";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -278,6 +293,7 @@ fn vttest2_9() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-9";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -295,6 +311,7 @@ fn vttest2_10() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-10";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -312,6 +329,7 @@ fn vttest2_11() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-11";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -329,6 +347,7 @@ fn vttest2_12() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-12";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -346,6 +365,7 @@ fn vttest2_13() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-13";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -363,6 +383,7 @@ fn vttest2_14() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest2-14";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -380,6 +401,7 @@ fn vttest3_0() {
|
||||
110,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest3-0";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -397,6 +419,7 @@ fn vttest8_0() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-0";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -414,6 +437,7 @@ fn vttest8_1() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-1";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -431,6 +455,7 @@ fn vttest8_2() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-2";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -448,6 +473,7 @@ fn vttest8_3() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-3";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -465,6 +491,7 @@ fn vttest8_4() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-4";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -482,6 +509,7 @@ fn vttest8_5() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vttest8-5";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -499,6 +527,7 @@ fn csi_b() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "csi-b";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -516,6 +545,7 @@ fn csi_capital_i() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "csi-capital-i";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -533,6 +563,7 @@ fn csi_capital_z() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "csi-capital-z";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -550,6 +581,7 @@ fn terminal_reports() {
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "terminal_reports";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -567,6 +599,7 @@ fn wide_characters() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -584,6 +617,7 @@ fn wide_characters_line_wrap() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_line_wrap";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -601,6 +635,7 @@ fn insert_character_in_line_with_wide_character() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_middle_line_insert";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -618,6 +653,7 @@ fn delete_char_in_middle_of_line_with_widechar() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide-chars-delete-middle";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -635,6 +671,7 @@ fn delete_char_in_middle_of_line_with_multiple_widechars() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide-chars-delete-middle-after-multi";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -652,6 +689,7 @@ fn fish_wide_characters_override_clock() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fish_wide_characters_override_clock";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -669,6 +707,7 @@ fn bash_delete_wide_characters() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "bash_delete_wide_characters";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -686,6 +725,7 @@ fn delete_wide_characters_before_cursor() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "delete_wide_characters_before_cursor";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -703,6 +743,7 @@ fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "delete_wide_characters_before_cursor_when_cursor_is_on_wide_character";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -720,6 +761,7 @@ fn delete_wide_character_under_cursor() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "delete_wide_character_under_cursor";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -737,6 +779,7 @@ fn replace_wide_character_under_cursor() {
|
||||
104,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "replace_wide_character_under_cursor";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -754,6 +797,7 @@ fn wrap_wide_characters() {
|
||||
90,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_full";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -771,6 +815,7 @@ fn wrap_wide_characters_on_size_change() {
|
||||
93,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_full";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -789,6 +834,7 @@ fn unwrap_wide_characters_on_size_change() {
|
||||
93,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_full";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -808,6 +854,7 @@ fn wrap_wide_characters_in_the_middle_of_the_line() {
|
||||
91,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_line_middle";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -825,6 +872,7 @@ fn wrap_wide_characters_at_the_end_of_the_line() {
|
||||
90,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "wide_characters_line_end";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -842,6 +890,7 @@ fn copy_selected_text_from_viewport() {
|
||||
125,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "grid_copy";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -867,6 +916,7 @@ fn copy_wrapped_selected_text_from_viewport() {
|
||||
73,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "grid_copy_wrapped";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -891,6 +941,7 @@ fn copy_selected_text_from_lines_above() {
|
||||
125,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "grid_copy";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -916,6 +967,7 @@ fn copy_selected_text_from_lines_below() {
|
||||
125,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "grid_copy";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -949,6 +1001,7 @@ fn run_bandwhich_from_fish_shell() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fish_and_bandwhich";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -966,6 +1019,7 @@ fn fish_tab_completion_options() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fish_tab_completion_options";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -988,6 +1042,7 @@ pub fn fish_select_tab_completion_options() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fish_select_tab_completion_options";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1013,6 +1068,7 @@ pub fn vim_scroll_region_down() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vim_scroll_region_down";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1036,6 +1092,7 @@ pub fn vim_ctrl_d() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vim_ctrl_d";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1058,6 +1115,7 @@ pub fn vim_ctrl_u() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vim_ctrl_u";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1075,6 +1133,7 @@ pub fn htop() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "htop";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1092,6 +1151,7 @@ pub fn htop_scrolling() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "htop_scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1109,6 +1169,7 @@ pub fn htop_right_scrolling() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "htop_right_scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1134,6 +1195,7 @@ pub fn vim_overwrite() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "vim_overwrite";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1153,6 +1215,7 @@ pub fn clear_scroll_region() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "clear_scroll_region";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1170,6 +1233,7 @@ pub fn display_tab_characters_properly() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "tab_characters";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1187,6 +1251,7 @@ pub fn neovim_insert_mode() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "nvim_insert";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1204,6 +1269,7 @@ pub fn bash_cursor_linewrap() {
|
||||
116,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "bash_cursor_linewrap";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1223,6 +1289,7 @@ pub fn fish_paste_multiline() {
|
||||
149,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fish_paste_multiline";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1240,6 +1307,7 @@ pub fn git_log() {
|
||||
149,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "git_log";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1259,6 +1327,7 @@ pub fn git_diff_scrollup() {
|
||||
149,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "git_diff_scrollup";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1276,6 +1345,7 @@ pub fn emacs_longbuf() {
|
||||
284,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "emacs_longbuf_tutorial";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1293,6 +1363,7 @@ pub fn top_and_quit() {
|
||||
235,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "top_and_quit";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1316,6 +1387,7 @@ pub fn exa_plus_omf_theme() {
|
||||
235,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "exa_plus_omf_theme";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1333,6 +1405,7 @@ pub fn scroll_up() {
|
||||
50,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1351,6 +1424,7 @@ pub fn scroll_down() {
|
||||
50,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1370,6 +1444,7 @@ pub fn scroll_up_with_line_wraps() {
|
||||
25,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1388,6 +1463,7 @@ pub fn scroll_down_with_line_wraps() {
|
||||
25,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1407,6 +1483,7 @@ pub fn scroll_up_decrease_width_and_scroll_down() {
|
||||
50,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1431,6 +1508,7 @@ pub fn scroll_up_increase_width_and_scroll_down() {
|
||||
25,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scrolling";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1455,6 +1533,7 @@ pub fn move_cursor_below_scroll_region() {
|
||||
114,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "move_cursor_below_scroll_region";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1472,6 +1551,7 @@ pub fn insert_wide_characters_in_existing_line() {
|
||||
86,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "chinese_characters_line_middle";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1494,6 +1574,7 @@ pub fn full_screen_scroll_region_and_scroll_up() {
|
||||
80,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scroll_region_full_screen";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1514,6 +1595,7 @@ pub fn ring_bell() {
|
||||
64,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "ring_bell";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1531,6 +1613,7 @@ pub fn alternate_screen_change_size() {
|
||||
20,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "alternate_screen_change_size";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1552,6 +1635,7 @@ pub fn fzf_fullscreen() {
|
||||
112,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "fzf_fullscreen";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1573,6 +1657,7 @@ pub fn replace_multiple_wide_characters_under_cursor() {
|
||||
112,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "replace_multiple_wide_characters";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1594,6 +1679,7 @@ pub fn replace_non_wide_characters_with_wide_characters() {
|
||||
112,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "replace_non_wide_characters_with_wide_characters";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1611,6 +1697,7 @@ pub fn scroll_down_ansi() {
|
||||
112,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let fixture_name = "scroll_down";
|
||||
let content = read_fixture(fixture_name);
|
||||
@ -1619,3 +1706,55 @@ pub fn scroll_down_ansi() {
|
||||
}
|
||||
assert_snapshot!(format!("{:?}", grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_pixel_size_reports() {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut grid = Grid::new(
|
||||
51,
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(Some(SizeInPixels {
|
||||
height: 21,
|
||||
width: 8,
|
||||
}))),
|
||||
);
|
||||
let fixture_name = "terminal_pixel_size_reports";
|
||||
let content = read_fixture(fixture_name);
|
||||
for byte in content {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
assert_eq!(
|
||||
grid.pending_messages_to_pty
|
||||
.iter()
|
||||
.map(|bytes| String::from_utf8(bytes.clone()).unwrap())
|
||||
.collect::<Vec<String>>(),
|
||||
vec!["\x1b[4;1071;776t", "\x1b[6;21;8t"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_pixel_size_reports_in_unsupported_terminals() {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut grid = Grid::new(
|
||||
51,
|
||||
97,
|
||||
Palette::default(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)), // in an unsupported terminal, we don't have this info
|
||||
);
|
||||
let fixture_name = "terminal_pixel_size_reports";
|
||||
let content = read_fixture(fixture_name);
|
||||
for byte in content {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
let expected: Vec<String> = vec![];
|
||||
assert_eq!(
|
||||
grid.pending_messages_to_pty
|
||||
.iter()
|
||||
.map(|bytes| String::from_utf8(bytes.clone()).unwrap())
|
||||
.collect::<Vec<String>>(),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
@ -430,6 +430,14 @@ pub(crate) fn route_thread_main(
|
||||
.send_to_screen(ScreenInstruction::TerminalResize(min_size))
|
||||
.unwrap();
|
||||
}
|
||||
ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions) => {
|
||||
rlocked_sessions
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::TerminalPixelDimensions(pixel_dimensions))
|
||||
.unwrap();
|
||||
}
|
||||
ClientToServerMsg::NewClient(
|
||||
client_attributes,
|
||||
cli_args,
|
||||
|
@ -8,7 +8,7 @@ use std::str;
|
||||
|
||||
use zellij_tile::prelude::Style;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::{
|
||||
input::command::TerminalAction, input::layout::Layout, position::Position, zellij_tile,
|
||||
};
|
||||
@ -27,7 +27,7 @@ use zellij_tile::data::{Event, InputMode, ModeInfo, PluginCapabilities, TabInfo}
|
||||
use zellij_utils::{
|
||||
errors::{ContextType, ScreenContext},
|
||||
input::{get_mode_info, options::Options},
|
||||
ipc::ClientAttributes,
|
||||
ipc::{ClientAttributes, PixelDimensions},
|
||||
};
|
||||
|
||||
/// Instructions that can be sent to the [`Screen`].
|
||||
@ -87,6 +87,7 @@ pub enum ScreenInstruction {
|
||||
ToggleTab(ClientId),
|
||||
UpdateTabName(Vec<u8>, ClientId),
|
||||
TerminalResize(Size),
|
||||
TerminalPixelDimensions(PixelDimensions),
|
||||
ChangeMode(ModeInfo, ClientId),
|
||||
LeftClick(Position, ClientId),
|
||||
RightClick(Position, ClientId),
|
||||
@ -162,6 +163,9 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::GoToTab(..) => ScreenContext::GoToTab,
|
||||
ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName,
|
||||
ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize,
|
||||
ScreenInstruction::TerminalPixelDimensions(..) => {
|
||||
ScreenContext::TerminalPixelDimensions
|
||||
}
|
||||
ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode,
|
||||
ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab,
|
||||
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
||||
@ -193,6 +197,8 @@ pub(crate) struct Screen {
|
||||
tabs: BTreeMap<usize, Tab>,
|
||||
/// The full size of this [`Screen`].
|
||||
size: Size,
|
||||
pixel_dimensions: PixelDimensions,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
/// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`]
|
||||
overlay: OverlayWindow,
|
||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||
@ -225,6 +231,8 @@ impl Screen {
|
||||
bus,
|
||||
max_panes,
|
||||
size: client_attributes.size,
|
||||
pixel_dimensions: Default::default(),
|
||||
character_cell_size: Rc::new(RefCell::new(None)),
|
||||
style: client_attributes.style,
|
||||
connected_clients: Rc::new(RefCell::new(HashSet::new())),
|
||||
active_tab_indices: BTreeMap::new(),
|
||||
@ -434,6 +442,20 @@ impl Screen {
|
||||
}
|
||||
self.render();
|
||||
}
|
||||
pub fn update_pixel_dimensions(&mut self, pixel_dimensions: PixelDimensions) {
|
||||
self.pixel_dimensions.merge(pixel_dimensions);
|
||||
if let Some(character_cell_size) = self.pixel_dimensions.character_cell_size {
|
||||
*self.character_cell_size.borrow_mut() = Some(character_cell_size);
|
||||
} else if let Some(text_area_size) = self.pixel_dimensions.text_area_size {
|
||||
let character_cell_size_height = text_area_size.height / self.size.rows;
|
||||
let character_cell_size_width = text_area_size.width / self.size.cols;
|
||||
let character_cell_size = SizeInPixels {
|
||||
height: character_cell_size_height,
|
||||
width: character_cell_size_width,
|
||||
};
|
||||
*self.character_cell_size.borrow_mut() = Some(character_cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||
pub fn render(&mut self) {
|
||||
@ -514,6 +536,7 @@ impl Screen {
|
||||
position,
|
||||
String::new(),
|
||||
self.size,
|
||||
self.character_cell_size.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.max_panes,
|
||||
@ -1254,6 +1277,9 @@ pub(crate) fn screen_thread_main(
|
||||
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => {
|
||||
screen.update_pixel_dimensions(pixel_dimensions);
|
||||
}
|
||||
ScreenInstruction::ChangeMode(mode_info, client_id) => {
|
||||
screen.change_mode(mode_info, client_id);
|
||||
|
||||
|
@ -40,7 +40,7 @@ use zellij_utils::{
|
||||
layout::{Layout, Run},
|
||||
parse_keys,
|
||||
},
|
||||
pane_size::{Offset, PaneGeom, Size, Viewport},
|
||||
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
|
||||
};
|
||||
|
||||
macro_rules! resize_pty {
|
||||
@ -72,6 +72,7 @@ pub(crate) struct Tab {
|
||||
max_panes: Option<usize>,
|
||||
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>>>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
pub senders: ThreadSenders,
|
||||
synchronize_is_active: bool,
|
||||
@ -270,6 +271,7 @@ impl Tab {
|
||||
position: usize,
|
||||
name: String,
|
||||
display_area: Size,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
senders: ThreadSenders,
|
||||
max_panes: Option<usize>,
|
||||
@ -302,6 +304,7 @@ impl Tab {
|
||||
connected_clients.clone(),
|
||||
connected_clients_in_app.clone(),
|
||||
mode_info.clone(),
|
||||
character_cell_size.clone(),
|
||||
session_is_mirrored,
|
||||
draw_pane_frames,
|
||||
default_mode_info.clone(),
|
||||
@ -333,6 +336,7 @@ impl Tab {
|
||||
max_panes,
|
||||
viewport,
|
||||
display_area,
|
||||
character_cell_size,
|
||||
synchronize_is_active: false,
|
||||
os_api,
|
||||
senders,
|
||||
@ -413,6 +417,7 @@ impl Tab {
|
||||
next_terminal_position,
|
||||
layout.pane_name.clone().unwrap_or_default(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
new_pane.set_borderless(layout.borderless);
|
||||
self.tiled_panes
|
||||
@ -640,6 +645,7 @@ impl Tab {
|
||||
next_terminal_position,
|
||||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
|
||||
resize_pty!(new_pane, self.os_api);
|
||||
@ -661,6 +667,7 @@ impl Tab {
|
||||
next_terminal_position,
|
||||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
|
||||
self.should_clear_display_before_rendering = true;
|
||||
@ -689,6 +696,7 @@ impl Tab {
|
||||
next_terminal_position,
|
||||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
||||
@ -715,6 +723,7 @@ impl Tab {
|
||||
next_terminal_position,
|
||||
String::new(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_vertically(pid, Box::new(new_terminal), client_id);
|
||||
|
@ -103,12 +103,14 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
connected_clients.insert(client_id);
|
||||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let copy_command = None;
|
||||
let character_cell_info = Rc::new(RefCell::new(None));
|
||||
let clipboard = Clipboard::default();
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_info,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
@ -151,6 +153,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
|
||||
columns,
|
||||
palette,
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
@ -171,6 +174,7 @@ fn take_snapshot_and_cursor_position(
|
||||
columns,
|
||||
palette,
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
|
@ -12,7 +12,7 @@ use zellij_tile::prelude::Style;
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
@ -96,6 +96,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
let client_id = 1;
|
||||
let session_is_mirrored = true;
|
||||
let mut connected_clients = HashSet::new();
|
||||
let character_cell_info = Rc::new(RefCell::new(None));
|
||||
connected_clients.insert(client_id);
|
||||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let copy_command = None;
|
||||
@ -105,6 +106,54 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_info,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
style,
|
||||
mode_info,
|
||||
draw_pane_frames,
|
||||
connected_clients,
|
||||
session_is_mirrored,
|
||||
client_id,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
vec![1],
|
||||
index,
|
||||
client_id,
|
||||
);
|
||||
tab
|
||||
}
|
||||
|
||||
fn create_new_tab_with_cell_size(
|
||||
size: Size,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
) -> Tab {
|
||||
let index = 0;
|
||||
let position = 0;
|
||||
let name = String::new();
|
||||
let os_api = Box::new(FakeInputOutput {});
|
||||
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 copy_command = None;
|
||||
let copy_clipboard = Clipboard::default();
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_size,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
@ -13897,3 +13946,28 @@ pub fn nondirectional_resize_increase_with_pane_above_aligned_right_with_current
|
||||
"Pane 3 col count"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn custom_cursor_height_width_ratio() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let character_cell_size = Rc::new(RefCell::new(None));
|
||||
let tab = create_new_tab_with_cell_size(size, character_cell_size.clone());
|
||||
let initial_cursor_height_width_ratio = tab.tiled_panes.cursor_height_width_ratio();
|
||||
*character_cell_size.borrow_mut() = Some(SizeInPixels {
|
||||
height: 10,
|
||||
width: 4,
|
||||
});
|
||||
let cursor_height_width_ratio_after_update = tab.tiled_panes.cursor_height_width_ratio();
|
||||
assert_eq!(
|
||||
initial_cursor_height_width_ratio, None,
|
||||
"initially no ratio "
|
||||
);
|
||||
assert_eq!(
|
||||
cursor_height_width_ratio_after_update,
|
||||
Some(3),
|
||||
"ratio updated successfully"
|
||||
); // 10 / 4 == 2.5, rounded: 3
|
||||
}
|
||||
|
@ -12,11 +12,11 @@ use zellij_utils::input::command::TerminalAction;
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use zellij_utils::ipc::ClientAttributes;
|
||||
use zellij_utils::ipc::{ClientAttributes, PixelDimensions};
|
||||
use zellij_utils::nix;
|
||||
|
||||
use zellij_utils::{
|
||||
@ -464,3 +464,81 @@ fn switch_to_tab_with_fullscreen() {
|
||||
"Active pane is still the fullscreen pane"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_screen_pixel_dimensions() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let mut screen = create_new_screen(size);
|
||||
let initial_pixel_dimensions = screen.pixel_dimensions;
|
||||
screen.update_pixel_dimensions(PixelDimensions {
|
||||
character_cell_size: Some(SizeInPixels {
|
||||
height: 10,
|
||||
width: 5,
|
||||
}),
|
||||
text_area_size: None,
|
||||
});
|
||||
let pixel_dimensions_after_first_update = screen.pixel_dimensions;
|
||||
screen.update_pixel_dimensions(PixelDimensions {
|
||||
character_cell_size: None,
|
||||
text_area_size: Some(SizeInPixels {
|
||||
height: 100,
|
||||
width: 50,
|
||||
}),
|
||||
});
|
||||
let pixel_dimensions_after_second_update = screen.pixel_dimensions;
|
||||
screen.update_pixel_dimensions(PixelDimensions {
|
||||
character_cell_size: None,
|
||||
text_area_size: None,
|
||||
});
|
||||
let pixel_dimensions_after_third_update = screen.pixel_dimensions;
|
||||
assert_eq!(
|
||||
initial_pixel_dimensions,
|
||||
PixelDimensions {
|
||||
character_cell_size: None,
|
||||
text_area_size: None
|
||||
},
|
||||
"Initial pixel dimensions empty"
|
||||
);
|
||||
assert_eq!(
|
||||
pixel_dimensions_after_first_update,
|
||||
PixelDimensions {
|
||||
character_cell_size: Some(SizeInPixels {
|
||||
height: 10,
|
||||
width: 5
|
||||
}),
|
||||
text_area_size: None
|
||||
},
|
||||
"character_cell_size updated properly",
|
||||
);
|
||||
assert_eq!(
|
||||
pixel_dimensions_after_second_update,
|
||||
PixelDimensions {
|
||||
character_cell_size: Some(SizeInPixels {
|
||||
height: 10,
|
||||
width: 5
|
||||
}),
|
||||
text_area_size: Some(SizeInPixels {
|
||||
height: 100,
|
||||
width: 50,
|
||||
}),
|
||||
},
|
||||
"text_area_size updated properly without overriding character_cell_size",
|
||||
);
|
||||
assert_eq!(
|
||||
pixel_dimensions_after_third_update,
|
||||
PixelDimensions {
|
||||
character_cell_size: Some(SizeInPixels {
|
||||
height: 10,
|
||||
width: 5
|
||||
}),
|
||||
text_area_size: Some(SizeInPixels {
|
||||
height: 100,
|
||||
width: 50,
|
||||
}),
|
||||
},
|
||||
"empty update does not delete existing data",
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ log = "0.4.16"
|
||||
log4rs = "1.0.0"
|
||||
unicode-width = "0.1.8"
|
||||
miette = { version = "3.3.0", features = ["fancy"] }
|
||||
regex = "1.5.5"
|
||||
termwiz = "0.16.0"
|
||||
|
||||
|
||||
|
@ -267,6 +267,7 @@ pub enum ScreenContext {
|
||||
GoToTab,
|
||||
UpdateTabName,
|
||||
TerminalResize,
|
||||
TerminalPixelDimensions,
|
||||
ChangeMode,
|
||||
LeftClick,
|
||||
RightClick,
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
cli::CliArgs,
|
||||
errors::{get_current_ctx, ErrorContext},
|
||||
input::{actions::Action, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig},
|
||||
pane_size::Size,
|
||||
pane_size::{Size, SizeInPixels},
|
||||
};
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
use nix::unistd::dup;
|
||||
@ -43,6 +43,23 @@ pub struct ClientAttributes {
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PixelDimensions {
|
||||
pub text_area_size: Option<SizeInPixels>,
|
||||
pub character_cell_size: Option<SizeInPixels>,
|
||||
}
|
||||
|
||||
impl PixelDimensions {
|
||||
pub fn merge(&mut self, other: PixelDimensions) {
|
||||
if let Some(text_area_size) = other.text_area_size {
|
||||
self.text_area_size = Some(text_area_size);
|
||||
}
|
||||
if let Some(character_cell_size) = other.character_cell_size {
|
||||
self.character_cell_size = Some(character_cell_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Types of messages sent from the client to the server
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@ -57,6 +74,7 @@ pub enum ClientToServerMsg {
|
||||
DetachSession(SessionId),
|
||||
// Disconnect from the session we're connected to
|
||||
DisconnectFromSession,*/
|
||||
TerminalPixelDimensions(PixelDimensions),
|
||||
TerminalResize(Size),
|
||||
NewClient(
|
||||
ClientAttributes,
|
||||
|
@ -15,8 +15,10 @@ pub use anyhow;
|
||||
pub use async_std;
|
||||
pub use clap;
|
||||
pub use interprocess;
|
||||
pub use lazy_static;
|
||||
pub use libc;
|
||||
pub use nix;
|
||||
pub use regex;
|
||||
pub use serde;
|
||||
pub use serde_yaml;
|
||||
pub use signal_hook;
|
||||
|
@ -34,6 +34,12 @@ pub struct Size {
|
||||
pub cols: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct SizeInPixels {
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
#[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Dimension {
|
||||
pub constraint: Constraint,
|
||||
|
Loading…
Reference in New Issue
Block a user