refactor(tabs): lay down infrastructure for multiple users (#864)

* refactor(screen): support multiple mirrored clients

* style(fmt): make rustfmt happy

* style(clippy): make clippy happy

* whitespace

* github, y u no update CI?!

* is this a cache issue?

* is it the checkout cache?

* no cache at all?

* Debug

* fix gototab

* decoment

* gototab none in wasm_vm

* gototab none in wasm_vm

* the fun never ends

* tests(e2e): update infra and add multiple user mirroring test

* refactor(tab): change structs in tabs and terminal panes to support multiple users

* style(fmt): make rustfmt happy

* style(fmt): make clippy happy
This commit is contained in:
Aram Drevekenin 2021-11-12 17:22:14 +01:00 committed by GitHub
parent 3e07040808
commit bd795a3e9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 2274 additions and 1579 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
use std::sync::{Arc, Mutex};
use zellij_tile::data::Palette;
use zellij_server::panes::TerminalPane;
@ -16,6 +17,7 @@ const CONNECTION_STRING: &str = "127.0.0.1:2222";
const CONNECTION_USERNAME: &str = "test";
const CONNECTION_PASSWORD: &str = "test";
const SESSION_NAME: &str = "e2e-test";
const RETRIES: usize = 10;
fn ssh_connect() -> ssh2::Session {
let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
@ -24,7 +26,16 @@ fn ssh_connect() -> ssh2::Session {
sess.handshake().unwrap();
sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
.unwrap();
sess.set_timeout(3000);
sess
}
fn ssh_connect_without_timeout() -> ssh2::Session {
let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
.unwrap();
sess
}
@ -107,6 +118,76 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
channel.flush().unwrap();
}
fn read_from_channel(
channel: &Arc<Mutex<ssh2::Channel>>,
last_snapshot: &Arc<Mutex<String>>,
cursor_coordinates: &Arc<Mutex<(usize, usize)>>,
pane_geom: &PaneGeom,
) -> (Arc<Mutex<bool>>, std::thread::JoinHandle<()>) {
let should_keep_running = Arc::new(Mutex::new(true));
let thread = std::thread::Builder::new()
.name("read_thread".into())
.spawn({
let should_keep_running = should_keep_running.clone();
let channel = channel.clone();
let last_snapshot = last_snapshot.clone();
let cursor_coordinates = cursor_coordinates.clone();
let mut vte_parser = vte::Parser::new();
let mut terminal_output = TerminalPane::new(0, *pane_geom, Palette::default(), 0); // 0 is the pane index
let mut retries_left = 3;
move || {
let mut should_sleep = false;
loop {
if !*should_keep_running.lock().unwrap() {
break;
}
if should_sleep {
std::thread::sleep(std::time::Duration::from_millis(10));
should_sleep = false;
}
let mut buf = [0u8; 1280000];
match channel.lock().unwrap().read(&mut buf) {
Ok(0) => {
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
}
Ok(count) => {
for byte in buf.iter().take(count) {
vte_parser.advance(&mut terminal_output.grid, *byte);
}
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.grid.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
} else if retries_left > 0 {
retries_left -= 1;
} else {
break;
}
}
}
}
}
})
.unwrap();
(should_keep_running, thread)
}
pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
let output_lines = terminal_output.read_buffer_as_lines();
let cursor_coordinates = terminal_output.cursor_coordinates();
@ -128,42 +209,44 @@ pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
snapshot
}
pub struct RemoteTerminal<'a> {
channel: &'a mut ssh2::Channel,
pub struct RemoteTerminal {
channel: Arc<Mutex<ssh2::Channel>>,
cursor_x: usize,
cursor_y: usize,
current_snapshot: String,
last_snapshot: Arc<Mutex<String>>,
}
impl<'a> std::fmt::Debug for RemoteTerminal<'a> {
impl std::fmt::Debug for RemoteTerminal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"cursor x: {}\ncursor_y: {}\ncurrent_snapshot:\n{}",
self.cursor_x, self.cursor_y, self.current_snapshot
self.cursor_x,
self.cursor_y,
*self.last_snapshot.lock().unwrap()
)
}
}
impl<'a> RemoteTerminal<'a> {
impl RemoteTerminal {
pub fn cursor_position_is(&self, x: usize, y: usize) -> bool {
x == self.cursor_x && y == self.cursor_y
}
pub fn tip_appears(&self) -> bool {
self.current_snapshot.contains("Tip:")
self.last_snapshot.lock().unwrap().contains("Tip:")
}
pub fn status_bar_appears(&self) -> bool {
self.current_snapshot.contains("Ctrl +")
self.last_snapshot.lock().unwrap().contains("Ctrl +")
}
pub fn snapshot_contains(&self, text: &str) -> bool {
self.current_snapshot.contains(text)
self.last_snapshot.lock().unwrap().contains(text)
}
#[allow(unused)]
pub fn current_snapshot(&self) -> String {
// convenience method for writing tests,
// this should only be used when developing,
// please prefer "snapsht_contains" instead
self.current_snapshot.clone()
self.last_snapshot.lock().unwrap().clone()
}
#[allow(unused)]
pub fn current_cursor_position(&self) -> String {
@ -173,21 +256,25 @@ impl<'a> RemoteTerminal<'a> {
format!("x: {}, y: {}", self.cursor_x, self.cursor_y)
}
pub fn send_key(&mut self, key: &[u8]) {
self.channel.write_all(key).unwrap();
self.channel.flush().unwrap();
let mut channel = self.channel.lock().unwrap();
channel.write_all(key).unwrap();
channel.flush().unwrap();
}
pub fn change_size(&mut self, cols: u32, rows: u32) {
self.channel
.lock()
.unwrap()
.request_pty_size(cols, rows, Some(cols), Some(rows))
.unwrap();
}
pub fn attach_to_original_session(&mut self) {
self.channel
let mut channel = self.channel.lock().unwrap();
channel
.write_all(
format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME).as_bytes(),
)
.unwrap();
self.channel.flush().unwrap();
channel.flush().unwrap();
}
}
@ -200,26 +287,21 @@ pub struct Step {
pub struct RemoteRunner {
steps: Vec<Step>,
current_step_index: usize,
vte_parser: vte::Parser,
terminal_output: TerminalPane,
channel: ssh2::Channel,
test_name: &'static str,
channel: Arc<Mutex<ssh2::Channel>>,
currently_running_step: Option<String>,
retries_left: usize,
win_size: Size,
layout_file_name: Option<&'static str>,
without_frames: bool,
session_name: Option<String>,
attach_to_existing: bool,
retry_pause_ms: usize,
panic_on_no_retries_left: bool,
last_snapshot: Arc<Mutex<String>>,
cursor_coordinates: Arc<Mutex<(usize, usize)>>, // x, y
reader_thread: (Arc<Mutex<bool>>, std::thread::JoinHandle<()>),
pub test_timed_out: bool,
}
impl RemoteRunner {
pub fn new(test_name: &'static str, win_size: Size) -> Self {
pub fn new(win_size: Size) -> Self {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
@ -230,35 +312,38 @@ impl RemoteRunner {
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 10,
win_size,
layout_file_name: None,
without_frames: false,
session_name: None,
attach_to_existing: false,
retries_left: RETRIES,
retry_pause_ms: 100,
test_timed_out: false,
panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
}
}
pub fn new_with_session_name(
test_name: &'static str,
win_size: Size,
session_name: &str,
) -> Self {
pub fn kill_running_sessions(win_size: Size) {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel);
}
pub fn new_with_session_name(win_size: Size, session_name: &str) -> Self {
// notice that this method does not have a timeout, so use with caution!
let sess = ssh_connect_without_timeout();
let mut channel = sess.channel_session().unwrap();
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
@ -269,35 +354,31 @@ impl RemoteRunner {
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_in_session(&mut channel, session_name);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 10,
win_size,
layout_file_name: None,
without_frames: false,
session_name: Some(String::from(session_name)),
attach_to_existing: false,
retries_left: RETRIES,
retry_pause_ms: 100,
test_timed_out: false,
panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
}
}
pub fn new_existing_session(
test_name: &'static str,
win_size: Size,
session_name: &str,
) -> Self {
let sess = ssh_connect();
pub fn new_existing_session(win_size: Size, session_name: &str) -> Self {
let sess = ssh_connect_without_timeout();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
@ -308,31 +389,31 @@ impl RemoteRunner {
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
attach_to_existing_session(&mut channel, session_name);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 10,
win_size,
layout_file_name: None,
without_frames: false,
session_name: Some(String::from(session_name)),
attach_to_existing: true,
retries_left: RETRIES,
retry_pause_ms: 100,
test_timed_out: false,
panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
}
}
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
pub fn new_without_frames(win_size: Size) -> Self {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
@ -343,36 +424,32 @@ impl RemoteRunner {
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_without_frames(&mut channel);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 10,
win_size,
layout_file_name: None,
without_frames: true,
session_name: None,
attach_to_existing: false,
retries_left: RETRIES,
retry_pause_ms: 100,
test_timed_out: false,
panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
}
}
pub fn new_with_layout(
test_name: &'static str,
win_size: Size,
layout_file_name: &'static str,
) -> Self {
pub fn new_with_layout(win_size: Size, layout_file_name: &'static str) -> Self {
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name);
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
@ -383,159 +460,104 @@ impl RemoteRunner {
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_with_layout(&mut channel, &remote_path.to_string_lossy());
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 10,
win_size,
layout_file_name: Some(layout_file_name),
without_frames: false,
session_name: None,
attach_to_existing: false,
retries_left: RETRIES,
retry_pause_ms: 100,
test_timed_out: false,
panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
}
}
pub fn dont_panic(mut self) -> Self {
self.panic_on_no_retries_left = false;
self
}
pub fn retry_pause_ms(mut self, retry_pause_ms: usize) -> Self {
self.retry_pause_ms = retry_pause_ms;
self
}
pub fn add_step(mut self, step: Step) -> Self {
self.steps.push(step);
self
}
pub fn replace_steps(&mut self, steps: Vec<Step>) {
self.steps = steps;
}
fn display_informative_error(&mut self) {
let test_name = self.test_name;
let current_step_name = self.currently_running_step.as_ref().cloned();
match current_step_name {
Some(current_step) => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Was waiting for step: {}", test_name, current_step);
eprintln!("{:?}", remote_terminal);
}
None => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Haven't begun running steps yet.", test_name);
eprintln!("{:?}", remote_terminal);
}
}
}
pub fn get_current_snapshot(&mut self) -> String {
take_snapshot(&mut self.terminal_output)
}
fn current_remote_terminal_state(&mut self) -> RemoteTerminal {
let current_snapshot = self.get_current_snapshot();
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
RemoteTerminal {
cursor_x,
cursor_y,
current_snapshot,
channel: &mut self.channel,
}
}
pub fn run_next_step(&mut self) {
if let Some(next_step) = self.steps.get(self.current_step_index) {
let current_snapshot = take_snapshot(&mut self.terminal_output);
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
let (cursor_x, cursor_y) = *self.cursor_coordinates.lock().unwrap();
let remote_terminal = RemoteTerminal {
cursor_x,
cursor_y,
current_snapshot,
channel: &mut self.channel,
last_snapshot: self.last_snapshot.clone(),
channel: self.channel.clone(),
};
let instruction = next_step.instruction;
self.currently_running_step = Some(String::from(next_step.name));
if instruction(remote_terminal) {
self.retries_left = RETRIES;
self.current_step_index += 1;
} else {
self.retries_left -= 1;
std::thread::sleep(std::time::Duration::from_millis(self.retry_pause_ms as u64));
}
}
}
pub fn steps_left(&self) -> bool {
self.steps.get(self.current_step_index).is_some()
}
fn restart_test(&mut self) -> String {
if let Some(layout_file_name) = self.layout_file_name.as_ref() {
// let mut new_runner = RemoteRunner::new_with_layout(self.test_name, self.win_size, Path::new(&local_layout_path), session_name);
let mut new_runner =
RemoteRunner::new_with_layout(self.test_name, self.win_size, layout_file_name);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else if self.without_frames {
let mut new_runner = RemoteRunner::new_without_frames(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else if self.session_name.is_some() {
let mut new_runner = if self.attach_to_existing {
RemoteRunner::new_existing_session(
self.test_name,
self.win_size,
self.session_name.as_ref().unwrap(),
)
} else {
RemoteRunner::new_with_session_name(
self.test_name,
self.win_size,
self.session_name.as_ref().unwrap(),
)
};
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else {
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
}
self.run_all_steps()
}
pub fn run_all_steps(&mut self) -> String {
// returns the last snapshot
pub fn take_snapshot_after(&mut self, step: Step) -> String {
let mut retries_left = RETRIES;
let instruction = step.instruction;
loop {
let mut buf = [0u8; 1024];
match self.channel.read(&mut buf) {
Ok(0) => break,
Ok(_count) => {
for byte in buf.iter() {
self.vte_parser
.advance(&mut self.terminal_output.grid, *byte);
if retries_left == 0 {
self.test_timed_out = true;
return self.last_snapshot.lock().unwrap().clone();
}
let (cursor_x, cursor_y) = *self.cursor_coordinates.lock().unwrap();
let remote_terminal = RemoteTerminal {
cursor_x,
cursor_y,
last_snapshot: self.last_snapshot.clone(),
channel: self.channel.clone(),
};
if instruction(remote_terminal) {
return self.last_snapshot.lock().unwrap().clone();
} else {
retries_left -= 1;
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
}
}
pub fn run_all_steps(&mut self) {
loop {
self.run_next_step();
if !self.steps_left() {
break;
}
}
Err(e) => {
if self.retries_left > 0 {
return self.restart_test();
}
} else if self.retries_left == 0 {
self.test_timed_out = true;
if self.panic_on_no_retries_left {
self.display_informative_error();
panic!("timed out waiting for test: {:?}", e);
break;
}
}
}
}
take_snapshot(&mut self.terminal_output)
}
}
impl Drop for RemoteRunner {
fn drop(&mut self) {
let _ = self.channel.close();
let _ = self.channel.lock().unwrap().close();
let reader_thread_running = &mut self.reader_thread.0;
*reader_thread_running.lock().unwrap() = false;
}
}

View file

@ -22,4 +22,4 @@ expression: last_snapshot
│ │
└──────┘
Ctrl +
...

View file

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
$ │$
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ █ │$
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -0,0 +1,29 @@
---
source: src/tests/e2e/cases.rs
expression: second_runner_snapshot
---
Zellij (mirrored_sessions)  Tab #1  Tab #2 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ █ ││$ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -1,11 +1,11 @@
---
source: src/tests/e2e/cases.rs
expression: last_snapshot
expression: first_runner_snapshot
---
Zellij (mirrored_sessions)  Tab #1 
Zellij (mirrored_sessions)  Tab #1  Tab #2 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ ││$ █
│$ █ ││$
│ ││ │
│ ││ │
│ ││ │
@ -26,4 +26,4 @@ expression: last_snapshot
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -0,0 +1,29 @@
---
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│$ █ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.

View file

@ -348,7 +348,7 @@ impl Screen {
let mut output = Output::default();
let mut tabs_to_close = vec![];
for (tab_index, tab) in self.tabs.iter_mut() {
if tab.get_active_pane().is_some() {
if tab.has_active_panes() {
tab.render(&mut output);
} else {
tabs_to_close.push(*tab_index);
@ -511,20 +511,20 @@ impl Screen {
{
self.get_active_tab_mut(client_id)
.unwrap()
.clear_active_terminal_scroll();
.clear_active_terminal_scroll(client_id);
}
self.colors = mode_info.palette;
self.mode_info = mode_info;
for tab in self.tabs.values_mut() {
tab.mode_info = self.mode_info.clone();
tab.mark_active_pane_for_rerender();
tab.mark_active_pane_for_rerender(client_id);
}
}
pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) {
if !self
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_left()
.move_focus_left(client_id)
{
self.switch_tab_prev(client_id);
}
@ -533,7 +533,7 @@ impl Screen {
if !self
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_right()
.move_focus_right(client_id)
{
self.switch_tab_next(client_id);
}
@ -597,10 +597,13 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::NewPane(pid, client_or_tab_index) => {
match client_or_tab_index {
ClientOrTabIndex::ClientId(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().new_pane(pid);
screen
.get_active_tab_mut(client_id)
.unwrap()
.new_pane(pid, Some(client_id));
}
ClientOrTabIndex::TabIndex(tab_index) => {
screen.tabs.get_mut(&tab_index).unwrap().new_pane(pid);
screen.tabs.get_mut(&tab_index).unwrap().new_pane(pid, None);
}
};
screen
@ -616,7 +619,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.horizontal_split(pid);
.horizontal_split(pid, client_id);
screen
.bus
.senders
@ -630,7 +633,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.vertical_split(pid);
.vertical_split(pid, client_id);
screen
.bus
.senders
@ -644,26 +647,38 @@ pub(crate) fn screen_thread_main(
let active_tab = screen.get_active_tab_mut(client_id).unwrap();
match active_tab.is_sync_panes_active() {
true => active_tab.write_to_terminals_on_current_tab(bytes),
false => active_tab.write_to_active_terminal(bytes),
false => active_tab.write_to_active_terminal(bytes, client_id),
}
}
ScreenInstruction::ResizeLeft(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_left();
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_left(client_id);
screen.render();
}
ScreenInstruction::ResizeRight(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_right();
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_right(client_id);
screen.render();
}
ScreenInstruction::ResizeDown(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_down();
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_down(client_id);
screen.render();
}
ScreenInstruction::ResizeUp(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_up();
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_up(client_id);
screen.render();
}
@ -671,7 +686,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_increase();
.resize_increase(client_id);
screen.render();
}
@ -679,12 +694,15 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_decrease();
.resize_decrease(client_id);
screen.render();
}
ScreenInstruction::SwitchFocus(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().move_focus();
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus(client_id);
screen.render();
}
@ -692,7 +710,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.focus_next_pane();
.focus_next_pane(client_id);
screen.render();
}
@ -700,7 +718,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.focus_previous_pane();
.focus_previous_pane(client_id);
screen.render();
}
@ -708,7 +726,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_left();
.move_focus_left(client_id);
screen.render();
}
@ -726,7 +744,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_down();
.move_focus_down(client_id);
screen.render();
}
@ -734,7 +752,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_right();
.move_focus_right(client_id);
screen.render();
}
@ -752,7 +770,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_up();
.move_focus_up(client_id);
screen.render();
}
@ -760,7 +778,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_up();
.scroll_active_terminal_up(client_id);
screen.render();
}
@ -768,7 +786,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_active_pane();
.move_active_pane(client_id);
screen.render();
}
@ -776,7 +794,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_active_pane_down();
.move_active_pane_down(client_id);
screen.render();
}
@ -784,7 +802,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_active_pane_up();
.move_active_pane_up(client_id);
screen.render();
}
@ -792,7 +810,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_active_pane_right();
.move_active_pane_right(client_id);
screen.render();
}
@ -800,7 +818,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_active_pane_left();
.move_active_pane_left(client_id);
screen.render();
}
@ -816,7 +834,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_down();
.scroll_active_terminal_down(client_id);
screen.render();
}
@ -832,7 +850,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_to_bottom();
.scroll_active_terminal_to_bottom(client_id);
screen.render();
}
@ -840,7 +858,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_up_page();
.scroll_active_terminal_up_page(client_id);
screen.render();
}
@ -848,7 +866,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_down_page();
.scroll_active_terminal_down_page(client_id);
screen.render();
}
@ -856,7 +874,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_up_half_page();
.scroll_active_terminal_up_half_page(client_id);
screen.render();
}
@ -864,7 +882,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.scroll_active_terminal_down_half_page();
.scroll_active_terminal_down_half_page(client_id);
screen.render();
}
@ -872,7 +890,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.clear_active_terminal_scroll();
.clear_active_terminal_scroll(client_id);
screen.render();
}
@ -880,7 +898,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.close_focused_pane();
.close_focused_pane(client_id);
screen.update_tabs(); // update_tabs eventually calls render through the plugin thread
}
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
@ -917,7 +935,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.toggle_active_pane_fullscreen();
.toggle_active_pane_fullscreen(client_id);
screen.update_tabs();
screen.render();
@ -1011,7 +1029,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.handle_left_click(&point);
.handle_left_click(&point, client_id);
screen.render();
}
@ -1019,7 +1037,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.handle_right_click(&point);
.handle_right_click(&point, client_id);
screen.render();
}
@ -1027,7 +1045,7 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.handle_mouse_release(&point);
.handle_mouse_release(&point, client_id);
screen.render();
}
@ -1035,12 +1053,15 @@ pub(crate) fn screen_thread_main(
screen
.get_active_tab_mut(client_id)
.unwrap()
.handle_mouse_hold(&point);
.handle_mouse_hold(&point, client_id);
screen.render();
}
ScreenInstruction::Copy(client_id) => {
screen.get_active_tab(client_id).unwrap().copy_selection();
screen
.get_active_tab(client_id)
.unwrap()
.copy_selection(client_id);
screen.render();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,3 @@
use crate::{
logging_pipe::LoggingPipe,
panes::PaneId,
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
};
use highway::{HighwayHash, PortableHash};
use log::{debug, info, warn};
use serde::{de::DeserializeOwned, Serialize};
@ -25,6 +18,15 @@ use wasmer::{
};
use wasmer_wasi::{Pipe, WasiEnv, WasiState};
use zellij_tile::data::{Event, EventType, PluginIds};
use crate::{
logging_pipe::LoggingPipe,
panes::PaneId,
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
};
use zellij_utils::{
consts::ZELLIJ_PROJ_DIR,
errors::{ContextType, PluginContext},