* 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
544 lines
13 KiB
Rust
544 lines
13 KiB
Rust
use super::{Screen, ScreenInstruction};
|
|
use crate::panes::PaneId;
|
|
use crate::zellij_tile::data::{ModeInfo, Palette};
|
|
use crate::{
|
|
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
|
thread_bus::Bus,
|
|
ClientId,
|
|
};
|
|
use std::convert::TryInto;
|
|
use std::path::PathBuf;
|
|
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, SizeInPixels};
|
|
|
|
use std::os::unix::io::RawFd;
|
|
|
|
use zellij_utils::ipc::{ClientAttributes, PixelDimensions};
|
|
use zellij_utils::nix;
|
|
|
|
use zellij_utils::{
|
|
interprocess::local_socket::LocalSocketStream,
|
|
ipc::{ClientToServerMsg, ServerToClientMsg},
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
struct FakeInputOutput {}
|
|
|
|
impl ServerOsApi for FakeInputOutput {
|
|
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
|
// noop
|
|
}
|
|
fn spawn_terminal(
|
|
&self,
|
|
_file_to_open: TerminalAction,
|
|
_quit_db: Box<dyn Fn(PaneId) + Send>,
|
|
) -> (RawFd, RawFd) {
|
|
unimplemented!()
|
|
}
|
|
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
|
unimplemented!()
|
|
}
|
|
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
|
|
unimplemented!()
|
|
}
|
|
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
|
|
unimplemented!()
|
|
}
|
|
fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> {
|
|
unimplemented!()
|
|
}
|
|
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
|
unimplemented!()
|
|
}
|
|
fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
|
unimplemented!()
|
|
}
|
|
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
|
Box::new((*self).clone())
|
|
}
|
|
fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) {
|
|
unimplemented!()
|
|
}
|
|
fn new_client(
|
|
&mut self,
|
|
_client_id: ClientId,
|
|
_stream: LocalSocketStream,
|
|
) -> IpcReceiverWithContext<ClientToServerMsg> {
|
|
unimplemented!()
|
|
}
|
|
fn remove_client(&mut self, _client_id: ClientId) {
|
|
unimplemented!()
|
|
}
|
|
fn load_palette(&self) -> Palette {
|
|
unimplemented!()
|
|
}
|
|
fn get_cwd(&self, _pid: Pid) -> Option<PathBuf> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
fn create_new_screen(size: Size) -> Screen {
|
|
let mut bus: Bus<ScreenInstruction> = Bus::empty();
|
|
let fake_os_input = FakeInputOutput {};
|
|
bus.os_input = Some(Box::new(fake_os_input));
|
|
let client_attributes = ClientAttributes {
|
|
size,
|
|
..Default::default()
|
|
};
|
|
let max_panes = None;
|
|
let mode_info = ModeInfo::default();
|
|
let draw_pane_frames = false;
|
|
let session_is_mirrored = true;
|
|
let copy_command = None;
|
|
let copy_clipboard = Clipboard::default();
|
|
Screen::new(
|
|
bus,
|
|
&client_attributes,
|
|
max_panes,
|
|
mode_info,
|
|
draw_pane_frames,
|
|
session_is_mirrored,
|
|
copy_command,
|
|
copy_clipboard,
|
|
)
|
|
}
|
|
|
|
fn new_tab(screen: &mut Screen, pid: i32) {
|
|
let client_id = 1;
|
|
screen.new_tab(
|
|
LayoutTemplate::default().try_into().unwrap(),
|
|
vec![pid],
|
|
client_id,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn open_new_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
|
|
assert_eq!(screen.tabs.len(), 2, "Screen now has two tabs");
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab switched to new tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn switch_to_prev_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
screen.switch_tab_prev(1);
|
|
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
0,
|
|
"Active tab switched to previous tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn switch_to_next_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
screen.switch_tab_prev(1);
|
|
screen.switch_tab_next(1);
|
|
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab switched to next tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn close_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
screen.close_tab(1);
|
|
|
|
assert_eq!(screen.tabs.len(), 1, "Only one tab left");
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
0,
|
|
"Active tab switched to previous tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn close_the_middle_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
new_tab(&mut screen, 3);
|
|
screen.switch_tab_prev(1);
|
|
screen.close_tab(1);
|
|
|
|
assert_eq!(screen.tabs.len(), 2, "Two tabs left");
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab switched to previous tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn move_focus_left_at_left_screen_edge_changes_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
new_tab(&mut screen, 3);
|
|
screen.switch_tab_prev(1);
|
|
screen.move_focus_left_or_previous_tab(1);
|
|
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
0,
|
|
"Active tab switched to previous"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn move_focus_right_at_right_screen_edge_changes_tab() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
new_tab(&mut screen, 3);
|
|
screen.switch_tab_prev(1);
|
|
screen.move_focus_right_or_next_tab(1);
|
|
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
2,
|
|
"Active tab switched to next"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn toggle_to_previous_tab_simple() {
|
|
let position_and_size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(position_and_size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
screen.go_to_tab(1, 1);
|
|
screen.go_to_tab(2, 1);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
0,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn toggle_to_previous_tab_create_tabs_only() {
|
|
let position_and_size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(position_and_size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
new_tab(&mut screen, 2);
|
|
new_tab(&mut screen, 3);
|
|
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1],
|
|
"Tab history is invalid"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 2],
|
|
"Tab history is invalid"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
2,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1],
|
|
"Tab history is invalid"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn toggle_to_previous_tab_delete() {
|
|
let position_and_size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(position_and_size);
|
|
|
|
new_tab(&mut screen, 1); // 0
|
|
new_tab(&mut screen, 2); // 1
|
|
new_tab(&mut screen, 3); // 2
|
|
new_tab(&mut screen, 4); // 3
|
|
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1, 2],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
3,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1, 3],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
2,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1, 2],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
3,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.switch_tab_prev(1);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 1, 3],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
2,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
screen.switch_tab_prev(1);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 3, 2],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.close_tab(1);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 3],
|
|
"Tab history is invalid"
|
|
);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
1,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
|
|
screen.toggle_tab(1);
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
2,
|
|
"Active tab toggler to previous tab"
|
|
);
|
|
assert_eq!(
|
|
screen.tab_history.get(&1).unwrap(),
|
|
&[0, 2],
|
|
"Tab history is invalid"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn switch_to_tab_with_fullscreen() {
|
|
let size = Size {
|
|
cols: 121,
|
|
rows: 20,
|
|
};
|
|
let mut screen = create_new_screen(size);
|
|
|
|
new_tab(&mut screen, 1);
|
|
{
|
|
let active_tab = screen.get_active_tab_mut(1).unwrap();
|
|
active_tab.new_pane(PaneId::Terminal(2), Some(1));
|
|
active_tab.toggle_active_pane_fullscreen(1);
|
|
}
|
|
new_tab(&mut screen, 2);
|
|
|
|
screen.switch_tab_prev(1);
|
|
|
|
assert_eq!(
|
|
screen.get_active_tab(1).unwrap().position,
|
|
0,
|
|
"Active tab switched to previous"
|
|
);
|
|
assert_eq!(
|
|
screen
|
|
.get_active_tab(1)
|
|
.unwrap()
|
|
.get_active_pane_id(1)
|
|
.unwrap(),
|
|
PaneId::Terminal(2),
|
|
"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",
|
|
);
|
|
}
|