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:
Aram Drevekenin 2022-04-12 18:07:32 +02:00 committed by GitHub
parent 028885c822
commit 19adb29be5
24 changed files with 1099 additions and 27 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -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) {

View File

@ -0,0 +1 @@
;

View File

@ -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 {

View File

@ -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({

View 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")
}
}
}

View File

@ -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![],);
}

View File

@ -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. "")
@ -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

View File

@ -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(),

View File

@ -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) => {

View File

@ -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
{

View File

@ -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,
);
}

View File

@ -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 {

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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
}

View File

@ -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",
);
}

View File

@ -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"

View File

@ -267,6 +267,7 @@ pub enum ScreenContext {
GoToTab,
UpdateTabName,
TerminalResize,
TerminalPixelDimensions,
ChangeMode,
LeftClick,
RightClick,

View File

@ -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,

View File

@ -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;

View File

@ -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,