summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock1
-rw-r--r--Makefile.toml2
-rw-r--r--src/main.rs15
-rw-r--r--src/tests/e2e/cases.rs96
-rw-r--r--src/tests/e2e/remote_runner.rs178
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap4
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap29
-rw-r--r--zellij-client/Cargo.toml1
-rw-r--r--zellij-client/src/input_handler.rs30
-rw-r--r--zellij-client/src/lib.rs50
-rw-r--r--zellij-client/src/unit/input_handler_tests.rs79
-rw-r--r--zellij-server/src/lib.rs217
-rw-r--r--zellij-server/src/os_input_output.rs101
-rw-r--r--zellij-server/src/route.rs81
-rw-r--r--zellij-server/src/screen.rs13
-rw-r--r--zellij-server/src/tab.rs6
-rw-r--r--zellij-server/src/unit/screen_tests.rs43
-rw-r--r--zellij-server/src/unit/tab_tests.rs44
-rw-r--r--zellij-utils/src/cli.rs5
-rw-r--r--zellij-utils/src/errors.rs33
-rw-r--r--zellij-utils/src/ipc.rs9
22 files changed, 722 insertions, 316 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29e311d8c..4481475cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* Feature: Add ability to solely specify the tab name in the `tabs` section (https://github.com/zellij-org/zellij/pull/722)
* Feature: Plugins can be configured and the groundwork for "Headless" plugins has been laid (https://github.com/zellij-org/zellij/pull/660)
* Automatically update `example/default.yaml` on release (https://github.com/zellij-org/zellij/pull/736)
+* Feature: allow mirroring sessions in multiple terminal windows (https://github.com/zellij-org/zellij/pull/740)
## [0.17.0] - 2021-09-15
* New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691)
diff --git a/Cargo.lock b/Cargo.lock
index bf4ae0fd9..27d844651 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2736,6 +2736,7 @@ dependencies = [
"log",
"mio",
"termbg",
+ "zellij-tile",
"zellij-utils",
]
diff --git a/Makefile.toml b/Makefile.toml
index a5bb174e4..79392a647 100644
--- a/Makefile.toml
+++ b/Makefile.toml
@@ -142,7 +142,7 @@ args = ["build", "--verbose", "--release", "--target", "${CARGO_MAKE_TASK_ARGS}"
workspace = false
dependencies = ["build-plugins", "build-dev-data-dir"]
command = "cargo"
-args = ["build", "--verbose", "--target", "x86_64-unknown-linux-musl"]
+args = ["build", "--verbose", "--release", "--target", "x86_64-unknown-linux-musl"]
# Run e2e tests - we mark the e2e tests as "ignored" so they will not be run with the normal ones
[tasks.e2e-test]
diff --git a/src/main.rs b/src/main.rs
index 0a91ca652..90eeb96b2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -56,7 +56,6 @@ pub fn main() {
};
if let Some(Command::Sessions(Sessions::Attach {
session_name,
- force,
create,
options,
})) = opts.command.clone()
@@ -73,22 +72,14 @@ pub fn main() {
(ClientInfo::New(session_name.unwrap()), layout)
} else {
(
- ClientInfo::Attach(
- session_name.unwrap(),
- force,
- config_options.clone(),
- ),
+ ClientInfo::Attach(session_name.unwrap(), config_options.clone()),
None,
)
}
} else {
assert_session(session);
(
- ClientInfo::Attach(
- session_name.unwrap(),
- force,
- config_options.clone(),
- ),
+ ClientInfo::Attach(session_name.unwrap(), config_options.clone()),
None,
)
}
@@ -106,7 +97,7 @@ pub fn main() {
}
}
ActiveSession::One(session_name) => (
- ClientInfo::Attach(session_name, force, config_options.clone()),
+ ClientInfo::Attach(session_name, config_options.clone()),
None,
),
ActiveSession::Many => {
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs
index 307986c1a..77892463c 100644
--- a/src/tests/e2e/cases.rs
+++ b/src/tests/e2e/cases.rs
@@ -153,20 +153,12 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
},
})
.add_step(Step {
- name: "Send text to terminal",
- instruction: |mut remote_terminal: RemoteTerminal| -> bool {
- // this is just normal input that should be sent into the one terminal so that we can make
- // sure we silently failed to split in the previous step
- remote_terminal.send_key("Hi!".as_bytes());
- true
- },
- })
- .add_step(Step {
- name: "Wait for text to appear",
+ name: "Make sure only one pane appears",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
- if remote_terminal.cursor_position_is(6, 2) && remote_terminal.snapshot_contains("Hi!")
+ if remote_terminal.cursor_position_is(3, 2) && remote_terminal.snapshot_contains("...")
{
+ // ... is the truncated tip line
step_is_complete = true;
}
step_is_complete
@@ -917,3 +909,85 @@ pub fn start_without_pane_frames() {
.run_all_steps();
assert_snapshot!(last_snapshot);
}
+
+#[test]
+#[ignore]
+pub fn mirrored_sessions() {
+ let fake_win_size = Size {
+ cols: 120,
+ rows: 24,
+ };
+ let mut test_attempts = 10;
+ let session_name = "mirrored_sessions";
+ let mut last_snapshot = None;
+ loop {
+ // we run this test in a loop because there are some edge cases (especially in the CI)
+ // where the second runner times out and then we also need to restart the first runner
+ // if no test timed out, we break the loop and assert the snapshot
+ let mut first_runner =
+ RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears()
+ && remote_terminal.cursor_position_is(3, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for new pane to open",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2)
+ && remote_terminal.tip_appears()
+ {
+ // cursor is in the newly opened second pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ });
+ first_runner.run_all_steps();
+
+ let mut second_runner =
+ RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name)
+ .add_step(Step {
+ name: "Make sure session appears correctly",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2)
+ && remote_terminal.tip_appears()
+ {
+ // cursor is in the newly opened second pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ });
+ let last_test_snapshot = second_runner.run_all_steps();
+
+ if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 {
+ test_attempts -= 1;
+ continue;
+ } else {
+ last_snapshot = Some(last_test_snapshot);
+ break;
+ }
+ }
+ match last_snapshot {
+ Some(last_snapshot) => {
+ assert_snapshot!(last_snapshot);
+ }
+ None => {
+ panic!("test timed out before completing");
+ }
+ }
+}
diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs
index 7c4587f3e..924babf68 100644
--- a/src/tests/e2e/remote_runner.rs
+++ b/src/tests/e2e/remote_runner.rs
@@ -10,7 +10,7 @@ use std::net::TcpStream;
use std::path::Path;
-const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/debug/zellij";
+const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij";
const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts";
const CONNECTION_STRING: &str = "127.0.0.1:2222";
const CONNECTION_USERNAME: &str = "test";
@@ -24,7 +24,7 @@ fn ssh_connect() -> ssh2::Session {
sess.handshake().unwrap();
sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
.unwrap();
- sess.set_timeout(20000);
+ sess.set_timeout(3000);
sess
}
@@ -58,6 +58,27 @@ fn start_zellij(channel: &mut ssh2::Channel) {
channel.flush().unwrap();
}
+fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str) {
+ stop_zellij(channel);
+ channel
+ .write_all(
+ format!(
+ "{} --session {}\n",
+ ZELLIJ_EXECUTABLE_LOCATION, session_name
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+ channel.flush().unwrap();
+}
+
+fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) {
+ channel
+ .write_all(format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, session_name).as_bytes())
+ .unwrap();
+ channel.flush().unwrap();
+}
+
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
stop_zellij(channel);
channel
@@ -188,6 +209,9 @@ pub struct RemoteRunner {
win_size: Size,
layout_file_name: Option<&'static str>,
without_frames: bool,
+ session_name: Option<String>,
+ attach_to_existing: bool,
+ pub test_timed_out: bool,
}
impl RemoteRunner {
@@ -216,10 +240,89 @@ impl RemoteRunner {
test_name,
currently_running_step: None,
current_step_index: 0,
- retries_left: 3,
+ retries_left: 10,
+ win_size,
+ layout_file_name: None,
+ without_frames: false,
+ session_name: None,
+ attach_to_existing: false,
+ test_timed_out: false,
+ }
+ }
+ pub fn new_with_session_name(
+ test_name: &'static str,
+ win_size: Size,
+ session_name: &str,
+ ) -> 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);
+ cols.set_inner(win_size.cols);
+ let pane_geom = PaneGeom {
+ x: 0,
+ y: 0,
+ 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);
+ 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,
+ test_timed_out: false,
+ }
+ }
+ pub fn new_existing_session(
+ test_name: &'static str,
+ win_size: Size,
+ session_name: &str,
+ ) -> 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);
+ cols.set_inner(win_size.cols);
+ let pane_geom = PaneGeom {
+ x: 0,
+ y: 0,
+ 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);
+ 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,
+ test_timed_out: false,
}
}
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
@@ -247,10 +350,13 @@ impl RemoteRunner {
test_name,
currently_running_step: None,
current_step_index: 0,
- retries_left: 3,
+ retries_left: 10,
win_size,
layout_file_name: None,
without_frames: true,
+ session_name: None,
+ attach_to_existing: false,
+ test_timed_out: false,
}
}
pub fn new_with_layout(
@@ -283,10 +389,13 @@ impl RemoteRunner {
test_name,
currently_running_step: None,
current_step_index: 0,
- retries_left: 3,
+ retries_left: 10,
win_size,
layout_file_name: Some(layout_file_name),
without_frames: false,
+ session_name: None,
+ attach_to_existing: false,
+ test_timed_out: false,
}
}
pub fn add_step(mut self, step: Step) -> Self {
@@ -296,16 +405,6 @@ impl RemoteRunner {
pub fn replace_steps(&mut self, steps: Vec<Step>) {
self.steps = steps;
}
- 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);
@@ -341,6 +440,24 @@ impl RemoteRunner {
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
self.run_all_steps()
+ } 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));
+ self.run_all_steps()
} else {
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
@@ -349,22 +466,6 @@ impl RemoteRunner {
self.run_all_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 run_all_steps(&mut self) -> String {
// returns the last snapshot
loop {
@@ -381,23 +482,16 @@ impl RemoteRunner {
break;
}
}
- Err(e) => {
- if e.kind() == std::io::ErrorKind::TimedOut {
- if self.retries_left > 0 {
- return self.restart_test();
- }
- self.display_informative_error();
- panic!("Timed out waiting for test");
+ Err(_e) => {
+ if self.retries_left > 0 {
+ return self.restart_test();
}
- panic!("Error while reading remote session: {}", e);
+ self.test_timed_out = true;
}
}
}
take_snapshot(&mut self.terminal_output)
}
- pub fn get_current_snapshot(&mut self) -> String {
- take_snapshot(&mut self.terminal_output)
- }
}
impl Drop for RemoteRunner {
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
index d37f23e33..796a596b9 100644
--- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
@@ -5,7 +5,7 @@ expression: last_snapshot
---
Zellij
┌──────┐
-│$ Hi!█│
+│$ █ │
│ │
│ │
│ │
@@ -22,4 +22,4 @@ expression: last_snapshot
│ │
└──────┘
Ctrl +
-
+ ...
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap
new file mode 100644
index 000000000..3223680c8
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/e2e/cases.rs
+expression: last_snapshot
+
+---
+ Zellij (mirrored_sessions)  Tab #1 
+┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
+│$ ││$ █ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│