diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index aa8ad34b89..0198adfe06 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -75,8 +75,12 @@ pub fn starts_with_one_terminal() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("starts_with_one_terminal", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for app to load", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -86,8 +90,13 @@ pub fn starts_with_one_terminal() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -99,8 +108,11 @@ pub fn split_terminals_vertically() { rows: 24, }; - let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { name: "Split pane to the right", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -114,8 +126,9 @@ pub fn split_terminals_vertically() { } step_is_complete }, - }) - .add_step(Step { + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -125,8 +138,14 @@ pub fn split_terminals_vertically() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -134,49 +153,11 @@ pub fn split_terminals_vertically() { #[ignore] pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { let fake_win_size = Size { cols: 8, rows: 20 }; - let last_snapshot = RemoteRunner::new( - "cannot_split_terminals_vertically_when_active_terminal_is_too_small", - fake_win_size, - ) - .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: "Make sure only one pane appears", - instruction: |remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(3, 2) && remote_terminal.snapshot_contains("...") - { - // ... is the truncated tip line - step_is_complete = true; - } - step_is_complete - }, - }) - .run_all_steps(); - assert_snapshot!(last_snapshot); -} - -#[test] -#[ignore] -pub fn scrolling_inside_a_pane() { - let fake_win_size = Size { - cols: 120, - rows: 24, - }; - let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { name: "Split pane to the right", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -190,52 +171,109 @@ pub fn scrolling_inside_a_pane() { } step_is_complete }, - }) - .add_step(Step { - name: "Fill terminal with text", - instruction: |mut 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 - remote_terminal.send_key(format!("{:0<56}", "line1 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line2 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line3 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line4 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line5 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line6 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line7 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line8 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line9 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line10 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line11 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line12 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line13 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line14 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line15 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line16 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line17 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line18 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line19 ").as_bytes()); - remote_terminal.send_key(format!("{:0<57}", "line20 ").as_bytes()); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { - name: "Scroll up inside pane", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Make sure only one pane appears", + instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(118, 20) { - // all lines have been written to the pane - remote_terminal.send_key(&SCROLL_MODE); - remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE); + // if remote_terminal.cursor_position_is(3, 2) && remote_terminal.snapshot_contains("...") + if remote_terminal.cursor_position_is(3, 2) { + // ... is the truncated tip line step_is_complete = true; } step_is_complete }, - }) - .add_step(Step { + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} + +#[test] +#[ignore] +pub fn scrolling_inside_a_pane() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .retry_pause_ms(1000) // we need a longer retry period here because it takes some time to fill the pty buffer + .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: "Fill terminal with text", + instruction: |mut 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 + let mut content_to_send = String::new(); + content_to_send.push_str(&format!("{:0<56}", "line1 ")); + content_to_send.push_str(&format!("{:0<58}", "line2 ")); + content_to_send.push_str(&format!("{:0<58}", "line3 ")); + content_to_send.push_str(&format!("{:0<58}", "line4 ")); + content_to_send.push_str(&format!("{:0<58}", "line5 ")); + content_to_send.push_str(&format!("{:0<58}", "line6 ")); + content_to_send.push_str(&format!("{:0<58}", "line7 ")); + content_to_send.push_str(&format!("{:0<58}", "line8 ")); + content_to_send.push_str(&format!("{:0<58}", "line9 ")); + content_to_send.push_str(&format!("{:0<58}", "line10 ")); + content_to_send.push_str(&format!("{:0<58}", "line11 ")); + content_to_send.push_str(&format!("{:0<58}", "line12 ")); + content_to_send.push_str(&format!("{:0<58}", "line13 ")); + content_to_send.push_str(&format!("{:0<58}", "line14 ")); + content_to_send.push_str(&format!("{:0<58}", "line15 ")); + content_to_send.push_str(&format!("{:0<58}", "line16 ")); + content_to_send.push_str(&format!("{:0<58}", "line17 ")); + content_to_send.push_str(&format!("{:0<58}", "line18 ")); + content_to_send.push_str(&format!("{:0<58}", "line19 ")); + content_to_send.push_str(&format!("{:0<57}", "line20 ")); + + remote_terminal.send_key(content_to_send.as_bytes()); + + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Scroll up inside pane", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(118, 20) { + // all lines have been written to the pane + remote_terminal.send_key(&SCROLL_MODE); + remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for scroll to finish", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -247,8 +285,14 @@ pub fn scrolling_inside_a_pane() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -259,38 +303,44 @@ pub fn toggle_pane_fullscreen() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("toggle_pane_fullscreen", fake_win_size) - .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: "Change newly opened pane to be fullscreen", - instruction: |mut 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 - remote_terminal.send_key(&PANE_MODE); - remote_terminal.send_key(&TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE); - // back to normal mode after toggling fullscreen - remote_terminal.send_key(&ENTER); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Change newly opened pane to be fullscreen", + instruction: |mut 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 + remote_terminal.send_key(&PANE_MODE); + remote_terminal.send_key(&TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE); + // back to normal mode after toggling fullscreen + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for pane to become fullscreen", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -300,8 +350,14 @@ pub fn toggle_pane_fullscreen() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -312,38 +368,44 @@ pub fn open_new_tab() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("open_new_tab", fake_win_size) - .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: "Open new tab", - instruction: |mut 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 - remote_terminal.send_key(&TAB_MODE); - remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); - // back to normal mode after split - remote_terminal.send_key(&ENTER); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Open new tab", + instruction: |mut 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 + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); + // back to normal mode after split + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for new tab to open", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -357,50 +419,146 @@ pub fn open_new_tab() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } #[test] #[ignore] -pub fn close_pane() { +pub fn close_tab() { let fake_win_size = Size { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("close_pane", fake_win_size) - .add_step(Step { - name: "Split pane to the right", + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Open new tab", + instruction: |mut 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 + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); + // back to normal mode + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Close tab", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.tip_appears() + && remote_terminal.snapshot_contains("Tab #2") + && remote_terminal.status_bar_appears() + { + // cursor is in the newly opened second tab + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&CLOSE_TAB_IN_TAB_MODE); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for tab to close", 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) + if remote_terminal.cursor_position_is(3, 2) + && !remote_terminal.snapshot_contains("Tab #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); + // cursor is in the first tab again step_is_complete = true; } step_is_complete }, - }) - .add_step(Step { - name: "Close pane", - instruction: |mut 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 - remote_terminal.send_key(&PANE_MODE); - remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE); - // back to normal mode after close - remote_terminal.send_key(&ENTER); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} + +#[test] +#[ignore] +pub fn close_pane() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Close pane", + instruction: |mut 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 + remote_terminal.send_key(&PANE_MODE); + remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE); + // back to normal mode after close + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for pane to close", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -410,8 +568,14 @@ pub fn close_pane() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -422,8 +586,11 @@ pub fn exit_zellij() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("exit_zellij", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { name: "Wait for app to load", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -434,8 +601,9 @@ pub fn exit_zellij() { } step_is_complete }, - }) - .add_step(Step { + }); + runner.run_all_steps(); + break runner.take_snapshot_after(Step { name: "Wait for app to exit", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -446,8 +614,8 @@ pub fn exit_zellij() { } step_is_complete }, - }) - .run_all_steps(); + }); + }; assert!(last_snapshot.contains("Bye from Zellij!")); } @@ -458,8 +626,11 @@ pub fn closing_last_pane_exits_zellij() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("closing_last_pane_exits_zellij", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { name: "Close pane", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -471,8 +642,13 @@ pub fn closing_last_pane_exits_zellij() { } step_is_complete }, - }) - .add_step(Step { + }); + runner.run_all_steps(); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } + break runner.take_snapshot_after(Step { name: "Wait for app to exit", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -481,50 +657,122 @@ pub fn closing_last_pane_exits_zellij() { } step_is_complete }, - }) - .run_all_steps(); + }); + }; assert!(last_snapshot.contains("Bye from Zellij!")); } #[test] #[ignore] -pub fn resize_pane() { +pub fn typing_exit_closes_pane() { let fake_win_size = Size { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("resize_pane", fake_win_size) - .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: "Resize pane", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Type exit", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + remote_terminal.send_key("e".as_bytes()); + remote_terminal.send_key("x".as_bytes()); + remote_terminal.send_key("i".as_bytes()); + remote_terminal.send_key("t".as_bytes()); + remote_terminal.send_key("\n".as_bytes()); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for pane to close", + 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 - remote_terminal.send_key(&RESIZE_MODE); - remote_terminal.send_key(&RESIZE_LEFT_IN_RESIZE_MODE); - // back to normal mode after resizing - remote_terminal.send_key(&ENTER); + // if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() { + // cursor is in the original pane step_is_complete = true; } step_is_complete }, - }) - .add_step(Step { + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} + +#[test] +#[ignore] +pub fn resize_pane() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Resize pane", + instruction: |mut 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 + remote_terminal.send_key(&RESIZE_MODE); + remote_terminal.send_key(&RESIZE_LEFT_IN_RESIZE_MODE); + // back to normal mode after resizing + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for pane to be resized", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -534,8 +782,14 @@ pub fn resize_pane() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -546,33 +800,39 @@ pub fn lock_mode() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("lock_mode", fake_win_size) - .add_step(Step { - name: "Enter lock mode", - 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(&LOCK_MODE); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { - name: "Send keys that should not be intercepted by the app", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.snapshot_contains("INTERFACE LOCKED") { - remote_terminal.send_key(&TAB_MODE); - remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); - remote_terminal.send_key("abc".as_bytes()); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .add_step(Step { + name: "Enter lock mode", + 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(&LOCK_MODE); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Send keys that should not be intercepted by the app", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("INTERFACE LOCKED") { + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); + remote_terminal.send_key("abc".as_bytes()); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for terminal to render sent keys", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -582,8 +842,14 @@ pub fn lock_mode() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -595,35 +861,41 @@ pub fn resize_terminal_window() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("resize_terminal_window", fake_win_size) - .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: "Change terminal window size", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { - // new pane has been opened and focused - remote_terminal.change_size(100, 24); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Change terminal window size", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + // new pane has been opened and focused + remote_terminal.change_size(100, 24); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "wait for terminal to be resized and app to be re-rendered", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -633,8 +905,14 @@ pub fn resize_terminal_window() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -645,60 +923,66 @@ pub fn detach_and_attach_session() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("detach_and_attach_session", fake_win_size) - .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: "Send some text to the active pane", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { - // new pane has been opened and focused - remote_terminal.send_key("I am some text".as_bytes()); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { - name: "Detach session", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(77, 2) { - remote_terminal.send_key(&SESSION_MODE); - remote_terminal.send_key(&DETACH_IN_SESSION_MODE); - // text has been entered - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { - name: "Reattach session", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if !remote_terminal.status_bar_appears() { - // we don't see the toolbar, so we can assume we've already detached - remote_terminal.attach_to_original_session(); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Send some text to the active pane", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + // new pane has been opened and focused + remote_terminal.send_key("I am some text".as_bytes()); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Detach session", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(77, 2) { + remote_terminal.send_key(&SESSION_MODE); + remote_terminal.send_key(&DETACH_IN_SESSION_MODE); + // text has been entered + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Reattach session", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if !remote_terminal.status_bar_appears() { + // we don't see the toolbar, so we can assume we've already detached + remote_terminal.attach_to_original_session(); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for session to be attached", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -708,8 +992,14 @@ pub fn detach_and_attach_session() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -721,8 +1011,13 @@ pub fn accepts_basic_layout() { rows: 24, }; let layout_file_name = "three-panes-with-nesting.yaml"; - let last_snapshot = RemoteRunner::new_with_layout("accepts_basic_layout", fake_win_size, layout_file_name) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new_with_layout(fake_win_size, layout_file_name); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for app to load", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -733,8 +1028,14 @@ pub fn accepts_basic_layout() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -746,34 +1047,40 @@ fn focus_pane_with_mouse() { rows: 24, }; - let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size) - .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: "Click left pane", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { - remote_terminal.send_key(&normal_mouse_report(Position::new(5, 2), 0)); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .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: "Click left pane", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + remote_terminal.send_key(&normal_mouse_report(Position::new(5, 2), 0)); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for left pane to be focused", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -783,8 +1090,14 @@ fn focus_pane_with_mouse() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -795,66 +1108,73 @@ pub fn scrolling_inside_a_pane_with_mouse() { cols: 120, rows: 24, }; - let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane_with_mouse", fake_win_size) - .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: "Fill terminal with text", - instruction: |mut 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 - remote_terminal.send_key(format!("{:0<56}", "line1 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line2 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line3 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line4 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line5 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line6 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line7 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line8 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line9 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line10 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line11 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line12 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line13 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line14 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line15 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line16 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line17 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line18 ").as_bytes()); - remote_terminal.send_key(format!("{:0<58}", "line19 ").as_bytes()); - remote_terminal.send_key(format!("{:0<57}", "line20 ").as_bytes()); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { - name: "Scroll up inside pane", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - let mut step_is_complete = false; - if remote_terminal.cursor_position_is(118, 20) { - // all lines have been written to the pane - remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64)); - step_is_complete = true; - } - step_is_complete - }, - }) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size) + .retry_pause_ms(1000) // we need a longer retry period here because it takes some time to fill the pty buffer + .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: "Fill terminal with text", + instruction: |mut 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 + remote_terminal.send_key(format!("{:0<56}", "line1 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line2 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line3 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line4 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line5 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line6 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line7 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line8 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line9 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line10 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line11 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line12 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line13 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line14 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line15 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line16 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line17 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line18 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line19 ").as_bytes()); + remote_terminal.send_key(format!("{:0<57}", "line20 ").as_bytes()); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Scroll up inside pane", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(118, 20) { + // all lines have been written to the pane + remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64)); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for scroll to finish", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -866,8 +1186,14 @@ pub fn scrolling_inside_a_pane_with_mouse() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -879,8 +1205,11 @@ pub fn start_without_pane_frames() { rows: 24, }; - let last_snapshot = RemoteRunner::new_without_frames("no_pane_frames", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new_without_frames(fake_win_size).add_step(Step { name: "Split pane to the right", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -894,8 +1223,9 @@ pub fn start_without_pane_frames() { } step_is_complete }, - }) - .add_step(Step { + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for new pane to appear", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -905,8 +1235,14 @@ pub fn start_without_pane_frames() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } @@ -919,79 +1255,144 @@ pub fn mirrored_sessions() { }; 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) - .dont_panic() - .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 - }, - }); + let (first_runner_snapshot, second_runner_snapshot) = loop { + // here we connect with one runner, then connect with another, perform some actions and + // then make sure they were also reflected (mirrored) in the first runner afterwards + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut first_runner = RemoteRunner::new_with_session_name(fake_win_size, session_name) + .dont_panic() + .add_step(Step { + name: "Wait for app to load", + 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) + { + 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) - .dont_panic() - .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(); + let mut second_runner = RemoteRunner::new_existing_session(fake_win_size, session_name) + .dont_panic() + .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: "Open new tab (second user)", + instruction: |mut 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 + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&NEW_TAB_IN_TAB_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 tab to open", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.tip_appears() + && remote_terminal.snapshot_contains("Tab #2") + && remote_terminal.status_bar_appears() + { + // cursor is in the newly opened second tab + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Switch to previous tab", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.tip_appears() + && remote_terminal.snapshot_contains("Tab #2") + { + // cursor is in the newly opened second pane + remote_terminal.send_key("some text".as_bytes()); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Wait for text to appear on screen", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("some text") { + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&MOVE_FOCUS_LEFT_IN_PANE_MODE); // same key as tab mode + step_is_complete = true; + } + step_is_complete + }, + }); + second_runner.run_all_steps(); + + if first_runner.test_timed_out || second_runner.test_timed_out { + test_attempts -= 1; + continue; + } + let second_runner_snapshot = second_runner.take_snapshot_after(Step { + name: "take snapshot after", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.snapshot_contains("┐┌") + { + // cursor is back in the first tab + step_is_complete = true; + } + step_is_complete + }, + }); + let first_runner_snapshot = first_runner.take_snapshot_after(Step { + name: "take snapshot after", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 2) + && remote_terminal.snapshot_contains("┐┌") + { + // cursor is back in the first tab + step_is_complete = true; + } + step_is_complete + }, + }); 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); + break (first_runner_snapshot, second_runner_snapshot); } - None => { - panic!("test timed out before completing"); - } - } + }; + assert_snapshot!(first_runner_snapshot); + assert_snapshot!(second_runner_snapshot); } #[test] @@ -1005,8 +1406,11 @@ pub fn bracketed_paste() { // we make sure the text in bracketed paste mode is sent directly to the terminal and not // interpreted by us (in this case it will send ^T to the terminal), then we exit bracketed // paste, send some more text and make sure it's also sent to the terminal - let last_snapshot = RemoteRunner::new("bracketed_paste", fake_win_size) - .add_step(Step { + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + drop(()); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { name: "Send pasted text followed by normal text", instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -1021,8 +1425,10 @@ pub fn bracketed_paste() { } step_is_complete }, - }) - .add_step(Step { + }); + runner.run_all_steps(); + + let last_snapshot = runner.take_snapshot_after(Step { name: "Wait for terminal to render sent keys", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; @@ -1032,7 +1438,13 @@ pub fn bracketed_paste() { } step_is_complete }, - }) - .run_all_steps(); + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; assert_snapshot!(last_snapshot); } diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index d5d7a3e3b8..46e2b88f15 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -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>, + last_snapshot: &Arc>, + cursor_coordinates: &Arc>, + pane_geom: &PaneGeom, +) -> (Arc>, 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>, cursor_x: usize, cursor_y: usize, - current_snapshot: String, + last_snapshot: Arc>, } -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, current_step_index: usize, - vte_parser: vte::Parser, - terminal_output: TerminalPane, - channel: ssh2::Channel, - test_name: &'static str, + channel: Arc>, currently_running_step: Option, retries_left: usize, - win_size: Size, - layout_file_name: Option<&'static str>, - without_frames: bool, - session_name: Option, - attach_to_existing: bool, + retry_pause_ms: usize, panic_on_no_retries_left: bool, + last_snapshot: Arc>, + cursor_coordinates: Arc>, // x, y + reader_thread: (Arc>, 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) { - 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(), - ) + pub fn take_snapshot_after(&mut self, step: Step) -> String { + let mut retries_left = RETRIES; + let instruction = step.instruction; + loop { + 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(), }; - 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)); + 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; + } } - self.run_all_steps() } - pub fn run_all_steps(&mut self) -> String { - // returns the last snapshot + pub fn run_all_steps(&mut self) { 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); - } - self.run_next_step(); - if !self.steps_left() { - break; - } - } - Err(e) => { - if self.retries_left > 0 { - return self.restart_test(); - } - self.test_timed_out = true; - if self.panic_on_no_retries_left { - self.display_informative_error(); - panic!("timed out waiting for test: {:?}", e); - } - } + self.run_next_step(); + if !self.steps_left() { + break; + } else if self.retries_left == 0 { + self.test_timed_out = true; + 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; } } 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 796a596b9f..ed01a3b93a 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 @@ -22,4 +22,4 @@ expression: last_snapshot │ │ └──────┘ Ctrl + - ... + diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap index bbb5b068ac..bef865a19f 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap @@ -1,29 +1,29 @@ --- -source: src/tests/integration/e2e.rs +source: src/tests/e2e/cases.rs expression: last_snapshot --- - Zellij  Tab #1  - -$ │$ █ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - - Ctrl + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  - Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ █ ││$ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + <←↓↑→> Move focus / New / Close / Rename / Sync / Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap new file mode 100644 index 0000000000..881e628948 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + <←↓↑→> Move focus / New / Close / Rename / Sync / Toggle / Select pane 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 index 1d975fafce..7dbfbbd1e5 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane. + <←↓↑→> Move focus / New / Close / Rename / Sync / Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap new file mode 100644 index 0000000000..e583c90b01 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot + +--- + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ █ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Tip: Alt + => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index d3af961ae1..c2c9d4237c 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -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(); } diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 0839841785..933dd9adef 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -120,7 +120,7 @@ pub(crate) struct Tab { pub name: String, panes: BTreeMap>, pub panes_to_hide: HashSet, - active_terminal: Option, + active_panes: HashMap, max_panes: Option, viewport: Viewport, // includes all non-UI panes display_area: Size, // includes all panes (including eg. the status bar and tab bar in the default layout) @@ -320,7 +320,7 @@ impl Tab { name, max_panes, panes_to_hide: HashSet::new(), - active_terminal: None, + active_panes: HashMap::new(), viewport: display_area.into(), display_area, fullscreen_is_active: false, @@ -428,18 +428,52 @@ impl Tab { } self.set_pane_frames(self.draw_pane_frames); // This is the end of the nasty viewport hack... - // FIXME: Active / new / current terminal, should be pane - self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); + let next_selectable_pane_id = self + .panes + .iter() + .filter(|(_id, pane)| pane.selectable()) + .map(|(id, _)| id.to_owned()) + .next(); + match next_selectable_pane_id { + Some(active_pane_id) => { + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, active_pane_id); + } + } + None => { + // this is very likely a configuration error (layout with no selectable panes) + self.active_panes.clear(); + } + } } pub fn add_client(&mut self, client_id: ClientId) { - self.connected_clients.insert(client_id); + match self.connected_clients.iter().next() { + Some(first_client_id) => { + let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap(); + self.connected_clients.insert(client_id); + self.active_panes.insert(client_id, first_active_pane_id); + } + None => { + let mut pane_ids: Vec = self.panes.keys().copied().collect(); + if pane_ids.is_empty() { + // no panes here, bye bye + return; + } + pane_ids.sort(); // TODO: make this predictable + let first_pane_id = pane_ids.get(0).unwrap(); + self.connected_clients.insert(client_id); + self.active_panes.insert(client_id, *first_pane_id); + } + } // TODO: we might be able to avoid this, we do this so that newly connected clients will // necessarily get a full render self.set_force_render(); } pub fn add_multiple_clients(&mut self, client_ids: &[ClientId]) { for client_id in client_ids { - self.connected_clients.insert(*client_id); + self.add_client(*client_id); } } pub fn remove_client(&mut self, client_id: ClientId) { @@ -448,10 +482,10 @@ impl Tab { pub fn drain_connected_clients(&mut self) -> Vec { self.connected_clients.drain().collect() } - pub fn new_pane(&mut self, pid: PaneId) { + pub fn new_pane(&mut self, pid: PaneId, client_id: Option) { self.close_down_to_max_terminals(); if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); + self.unset_fullscreen(); } // TODO: check minimum size of active terminal @@ -518,16 +552,23 @@ impl Tab { } } } - self.active_terminal = Some(pid); + if client_id.is_some() { + // right now we administratively change focus of all clients until the + // mirroring/multiplayer situation is sorted out + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, pid); + } + } } - pub fn horizontal_split(&mut self, pid: PaneId) { + pub fn horizontal_split(&mut self, pid: PaneId, client_id: ClientId) { self.close_down_to_max_terminals(); if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); + self.toggle_active_pane_fullscreen(client_id); } if let PaneId::Terminal(term_pid) = pid { let next_terminal_position = self.get_next_terminal_position(); - let active_pane_id = &self.get_active_pane_id().unwrap(); + let active_pane_id = &self.get_active_pane_id(client_id).unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 { self.senders @@ -546,20 +587,28 @@ impl Tab { ); active_pane.set_geom(top_winsize); self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); + + // right now we administratively change focus of all clients until the + // mirroring/multiplayer situation is sorted out + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, pid); + } + self.relayout_tab(Direction::Vertical); } } } - pub fn vertical_split(&mut self, pid: PaneId) { + pub fn vertical_split(&mut self, pid: PaneId, client_id: ClientId) { self.close_down_to_max_terminals(); if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); + self.toggle_active_pane_fullscreen(client_id); } if let PaneId::Terminal(term_pid) = pid { // TODO: check minimum size of active terminal let next_terminal_position = self.get_next_terminal_position(); - let active_pane_id = &self.get_active_pane_id().unwrap(); + let active_pane_id = &self.get_active_pane_id(client_id).unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.cols() < MIN_TERMINAL_WIDTH * 2 { self.senders @@ -574,19 +623,33 @@ impl Tab { active_pane.set_geom(left_winsize); self.panes.insert(pid, Box::new(new_terminal)); } - self.active_terminal = Some(pid); + + // right now we administratively change focus of all clients until the + // mirroring/multiplayer situation is sorted out + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, pid); + } + self.relayout_tab(Direction::Horizontal); } } - pub fn get_active_pane(&self) -> Option<&dyn Pane> { - self.get_active_pane_id() + pub fn has_active_panes(&self) -> bool { + // a tab without active panes is a dead tab and should close + // a pane can be active even if there are no connected clients, + // we remember that pane for one the client focuses the tab next + !self.active_panes.is_empty() + } + pub fn get_active_pane(&self, client_id: ClientId) -> Option<&dyn Pane> { + self.get_active_pane_id(client_id) .and_then(|ap| self.panes.get(&ap).map(Box::as_ref)) } - fn get_active_pane_id(&self) -> Option { - self.active_terminal + fn get_active_pane_id(&self, client_id: ClientId) -> Option { + // TODO: why do we need this? + self.active_panes.get(&client_id).copied() } - fn get_active_terminal_id(&self) -> Option { - if let Some(PaneId::Terminal(pid)) = self.active_terminal { + fn get_active_terminal_id(&self, client_id: ClientId) -> Option { + if let Some(PaneId::Terminal(pid)) = self.active_panes.get(&client_id).copied() { Some(pid) } else { None @@ -640,8 +703,8 @@ impl Tab { self.write_to_pane_id(input_bytes.clone(), pane_id); }); } - pub fn write_to_active_terminal(&mut self, input_bytes: Vec) { - self.write_to_pane_id(input_bytes, self.get_active_pane_id().unwrap()); + pub fn write_to_active_terminal(&mut self, input_bytes: Vec, client_id: ClientId) { + self.write_to_pane_id(input_bytes, self.get_active_pane_id(client_id).unwrap()); } pub fn write_to_pane_id(&mut self, input_bytes: Vec, pane_id: PaneId) { match pane_id { @@ -664,9 +727,12 @@ impl Tab { } } } - pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> { + pub fn get_active_terminal_cursor_position( + &self, + client_id: ClientId, + ) -> Option<(usize, usize)> { // (x, y) - let active_terminal = &self.get_active_pane()?; + let active_terminal = &self.get_active_pane(client_id)?; active_terminal .cursor_coordinates() .map(|(x_in_terminal, y_in_terminal)| { @@ -675,26 +741,36 @@ impl Tab { (x, y) }) } - pub fn toggle_active_pane_fullscreen(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { + pub fn unset_fullscreen(&mut self) { + if self.fullscreen_is_active { + let first_client_id = self.connected_clients.iter().next().unwrap(); // this is a temporary hack until we fix the ui for multiple clients + let active_pane_id = self.active_panes.get(first_client_id).unwrap(); + for terminal_id in self.panes_to_hide.iter() { + let pane = self.panes.get_mut(terminal_id).unwrap(); + pane.set_should_render(true); + pane.set_should_render_boundaries(true); + } + let viewport_pane_ids: Vec<_> = self + .get_pane_ids() + .into_iter() + .filter(|id| !self.is_inside_viewport(id)) + .collect(); + for pid in viewport_pane_ids { + let viewport_pane = self.panes.get_mut(&pid).unwrap(); + viewport_pane.reset_size_and_position_override(); + } + self.panes_to_hide.clear(); + let active_terminal = self.panes.get_mut(active_pane_id).unwrap(); + active_terminal.reset_size_and_position_override(); + self.set_force_render(); + self.resize_whole_tab(self.display_area); + self.toggle_fullscreen_is_active(); + } + } + pub fn toggle_active_pane_fullscreen(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.fullscreen_is_active { - for terminal_id in self.panes_to_hide.iter() { - let pane = self.panes.get_mut(terminal_id).unwrap(); - pane.set_should_render(true); - pane.set_should_render_boundaries(true); - } - let viewport_pane_ids: Vec<_> = self - .get_pane_ids() - .into_iter() - .filter(|id| !self.is_inside_viewport(id)) - .collect(); - for pid in viewport_pane_ids { - let viewport_pane = self.panes.get_mut(&pid).unwrap(); - viewport_pane.reset_size_and_position_override(); - } - self.panes_to_hide.clear(); - let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); - active_terminal.reset_size_and_position_override(); + self.unset_fullscreen(); } else { let panes = self.get_panes(); let pane_ids_to_hide = panes.filter_map(|(&id, _pane)| { @@ -729,10 +805,10 @@ impl Tab { }; active_terminal.get_geom_override(full_screen_geom); } + self.set_force_render(); + self.resize_whole_tab(self.display_area); + self.toggle_fullscreen_is_active(); } - self.set_force_render(); - self.resize_whole_tab(self.display_area); - self.toggle_fullscreen_is_active(); } } pub fn is_fullscreen_active(&self) -> bool { @@ -754,12 +830,12 @@ impl Tab { pub fn toggle_sync_panes_is_active(&mut self) { self.synchronize_is_active = !self.synchronize_is_active; } - pub fn mark_active_pane_for_rerender(&mut self) { - if let Some(active_terminal) = self - .active_terminal - .and_then(|active_terminal_id| self.panes.get_mut(&active_terminal_id)) - { - active_terminal.set_should_render(true) + pub fn mark_active_pane_for_rerender(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { + self.panes + .get_mut(&active_pane_id) + .unwrap() + .set_should_render(true) } } pub fn set_pane_frames(&mut self, draw_pane_frames: bool) { @@ -797,14 +873,14 @@ impl Tab { } } pub fn render(&mut self, output: &mut Output) { - if self.connected_clients.is_empty() || self.active_terminal.is_none() { + if self.connected_clients.is_empty() || self.active_panes.is_empty() { return; } for connected_client in self.connected_clients.iter() { // TODO: move this out of the render function self.senders .send_to_pty(PtyInstruction::UpdateActivePane( - self.active_terminal, + self.active_panes.get(connected_client).copied(), *connected_client, )) .unwrap(); @@ -818,9 +894,10 @@ impl Tab { output.push_str_to_all_clients(clear_display); self.should_clear_display_before_rendering = false; } + let first_client_id = self.connected_clients.iter().next().unwrap(); // this is a temporary hack until we fix the ui for multiple clients for (_kind, pane) in self.panes.iter_mut() { if !self.panes_to_hide.contains(&pane.pid()) { - match self.active_terminal.unwrap() == pane.pid() { + match self.active_panes.get(first_client_id).copied().unwrap() == pane.pid() { true => { pane.set_active_at(Instant::now()); match self.mode_info.mode { @@ -862,10 +939,13 @@ impl Tab { output.push_str_to_all_clients(&boundaries.vte_output()); } - match self.get_active_terminal_cursor_position() { + match self.get_active_terminal_cursor_position(*first_client_id) { Some((cursor_position_x, cursor_position_y)) => { let show_cursor = "\u{1b}[?25h"; - let change_cursor_shape = self.get_active_pane().unwrap().cursor_shape_csi(); + let change_cursor_shape = self + .get_active_pane(*first_client_id) + .unwrap() + .cursor_shape_csi(); let goto_cursor_position = &format!( "\u{1b}[{};{}H\u{1b}[m{}", cursor_position_y + 1, @@ -2098,9 +2178,9 @@ impl Tab { self.should_clear_display_before_rendering = true; self.set_pane_frames(self.draw_pane_frames); } - pub fn resize_left(&mut self) { + pub fn resize_left(&mut self, client_id: ClientId) { // TODO: find out by how much we actually reduced and only reduce by that much - if let Some(active_pane_id) = self.get_active_pane_id() { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.can_increase_pane_and_surroundings_left(&active_pane_id, RESIZE_PERCENT) { self.increase_pane_and_surroundings_left(&active_pane_id, RESIZE_PERCENT); } else if self.can_reduce_pane_and_surroundings_left(&active_pane_id, RESIZE_PERCENT) { @@ -2109,9 +2189,9 @@ impl Tab { } self.relayout_tab(Direction::Horizontal); } - pub fn resize_right(&mut self) { + pub fn resize_right(&mut self, client_id: ClientId) { // TODO: find out by how much we actually reduced and only reduce by that much - if let Some(active_pane_id) = self.get_active_pane_id() { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.can_increase_pane_and_surroundings_right(&active_pane_id, RESIZE_PERCENT) { self.increase_pane_and_surroundings_right(&active_pane_id, RESIZE_PERCENT); } else if self.can_reduce_pane_and_surroundings_right(&active_pane_id, RESIZE_PERCENT) { @@ -2120,9 +2200,9 @@ impl Tab { } self.relayout_tab(Direction::Horizontal); } - pub fn resize_down(&mut self) { + pub fn resize_down(&mut self, client_id: ClientId) { // TODO: find out by how much we actually reduced and only reduce by that much - if let Some(active_pane_id) = self.get_active_pane_id() { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.can_increase_pane_and_surroundings_down(&active_pane_id, RESIZE_PERCENT) { self.increase_pane_and_surroundings_down(&active_pane_id, RESIZE_PERCENT); } else if self.can_reduce_pane_and_surroundings_down(&active_pane_id, RESIZE_PERCENT) { @@ -2131,9 +2211,9 @@ impl Tab { } self.relayout_tab(Direction::Vertical); } - pub fn resize_up(&mut self) { + pub fn resize_up(&mut self, client_id: ClientId) { // TODO: find out by how much we actually reduced and only reduce by that much - if let Some(active_pane_id) = self.get_active_pane_id() { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.can_increase_pane_and_surroundings_up(&active_pane_id, RESIZE_PERCENT) { self.increase_pane_and_surroundings_up(&active_pane_id, RESIZE_PERCENT); } else if self.can_reduce_pane_and_surroundings_up(&active_pane_id, RESIZE_PERCENT) { @@ -2142,8 +2222,8 @@ impl Tab { } self.relayout_tab(Direction::Vertical); } - pub fn resize_increase(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { + pub fn resize_increase(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.try_increase_pane_and_surroundings_right_and_down(&active_pane_id) { return; } @@ -2169,8 +2249,8 @@ impl Tab { self.try_increase_pane_and_surroundings_up(&active_pane_id, RESIZE_PERCENT); } } - pub fn resize_decrease(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { + pub fn resize_decrease(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if self.try_reduce_pane_and_surroundings_left_and_up(&active_pane_id) { return; } @@ -2196,34 +2276,38 @@ impl Tab { } } - pub fn move_focus(&mut self) { + pub fn move_focus(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_terminal_id = self.get_active_pane_id().unwrap(); - let terminal_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations - let active_terminal_id_position = terminal_ids + let current_active_pane_id = self.get_active_pane_id(client_id).unwrap(); + let pane_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations + let active_pane_id_position = pane_ids .iter() - .position(|id| id == &active_terminal_id) + .position(|id| id == ¤t_active_pane_id) + .unwrap(); + let next_active_pane_id = pane_ids + .get(active_pane_id_position + 1) + .or_else(|| pane_ids.get(0)) + .copied() .unwrap(); - let active_terminal = terminal_ids - .get(active_terminal_id_position + 1) - .or_else(|| terminal_ids.get(0)) - .copied(); - self.active_terminal = active_terminal; + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, next_active_pane_id); + } } - pub fn focus_next_pane(&mut self) { + pub fn focus_next_pane(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_pane_id = self.get_active_pane_id().unwrap(); + let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let mut panes: Vec<(&PaneId, &Box)> = self.get_selectable_panes().collect(); panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { if a_pane.y() == b_pane.y() { @@ -2237,21 +2321,25 @@ impl Tab { .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - let active_terminal = panes + let next_active_pane_id = panes .get(active_pane_position + 1) .or_else(|| panes.get(0)) - .map(|p| *p.0); + .map(|p| *p.0) + .unwrap(); - self.active_terminal = active_terminal; + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, next_active_pane_id); + } } - pub fn focus_previous_pane(&mut self) { + pub fn focus_previous_pane(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_pane_id = self.get_active_pane_id().unwrap(); + let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let mut panes: Vec<(&PaneId, &Box)> = self.get_selectable_panes().collect(); panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { if a_pane.y() == b_pane.y() { @@ -2266,23 +2354,26 @@ impl Tab { .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - let active_terminal = if active_pane_position == 0 { - Some(*last_pane.0) + let next_active_pane_id = if active_pane_position == 0 { + *last_pane.0 } else { - Some(*panes.get(active_pane_position - 1).unwrap().0) + *panes.get(active_pane_position - 1).unwrap().0 }; - self.active_terminal = active_terminal; + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, next_active_pane_id); + } } // returns a boolean that indicates whether the focus moved - pub fn move_focus_left(&mut self) -> bool { + pub fn move_focus_left(&mut self, client_id: ClientId) -> bool { if !self.has_selectable_panes() { return false; } if self.fullscreen_is_active { return false; } - let active_terminal = self.get_active_pane(); - let updated_active_terminal = if let Some(active) = active_terminal { + let active_pane = self.get_active_pane(client_id); + let updated_active_pane = if let Some(active) = active_pane { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2295,34 +2386,53 @@ impl Tab { Some(&p) => { // render previously active pane so that its frame does not remain actively // colored - let previously_active_pane = - self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let previously_active_pane = self + .panes + .get_mut(self.active_panes.get(&client_id).unwrap()) + .unwrap(); previously_active_pane.set_should_render(true); let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, p); + } return true; } None => Some(active.pid()), } } else { - Some(active_terminal.unwrap().pid()) + Some(active_pane.unwrap().pid()) }; - self.active_terminal = updated_active_terminal; + match updated_active_pane { + Some(updated_active_pane) => { + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, updated_active_pane); + } + } + None => { + // TODO: can this happen? + self.active_panes.clear(); + } + } + false } - pub fn move_focus_down(&mut self) { + pub fn move_focus_down(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_terminal = self.get_active_pane(); - let updated_active_terminal = if let Some(active) = active_terminal { - let terminals = self.get_selectable_panes(); - let next_index = terminals + let active_pane = self.get_active_pane(client_id); + let updated_active_pane = if let Some(active) = active_pane { + let panes = self.get_selectable_panes(); + let next_index = panes .enumerate() .filter(|(_, (_, c))| { c.is_directly_below(active) && c.vertically_overlaps_with(active) @@ -2333,8 +2443,10 @@ impl Tab { Some(&p) => { // render previously active pane so that its frame does not remain actively // colored - let previously_active_pane = - self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let previously_active_pane = self + .panes + .get_mut(self.active_panes.get(&client_id).unwrap()) + .unwrap(); previously_active_pane.set_should_render(true); let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); @@ -2344,19 +2456,31 @@ impl Tab { None => Some(active.pid()), } } else { - Some(active_terminal.unwrap().pid()) + Some(active_pane.unwrap().pid()) }; - self.active_terminal = updated_active_terminal; + match updated_active_pane { + Some(updated_active_pane) => { + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, updated_active_pane); + } + } + None => { + // TODO: can this happen? + self.active_panes.clear(); + } + } } - pub fn move_focus_up(&mut self) { + pub fn move_focus_up(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_terminal = self.get_active_pane(); - let updated_active_terminal = if let Some(active) = active_terminal { + let active_pane = self.get_active_pane(client_id); + let updated_active_pane = if let Some(active) = active_pane { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2369,8 +2493,10 @@ impl Tab { Some(&p) => { // render previously active pane so that its frame does not remain actively // colored - let previously_active_pane = - self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let previously_active_pane = self + .panes + .get_mut(self.active_panes.get(&client_id).unwrap()) + .unwrap(); previously_active_pane.set_should_render(true); let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); @@ -2380,20 +2506,32 @@ impl Tab { None => Some(active.pid()), } } else { - Some(active_terminal.unwrap().pid()) + Some(active_pane.unwrap().pid()) }; - self.active_terminal = updated_active_terminal; + match updated_active_pane { + Some(updated_active_pane) => { + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, updated_active_pane); + } + } + None => { + // TODO: can this happen? + self.active_panes.clear(); + } + } } // returns a boolean that indicates whether the focus moved - pub fn move_focus_right(&mut self) -> bool { + pub fn move_focus_right(&mut self, client_id: ClientId) -> bool { if !self.has_selectable_panes() { return false; } if self.fullscreen_is_active { return false; } - let active_terminal = self.get_active_pane(); - let updated_active_terminal = if let Some(active) = active_terminal { + let active_pane = self.get_active_pane(client_id); + let updated_active_pane = if let Some(active) = active_pane { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2406,31 +2544,49 @@ impl Tab { Some(&p) => { // render previously active pane so that its frame does not remain actively // colored - let previously_active_pane = - self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let previously_active_pane = self + .panes + .get_mut(self.active_panes.get(&client_id).unwrap()) + .unwrap(); previously_active_pane.set_should_render(true); let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, p); + } return true; } None => Some(active.pid()), } } else { - Some(active_terminal.unwrap().pid()) + Some(active_pane.unwrap().pid()) }; - self.active_terminal = updated_active_terminal; + match updated_active_pane { + Some(updated_active_pane) => { + let connected_clients: Vec = + self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, updated_active_pane); + } + } + None => { + // TODO: can this happen? + self.active_panes.clear(); + } + } false } - pub fn move_active_pane(&mut self) { + pub fn move_active_pane(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - let active_pane_id = self.get_active_pane_id().unwrap(); + let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let mut panes: Vec<(&PaneId, &Box)> = self.get_selectable_panes().collect(); panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { if a_pane.y() == b_pane.y() { @@ -2473,14 +2629,14 @@ impl Tab { current_position.set_should_render(true); } } - pub fn move_active_pane_down(&mut self) { + pub fn move_active_pane_down(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - if let Some(active) = self.get_active_pane() { + if let Some(active) = self.get_active_pane(client_id) { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2490,7 +2646,8 @@ impl Tab { .max_by_key(|(_, (_, c))| c.active_at()) .map(|(_, (pid, _))| pid); if let Some(&p) = next_index { - let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap(); + let active_pane_id = self.active_panes.get(&client_id).unwrap(); + let current_position = self.panes.get(active_pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -2504,7 +2661,7 @@ impl Tab { resize_pty!(new_position, self.os_api); new_position.set_should_render(true); - let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let current_position = self.panes.get_mut(active_pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.get_geom_override(geom); @@ -2514,14 +2671,14 @@ impl Tab { } } } - pub fn move_active_pane_up(&mut self) { + pub fn move_active_pane_up(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - if let Some(active) = self.get_active_pane() { + if let Some(active) = self.get_active_pane(client_id) { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2531,7 +2688,8 @@ impl Tab { .max_by_key(|(_, (_, c))| c.active_at()) .map(|(_, (pid, _))| pid); if let Some(&p) = next_index { - let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap(); + let active_pane_id = self.active_panes.get(&client_id).unwrap(); + let current_position = self.panes.get(active_pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -2545,7 +2703,7 @@ impl Tab { resize_pty!(new_position, self.os_api); new_position.set_should_render(true); - let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let current_position = self.panes.get_mut(active_pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.get_geom_override(geom); @@ -2555,14 +2713,14 @@ impl Tab { } } } - pub fn move_active_pane_right(&mut self) { + pub fn move_active_pane_right(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - if let Some(active) = self.get_active_pane() { + if let Some(active) = self.get_active_pane(client_id) { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2572,7 +2730,8 @@ impl Tab { .max_by_key(|(_, (_, c))| c.active_at()) .map(|(_, (pid, _))| pid); if let Some(&p) = next_index { - let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap(); + let active_pane_id = self.active_panes.get(&client_id).unwrap(); + let current_position = self.panes.get(active_pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -2586,7 +2745,7 @@ impl Tab { resize_pty!(new_position, self.os_api); new_position.set_should_render(true); - let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let current_position = self.panes.get_mut(active_pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.get_geom_override(geom); @@ -2596,14 +2755,14 @@ impl Tab { } } } - pub fn move_active_pane_left(&mut self) { + pub fn move_active_pane_left(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } - if let Some(active) = self.get_active_pane() { + if let Some(active) = self.get_active_pane(client_id) { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -2613,7 +2772,8 @@ impl Tab { .max_by_key(|(_, (_, c))| c.active_at()) .map(|(_, (pid, _))| pid); if let Some(&p) = next_index { - let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap(); + let active_pane_id = self.active_panes.get(&client_id).unwrap(); + let current_position = self.panes.get(active_pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -2627,7 +2787,7 @@ impl Tab { resize_pty!(new_position, self.os_api); new_position.set_should_render(true); - let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + let current_position = self.panes.get_mut(active_pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.get_geom_override(geom); @@ -2793,14 +2953,40 @@ impl Tab { pub fn set_pane_selectable(&mut self, id: PaneId, selectable: bool) { if let Some(pane) = self.panes.get_mut(&id) { pane.set_selectable(selectable); - if self.get_active_pane_id() == Some(id) && !selectable { - self.active_terminal = self.next_active_pane(&self.get_pane_ids()); + if !selectable { + // there are some edge cases in which this causes a hard crash when there are no + // other selectable panes - ideally this should never happen unless it's a + // configuration error - but this *does* sometimes happen with the default + // configuration as well since we set this at run time. I left this here because + // this should very rarely happen and I hope in my heart that we will stop setting + // this at runtime in the default configuration at some point + // + // If however this is not the case and we find this does cause crashes, we can + // solve it by adding a "dangling_clients" struct to Tab which we would fill with + // the relevant client ids in this case and drain as soon as a new selectable pane + // is opened + self.move_clients_out_of_pane(id); + } + } + } + fn move_clients_out_of_pane(&mut self, pane_id: PaneId) { + let active_panes: Vec<(ClientId, PaneId)> = self + .active_panes + .iter() + .map(|(cid, pid)| (*cid, *pid)) + .collect(); + for (client_id, active_pane_id) in active_panes { + if active_pane_id == pane_id { + self.active_panes.insert( + client_id, + self.next_active_pane(&self.get_pane_ids()).unwrap(), + ); } } } pub fn close_pane(&mut self, id: PaneId) { if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); + self.unset_fullscreen(); } if let Some(pane_to_close) = self.panes.get(&id) { let freed_space = pane_to_close.position_and_size(); @@ -2812,10 +2998,7 @@ impl Tab { self.increase_pane_width(pane_id, freed_width); } self.panes.remove(&id); - if self.active_terminal == Some(id) { - let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; - } + self.move_clients_out_of_pane(id); self.relayout_tab(Direction::Horizontal); return; } @@ -2824,10 +3007,7 @@ impl Tab { self.increase_pane_width(pane_id, freed_width); } self.panes.remove(&id); - if self.active_terminal == Some(id) { - let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; - } + self.move_clients_out_of_pane(id); self.relayout_tab(Direction::Horizontal); return; } @@ -2836,10 +3016,7 @@ impl Tab { self.increase_pane_height(pane_id, freed_height); } self.panes.remove(&id); - if self.active_terminal == Some(id) { - let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; - } + self.move_clients_out_of_pane(id); self.relayout_tab(Direction::Vertical); return; } @@ -2848,10 +3025,7 @@ impl Tab { self.increase_pane_height(pane_id, freed_height); } self.panes.remove(&id); - if self.active_terminal == Some(id) { - let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; - } + self.move_clients_out_of_pane(id); self.relayout_tab(Direction::Vertical); return; } @@ -2859,20 +3033,20 @@ impl Tab { // if we reached here, this is either the last pane or there's some sort of // configuration error (eg. we're trying to close a pane surrounded by fixed panes) self.panes.remove(&id); - self.active_terminal = None; + self.active_panes.clear(); self.resize_whole_tab(self.display_area); } } - pub fn close_focused_pane(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { + pub fn close_focused_pane(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { self.close_pane(active_pane_id); self.senders .send_to_pty(PtyInstruction::ClosePane(active_pane_id)) .unwrap(); } } - pub fn scroll_active_terminal_up(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_up(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2880,8 +3054,8 @@ impl Tab { active_terminal.scroll_up(1); } } - pub fn scroll_active_terminal_down(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_down(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2892,8 +3066,8 @@ impl Tab { } } } - pub fn scroll_active_terminal_up_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_up_page(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2903,8 +3077,8 @@ impl Tab { active_terminal.scroll_up(scroll_rows); } } - pub fn scroll_active_terminal_down_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_down_page(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2917,8 +3091,8 @@ impl Tab { } } } - pub fn scroll_active_terminal_up_half_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_up_half_page(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2928,8 +3102,8 @@ impl Tab { active_terminal.scroll_up(scroll_rows); } } - pub fn scroll_active_terminal_down_half_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_down_half_page(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2942,8 +3116,8 @@ impl Tab { } } } - pub fn scroll_active_terminal_to_bottom(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn scroll_active_terminal_to_bottom(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2954,8 +3128,8 @@ impl Tab { } } } - pub fn clear_active_terminal_scroll(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { + pub fn clear_active_terminal_scroll(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { let active_terminal = self .panes .get_mut(&PaneId::Terminal(active_terminal_id)) @@ -2995,7 +3169,8 @@ impl Tab { fn get_pane_id_at(&self, point: &Position, search_selectable: bool) -> Option { if self.fullscreen_is_active { - return self.get_active_pane_id(); + let first_client_id = self.connected_clients.iter().next().unwrap(); // this is a temporary hack until we fix the ui for multiple clients + return self.get_active_pane_id(*first_client_id); } if search_selectable { self.get_selectable_panes() @@ -3007,29 +3182,32 @@ impl Tab { .map(|(&id, _)| id) } } - pub fn handle_left_click(&mut self, position: &Position) { - self.focus_pane_at(position); + pub fn handle_left_click(&mut self, position: &Position, client_id: ClientId) { + self.focus_pane_at(position, client_id); if let Some(pane) = self.get_pane_at(position, false) { let relative_position = pane.relative_position(position); pane.start_selection(&relative_position); }; } - pub fn handle_right_click(&mut self, position: &Position) { - self.focus_pane_at(position); + pub fn handle_right_click(&mut self, position: &Position, client_id: ClientId) { + self.focus_pane_at(position, client_id); if let Some(pane) = self.get_pane_at(position, false) { let relative_position = pane.relative_position(position); pane.handle_right_click(&relative_position); }; } - fn focus_pane_at(&mut self, point: &Position) { + fn focus_pane_at(&mut self, point: &Position, _client_id: ClientId) { if let Some(clicked_pane) = self.get_pane_id_at(point, true) { - self.active_terminal = Some(clicked_pane); + let connected_clients: Vec = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, clicked_pane); + } } } - pub fn handle_mouse_release(&mut self, position: &Position) { - let active_pane_id = self.get_active_pane_id(); + pub fn handle_mouse_release(&mut self, position: &Position, client_id: ClientId) { + let active_pane_id = self.get_active_pane_id(client_id); // on release, get the selected text from the active pane, and reset it's selection let mut selected_text = None; if active_pane_id != self.get_pane_id_at(position, true) { @@ -3051,8 +3229,8 @@ impl Tab { self.write_selection_to_clipboard(&selected_text); } } - pub fn handle_mouse_hold(&mut self, position_on_screen: &Position) { - if let Some(active_pane_id) = self.get_active_pane_id() { + pub fn handle_mouse_hold(&mut self, position_on_screen: &Position, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { if let Some(active_pane) = self.panes.get_mut(&active_pane_id) { let relative_position = active_pane.relative_position(position_on_screen); active_pane.update_selection(&relative_position); @@ -3060,8 +3238,10 @@ impl Tab { } } - pub fn copy_selection(&self) { - let selected_text = self.get_active_pane().and_then(|p| p.get_selected_text()); + pub fn copy_selection(&self, client_id: ClientId) { + let selected_text = self + .get_active_pane(client_id) + .and_then(|p| p.get_selected_text()); if let Some(selected_text) = selected_text { self.write_selection_to_clipboard(&selected_text); self.senders diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index ffb87f8dbd..f2fd692356 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -118,7 +118,7 @@ fn split_panes_vertically() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); + tab.vertical_split(new_pane_id, 1); assert_eq!(tab.panes.len(), 2, "The tab has two panes"); assert_eq!( tab.panes @@ -206,7 +206,7 @@ fn split_panes_horizontally() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); + tab.horizontal_split(new_pane_id, 1); assert_eq!(tab.panes.len(), 2, "The tab has two panes"); assert_eq!( @@ -297,7 +297,7 @@ fn split_largest_pane() { let mut tab = create_new_tab(size); for i in 2..5 { let new_pane_id = PaneId::Terminal(i); - tab.new_pane(new_pane_id); + tab.new_pane(new_pane_id, Some(1)); } assert_eq!(tab.panes.len(), 4, "The tab has four panes"); @@ -462,7 +462,7 @@ fn split_largest_pane() { pub fn cannot_split_panes_vertically_when_active_pane_is_too_small() { let size = Size { cols: 8, rows: 20 }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); + tab.vertical_split(PaneId::Terminal(2), 1); assert_eq!(tab.panes.len(), 1, "Tab still has only one pane"); } @@ -470,7 +470,7 @@ pub fn cannot_split_panes_vertically_when_active_pane_is_too_small() { pub fn cannot_split_panes_horizontally_when_active_pane_is_too_small() { let size = Size { cols: 121, rows: 4 }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); + tab.horizontal_split(PaneId::Terminal(2), 1); assert_eq!(tab.panes.len(), 1, "Tab still has only one pane"); } @@ -478,7 +478,7 @@ pub fn cannot_split_panes_horizontally_when_active_pane_is_too_small() { pub fn cannot_split_largest_pane_when_there_is_no_room() { let size = Size { cols: 8, rows: 4 }; let mut tab = create_new_tab(size); - tab.new_pane(PaneId::Terminal(2)); + tab.new_pane(PaneId::Terminal(2), Some(1)); assert_eq!(tab.panes.len(), 1, "Tab still has only one pane"); } @@ -491,9 +491,9 @@ pub fn toggle_focused_pane_fullscreen() { let mut tab = create_new_tab(size); for i in 2..5 { let new_pane_id = PaneId::Terminal(i); - tab.new_pane(new_pane_id); + tab.new_pane(new_pane_id, Some(1)); } - tab.toggle_active_pane_fullscreen(); + tab.toggle_active_pane_fullscreen(1); assert_eq!( tab.panes.get(&PaneId::Terminal(4)).unwrap().x(), 0, @@ -514,7 +514,7 @@ pub fn toggle_focused_pane_fullscreen() { 20, "Pane rows match fullscreen rows" ); - tab.toggle_active_pane_fullscreen(); + tab.toggle_active_pane_fullscreen(1); assert_eq!( tab.panes.get(&PaneId::Terminal(4)).unwrap().x(), 61, @@ -548,10 +548,10 @@ pub fn move_focus_is_disabled_in_fullscreen() { let mut tab = create_new_tab(size); for i in 2..5 { let new_pane_id = PaneId::Terminal(i); - tab.new_pane(new_pane_id); + tab.new_pane(new_pane_id, Some(1)); } - tab.toggle_active_pane_fullscreen(); - tab.move_focus_left(); + tab.toggle_active_pane_fullscreen(1); + tab.move_focus_left(1); assert_eq!( tab.panes.get(&PaneId::Terminal(4)).unwrap().x(), 0, @@ -591,8 +591,8 @@ pub fn close_pane_with_another_pane_above_it() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id, 1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 1, "One pane left in tab"); assert_eq!( @@ -652,9 +652,9 @@ pub fn close_pane_with_another_pane_below_it() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_focus_up(); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id, 1); + tab.move_focus_up(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 1, "One pane left in tab"); assert_eq!( @@ -711,8 +711,8 @@ pub fn close_pane_with_another_pane_to_the_left() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id, 1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 1, "One pane left in tab"); assert_eq!( @@ -769,9 +769,9 @@ pub fn close_pane_with_another_pane_to_the_right() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.move_focus_left(); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id, 1); + tab.move_focus_left(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 1, "One pane left in tab"); assert_eq!( @@ -831,11 +831,11 @@ pub fn close_pane_with_multiple_panes_above_it() { let mut tab = create_new_tab(size); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.horizontal_split(new_pane_id_1); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_2); - tab.move_focus_down(); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id_1, 1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_2, 1); + tab.move_focus_down(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 2, "Two panes left in tab"); assert_eq!( @@ -934,10 +934,10 @@ pub fn close_pane_with_multiple_panes_below_it() { let mut tab = create_new_tab(size); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.horizontal_split(new_pane_id_1); - tab.vertical_split(new_pane_id_2); - tab.move_focus_up(); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id_1, 1); + tab.vertical_split(new_pane_id_2, 1); + tab.move_focus_up(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 2, "Two panes left in tab"); assert_eq!( @@ -1036,11 +1036,11 @@ pub fn close_pane_with_multiple_panes_to_the_left() { let mut tab = create_new_tab(size); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.vertical_split(new_pane_id_1); - tab.move_focus_left(); - tab.horizontal_split(new_pane_id_2); - tab.move_focus_right(); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id_1, 1); + tab.move_focus_left(1); + tab.horizontal_split(new_pane_id_2, 1); + tab.move_focus_right(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 2, "Two panes left in tab"); assert_eq!( @@ -1139,10 +1139,10 @@ pub fn close_pane_with_multiple_panes_to_the_right() { let mut tab = create_new_tab(size); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.vertical_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.move_focus_left(); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.move_focus_left(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 2, "Two panes left in tab"); assert_eq!( @@ -1246,21 +1246,21 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { let new_pane_id_5 = PaneId::Terminal(6); let new_pane_id_6 = PaneId::Terminal(7); - tab.vertical_split(new_pane_id_1); - tab.vertical_split(new_pane_id_2); - tab.move_focus_left(); - tab.move_focus_left(); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_right(); - tab.horizontal_split(new_pane_id_4); - tab.move_focus_right(); - tab.horizontal_split(new_pane_id_5); - tab.move_focus_left(); - tab.move_focus_up(); - tab.resize_down(); - tab.vertical_split(new_pane_id_6); - tab.move_focus_down(); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id_1, 1); + tab.vertical_split(new_pane_id_2, 1); + tab.move_focus_left(1); + tab.move_focus_left(1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_right(1); + tab.horizontal_split(new_pane_id_4, 1); + tab.move_focus_right(1); + tab.horizontal_split(new_pane_id_5, 1); + tab.move_focus_left(1); + tab.move_focus_up(1); + tab.resize_down(1); + tab.vertical_split(new_pane_id_6, 1); + tab.move_focus_down(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 6, "Six panes left in tab"); @@ -1522,20 +1522,20 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { let new_pane_id_5 = PaneId::Terminal(6); let new_pane_id_6 = PaneId::Terminal(7); - tab.vertical_split(new_pane_id_1); - tab.vertical_split(new_pane_id_2); - tab.move_focus_left(); - tab.move_focus_left(); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_right(); - tab.horizontal_split(new_pane_id_4); - tab.move_focus_right(); - tab.horizontal_split(new_pane_id_5); - tab.move_focus_left(); - tab.resize_up(); - tab.vertical_split(new_pane_id_6); - tab.move_focus_up(); - tab.close_focused_pane(); + tab.vertical_split(new_pane_id_1, 1); + tab.vertical_split(new_pane_id_2, 1); + tab.move_focus_left(1); + tab.move_focus_left(1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_right(1); + tab.horizontal_split(new_pane_id_4, 1); + tab.move_focus_right(1); + tab.horizontal_split(new_pane_id_5, 1); + tab.move_focus_left(1); + tab.resize_up(1); + tab.vertical_split(new_pane_id_6, 1); + tab.move_focus_up(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 6, "Six panes left in tab"); @@ -1799,23 +1799,23 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { let new_pane_id_5 = PaneId::Terminal(6); let new_pane_id_6 = PaneId::Terminal(7); - tab.horizontal_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_3); - tab.move_focus_down(); - tab.vertical_split(new_pane_id_4); - tab.move_focus_down(); - tab.vertical_split(new_pane_id_5); - tab.move_focus_up(); - tab.move_focus_left(); - tab.resize_right(); - tab.resize_up(); - tab.resize_up(); - tab.horizontal_split(new_pane_id_6); - tab.move_focus_right(); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_down(1); + tab.vertical_split(new_pane_id_4, 1); + tab.move_focus_down(1); + tab.vertical_split(new_pane_id_5, 1); + tab.move_focus_up(1); + tab.move_focus_left(1); + tab.resize_right(1); + tab.resize_up(1); + tab.resize_up(1); + tab.horizontal_split(new_pane_id_6, 1); + tab.move_focus_right(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 6, "Six panes left in tab"); @@ -2079,22 +2079,22 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { let new_pane_id_5 = PaneId::Terminal(6); let new_pane_id_6 = PaneId::Terminal(7); - tab.horizontal_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_3); - tab.move_focus_down(); - tab.vertical_split(new_pane_id_4); - tab.move_focus_down(); - tab.vertical_split(new_pane_id_5); - tab.move_focus_up(); - tab.resize_left(); - tab.resize_up(); - tab.resize_up(); - tab.horizontal_split(new_pane_id_6); - tab.move_focus_left(); - tab.close_focused_pane(); + tab.horizontal_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_down(1); + tab.vertical_split(new_pane_id_4, 1); + tab.move_focus_down(1); + tab.vertical_split(new_pane_id_5, 1); + tab.move_focus_up(1); + tab.resize_left(1); + tab.resize_up(1); + tab.resize_up(1); + tab.horizontal_split(new_pane_id_6, 1); + tab.move_focus_left(1); + tab.close_focused_pane(1); assert_eq!(tab.panes.len(), 6, "Six panes left in tab"); @@ -2342,12 +2342,12 @@ pub fn move_focus_down() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_focus_up(); - tab.move_focus_down(); + tab.horizontal_split(new_pane_id, 1); + tab.move_focus_up(1); + tab.move_focus_down(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 10, "Active pane is the bottom one" ); @@ -2364,19 +2364,19 @@ pub fn move_focus_down_to_the_most_recently_used_pane() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.horizontal_split(new_pane_id_1); - tab.vertical_split(new_pane_id_2); - tab.vertical_split(new_pane_id_3); - tab.move_focus_up(); - tab.move_focus_down(); + tab.horizontal_split(new_pane_id_1, 1); + tab.vertical_split(new_pane_id_2, 1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_up(1); + tab.move_focus_down(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 10, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 91, "Active pane x position" ); @@ -2391,11 +2391,11 @@ pub fn move_focus_up() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_focus_up(); + tab.horizontal_split(new_pane_id, 1); + tab.move_focus_up(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 0, "Active pane is the top one" ); @@ -2412,20 +2412,20 @@ pub fn move_focus_up_to_the_most_recently_used_pane() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.horizontal_split(new_pane_id_1); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_2); - tab.vertical_split(new_pane_id_3); - tab.move_focus_down(); - tab.move_focus_up(); + tab.horizontal_split(new_pane_id_1, 1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_2, 1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_down(1); + tab.move_focus_up(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 0, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 91, "Active pane x position" ); @@ -2440,11 +2440,11 @@ pub fn move_focus_left() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.move_focus_left(); + tab.vertical_split(new_pane_id, 1); + tab.move_focus_left(1); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 0, "Active pane is the left one" ); @@ -2461,20 +2461,20 @@ pub fn move_focus_left_to_the_most_recently_used_pane() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.vertical_split(new_pane_id_1); - tab.move_focus_left(); - tab.horizontal_split(new_pane_id_2); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_right(); - tab.move_focus_left(); + tab.vertical_split(new_pane_id_1, 1); + tab.move_focus_left(1); + tab.horizontal_split(new_pane_id_2, 1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_right(1); + tab.move_focus_left(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 15, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 0, "Active pane x position" ); @@ -2489,12 +2489,12 @@ pub fn move_focus_right() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.move_focus_left(); - tab.move_focus_right(); + tab.vertical_split(new_pane_id, 1); + tab.move_focus_left(1); + tab.move_focus_right(1); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 61, "Active pane is the right one" ); @@ -2511,19 +2511,19 @@ pub fn move_focus_right_to_the_most_recently_used_pane() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.vertical_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_left(); - tab.move_focus_right(); + tab.vertical_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_left(1); + tab.move_focus_right(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 15, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 61, "Active pane x position" ); @@ -2538,17 +2538,17 @@ pub fn move_active_pane_down() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_focus_up(); - tab.move_active_pane_down(); + tab.horizontal_split(new_pane_id, 1); + tab.move_focus_up(1); + tab.move_active_pane_down(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 10, "Active pane is the bottom one" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(1), "Active pane is the bottom one" ); @@ -2565,24 +2565,24 @@ pub fn move_active_pane_down_to_the_most_recently_used_position() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.horizontal_split(new_pane_id_1); - tab.vertical_split(new_pane_id_2); - tab.vertical_split(new_pane_id_3); - tab.move_focus_up(); - tab.move_active_pane_down(); + tab.horizontal_split(new_pane_id_1, 1); + tab.vertical_split(new_pane_id_2, 1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_up(1); + tab.move_active_pane_down(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 10, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 91, "Active pane x position" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(1), "Active pane PaneId" ); @@ -2597,16 +2597,16 @@ pub fn move_active_pane_up() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_active_pane_up(); + tab.horizontal_split(new_pane_id, 1); + tab.move_active_pane_up(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 0, "Active pane is the top one" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(2), "Active pane is the top one" ); @@ -2623,26 +2623,26 @@ pub fn move_active_pane_up_to_the_most_recently_used_position() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.horizontal_split(new_pane_id_1); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_2); - tab.vertical_split(new_pane_id_3); - tab.move_focus_down(); - tab.move_active_pane_up(); + tab.horizontal_split(new_pane_id_1, 1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_2, 1); + tab.vertical_split(new_pane_id_3, 1); + tab.move_focus_down(1); + tab.move_active_pane_up(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 0, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 91, "Active pane x position" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(2), "Active pane PaneId" ); @@ -2657,16 +2657,16 @@ pub fn move_active_pane_left() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.move_active_pane_left(); + tab.vertical_split(new_pane_id, 1); + tab.move_active_pane_left(1); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 0, "Active pane is the left one" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(2), "Active pane is the left one" ); @@ -2683,26 +2683,26 @@ pub fn move_active_pane_left_to_the_most_recently_used_position() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.vertical_split(new_pane_id_1); - tab.move_focus_left(); - tab.horizontal_split(new_pane_id_2); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_right(); - tab.move_active_pane_left(); + tab.vertical_split(new_pane_id_1, 1); + tab.move_focus_left(1); + tab.horizontal_split(new_pane_id_2, 1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_right(1); + tab.move_active_pane_left(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 15, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 0, "Active pane x position" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(2), "Active pane PaneId" ); @@ -2717,17 +2717,17 @@ pub fn move_active_pane_right() { let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.vertical_split(new_pane_id); - tab.move_focus_left(); - tab.move_active_pane_right(); + tab.vertical_split(new_pane_id, 1); + tab.move_focus_left(1); + tab.move_active_pane_right(1); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 61, "Active pane is the right one" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(1), "Active pane is the right one" ); @@ -2744,24 +2744,24 @@ pub fn move_active_pane_right_to_the_most_recently_used_position() { let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); - tab.vertical_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.horizontal_split(new_pane_id_3); - tab.move_focus_left(); - tab.move_active_pane_right(); + tab.vertical_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.horizontal_split(new_pane_id_3, 1); + tab.move_focus_left(1); + tab.move_active_pane_right(1); assert_eq!( - tab.get_active_pane().unwrap().y(), + tab.get_active_pane(1).unwrap().y(), 15, "Active pane y position" ); assert_eq!( - tab.get_active_pane().unwrap().x(), + tab.get_active_pane(1).unwrap().x(), 61, "Active pane x position" ); assert_eq!( - tab.get_active_pane().unwrap().pid(), + tab.get_active_pane(1).unwrap().pid(), PaneId::Terminal(1), "Active pane Paneid" ); @@ -2784,8 +2784,8 @@ pub fn resize_down_with_pane_above() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.resize_down(); + tab.horizontal_split(new_pane_id, 1); + tab.resize_down(1); assert_eq!( tab.panes.get(&new_pane_id).unwrap().position_and_size().x, @@ -2874,9 +2874,9 @@ pub fn resize_down_with_pane_below() { }; let mut tab = create_new_tab(size); let new_pane_id = PaneId::Terminal(2); - tab.horizontal_split(new_pane_id); - tab.move_focus_up(); - tab.resize_down(); + tab.horizontal_split(new_pane_id, 1); + tab.move_focus_up(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&new_pane_id).unwrap().position_and_size().x, @@ -2971,10 +2971,10 @@ pub fn resize_down_with_panes_above_and_below() { let first_pane_id = PaneId::Terminal(1); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.horizontal_split(new_pane_id_1); - tab.horizontal_split(new_pane_id_2); - tab.move_focus_up(); - tab.resize_down(); + tab.horizontal_split(new_pane_id_1, 1); + tab.horizontal_split(new_pane_id_2, 1); + tab.move_focus_up(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&new_pane_id_1).unwrap().position_and_size().x, @@ -3088,11 +3088,11 @@ pub fn resize_down_with_multiple_panes_above() { let first_pane_id = PaneId::Terminal(1); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); - tab.horizontal_split(new_pane_id_1); - tab.move_focus_up(); - tab.vertical_split(new_pane_id_2); - tab.move_focus_down(); - tab.resize_down(); + tab.horizontal_split(new_pane_id_1, 1); + tab.move_focus_up(1); + tab.vertical_split(new_pane_id_2, 1); + tab.move_focus_down(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&new_pane_id_1).unwrap().position_and_size().x, @@ -3207,12 +3207,12 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { let pane_to_the_left = PaneId::Terminal(2); let focused_pane = PaneId::Terminal(3); let pane_above = PaneId::Terminal(4); - tab.horizontal_split(pane_to_the_left); - tab.vertical_split(focused_pane); - tab.move_focus_up(); - tab.vertical_split(pane_above); - tab.move_focus_down(); - tab.resize_down(); + tab.horizontal_split(pane_to_the_left, 1); + tab.vertical_split(focused_pane, 1); + tab.move_focus_up(1); + tab.vertical_split(pane_above, 1); + tab.move_focus_down(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().x, @@ -3375,11 +3375,11 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { let pane_below_and_left = PaneId::Terminal(2); let pane_below = PaneId::Terminal(3); let focused_pane = PaneId::Terminal(4); - tab.horizontal_split(pane_below_and_left); - tab.vertical_split(pane_below); - tab.move_focus_up(); - tab.vertical_split(focused_pane); - tab.resize_down(); + tab.horizontal_split(pane_below_and_left, 1); + tab.vertical_split(pane_below, 1); + tab.move_focus_up(1); + tab.vertical_split(focused_pane, 1); + tab.resize_down(1); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().x, @@ -3542,13 +3542,13 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { let focused_pane = PaneId::Terminal(2); let pane_to_the_right = PaneId::Terminal(3); let pane_above_and_right = PaneId::Terminal(4); - tab.horizontal_split(focused_pane); - tab.vertical_split(pane_to_the_right); - tab.move_focus_up(); - tab.vertical_split(pane_above_and_right); - tab.move_focus_down(); - tab.move_focus_left(); - tab.resize_down(); + tab.horizontal_split(focused_pane, 1); + tab.vertical_split(pane_to_the_right, 1); + tab.move_focus_up(1); + tab.vertical_split(pane_above_and_right, 1); + tab.move_focus_down(1); + tab.move_focus_left(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().x, @@ -3711,12 +3711,12 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { let pane_below = PaneId::Terminal(2); let pane_below_and_right = PaneId::Terminal(3); let pane_to_the_right = PaneId::Terminal(4); - tab.horizontal_split(pane_below); - tab.vertical_split(pane_below_and_right); - tab.move_focus_up(); - tab.vertical_split(pane_to_the_right); - tab.move_focus_left(); - tab.resize_down(); + tab.horizontal_split(pane_below, 1); + tab.vertical_split(pane_below_and_right, 1); + tab.move_focus_up(1); + tab.vertical_split(pane_to_the_right, 1); + tab.move_focus_left(1); + tab.resize_down(1); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().x, @@ -3875,15 +3875,15 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.move_focus_down(); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.move_focus_down(1); + tab.resize_down(1); assert_eq!( tab.panes @@ -4136,14 +4136,14 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.resize_down(1); assert_eq!( tab.panes @@ -4396,18 +4396,18 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.vertical_split(PaneId::Terminal(7)); - tab.vertical_split(PaneId::Terminal(8)); - tab.move_focus_left(); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.vertical_split(PaneId::Terminal(7), 1); + tab.vertical_split(PaneId::Terminal(8), 1); + tab.move_focus_left(1); + tab.resize_down(1); assert_eq!( tab.panes @@ -4738,20 +4738,20 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_left(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(7)); - tab.vertical_split(PaneId::Terminal(8)); - tab.move_focus_left(); - tab.move_focus_up(); - tab.move_focus_left(); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_left(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(7), 1); + tab.vertical_split(PaneId::Terminal(8), 1); + tab.move_focus_left(1); + tab.move_focus_up(1); + tab.move_focus_left(1); + tab.resize_down(1); assert_eq!( tab.panes @@ -5080,9 +5080,9 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { rows: 10, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.resize_down(1); assert_eq!( tab.panes @@ -5120,8 +5120,8 @@ pub fn resize_left_with_pane_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.resize_left(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5215,9 +5215,9 @@ pub fn resize_left_with_pane_to_the_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.resize_left(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5312,10 +5312,10 @@ pub fn resize_left_with_panes_to_the_left_and_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_left(); - tab.resize_left(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_left(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5448,11 +5448,11 @@ pub fn resize_left_with_multiple_panes_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.resize_left(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5586,12 +5586,12 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_down(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_down(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5764,13 +5764,13 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_down(); - tab.move_focus_left(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_down(1); + tab.move_focus_left(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -5943,11 +5943,11 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(4)); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.resize_left(1); assert_eq!( tab.panes @@ -6120,12 +6120,12 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_left(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_left(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -6300,15 +6300,15 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_down(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_down(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -6561,16 +6561,16 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_down(); - tab.move_focus_left(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_down(1); + tab.move_focus_left(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -6823,19 +6823,19 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov rows: 70, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_down(); - tab.resize_down(); - tab.vertical_split(PaneId::Terminal(6)); - tab.horizontal_split(PaneId::Terminal(7)); - tab.horizontal_split(PaneId::Terminal(8)); - tab.move_focus_up(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_down(1); + tab.resize_down(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.horizontal_split(PaneId::Terminal(7), 1); + tab.horizontal_split(PaneId::Terminal(8), 1); + tab.move_focus_up(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -7166,20 +7166,20 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo rows: 70, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_down(); - tab.resize_down(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(7)); - tab.horizontal_split(PaneId::Terminal(8)); - tab.move_focus_up(); - tab.resize_left(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_down(1); + tab.resize_down(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(7), 1); + tab.horizontal_split(PaneId::Terminal(8), 1); + tab.move_focus_up(1); + tab.resize_left(1); assert_eq!( tab.panes @@ -7505,8 +7505,8 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { let size = Size { cols: 10, rows: 20 }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.resize_left(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.resize_left(1); assert_eq!( tab.panes @@ -7544,8 +7544,8 @@ pub fn resize_right_with_pane_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.resize_right(1); assert_eq!( tab.panes @@ -7640,9 +7640,9 @@ pub fn resize_right_with_pane_to_the_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -7737,10 +7737,10 @@ pub fn resize_right_with_panes_to_the_left_and_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_left(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_left(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -7874,11 +7874,11 @@ pub fn resize_right_with_multiple_panes_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8012,12 +8012,12 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.horizontal_split(PaneId::Terminal(4)); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.horizontal_split(PaneId::Terminal(4), 1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8189,13 +8189,13 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.horizontal_split(PaneId::Terminal(4)); - tab.move_focus_left(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.horizontal_split(PaneId::Terminal(4), 1); + tab.move_focus_left(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8368,13 +8368,13 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.horizontal_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.horizontal_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8547,14 +8547,14 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.horizontal_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.move_focus_left(); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.horizontal_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.move_focus_left(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8729,15 +8729,15 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_down(); - tab.resize_right(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_down(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -8989,16 +8989,16 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_down(); - tab.move_focus_left(); - tab.resize_right(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_down(1); + tab.move_focus_left(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -9250,19 +9250,19 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo rows: 70, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_down(); - tab.resize_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.horizontal_split(PaneId::Terminal(7)); - tab.horizontal_split(PaneId::Terminal(8)); - tab.move_focus_up(); - tab.resize_right(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_down(1); + tab.resize_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.horizontal_split(PaneId::Terminal(7), 1); + tab.horizontal_split(PaneId::Terminal(8), 1); + tab.move_focus_up(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -9592,20 +9592,20 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab rows: 70, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.move_focus_down(); - tab.resize_up(); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(7)); - tab.horizontal_split(PaneId::Terminal(8)); - tab.move_focus_up(); - tab.resize_right(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.move_focus_down(1); + tab.resize_up(1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(7), 1); + tab.horizontal_split(PaneId::Terminal(8), 1); + tab.move_focus_up(1); + tab.resize_right(1); assert_eq!( tab.panes @@ -9930,8 +9930,8 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { // █ == focused pane let size = Size { cols: 10, rows: 20 }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.resize_right(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.resize_right(1); assert_eq!( tab.panes @@ -9970,8 +9970,8 @@ pub fn resize_up_with_pane_above() { rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10067,9 +10067,9 @@ pub fn resize_up_with_pane_below() { rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10168,10 +10168,10 @@ pub fn resize_up_with_panes_above_and_below() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_up(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_up(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10305,11 +10305,11 @@ pub fn resize_up_with_multiple_panes_above() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_down(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_down(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10442,12 +10442,12 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(4)); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10621,13 +10621,13 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10801,13 +10801,13 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_left(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_left(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -10981,14 +10981,14 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_left(); - tab.move_focus_up(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_left(1); + tab.move_focus_up(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -11162,14 +11162,14 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -11421,15 +11421,15 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.move_focus_up(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.move_focus_up(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -11681,18 +11681,18 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_left(); - tab.vertical_split(PaneId::Terminal(7)); - tab.vertical_split(PaneId::Terminal(8)); - tab.move_focus_left(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_left(1); + tab.vertical_split(PaneId::Terminal(7), 1); + tab.vertical_split(PaneId::Terminal(8), 1); + tab.move_focus_left(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -12022,19 +12022,19 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.move_focus_up(); - tab.vertical_split(PaneId::Terminal(3)); - tab.vertical_split(PaneId::Terminal(4)); - tab.move_focus_down(); - tab.vertical_split(PaneId::Terminal(5)); - tab.vertical_split(PaneId::Terminal(6)); - tab.move_focus_up(); - tab.move_focus_left(); - tab.vertical_split(PaneId::Terminal(7)); - tab.vertical_split(PaneId::Terminal(8)); - tab.move_focus_left(); - tab.resize_up(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.move_focus_up(1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.vertical_split(PaneId::Terminal(4), 1); + tab.move_focus_down(1); + tab.vertical_split(PaneId::Terminal(5), 1); + tab.vertical_split(PaneId::Terminal(6), 1); + tab.move_focus_up(1); + tab.move_focus_left(1); + tab.vertical_split(PaneId::Terminal(7), 1); + tab.vertical_split(PaneId::Terminal(8), 1); + tab.move_focus_left(1); + tab.resize_up(1); assert_eq!( tab.panes @@ -12363,8 +12363,8 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { rows: 10, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2)); - tab.resize_down(); + tab.horizontal_split(PaneId::Terminal(2), 1); + tab.resize_down(1); assert_eq!( tab.panes @@ -12395,16 +12395,16 @@ pub fn nondirectional_resize_increase_with_1_pane() { rows: 10, }; let mut tab = create_new_tab(size); - tab.resize_increase(); + tab.resize_increase(1); assert_eq!( - tab.get_active_pane().unwrap().position_and_size().y, + tab.get_active_pane(1).unwrap().position_and_size().y, 0, "There is only 1 pane so both coordinates should be 0" ); assert_eq!( - tab.get_active_pane().unwrap().position_and_size().x, + tab.get_active_pane(1).unwrap().position_and_size().x, 0, "There is only 1 pane so both coordinates should be 0" ); @@ -12418,8 +12418,8 @@ pub fn nondirectional_resize_increase_with_1_pane_to_left() { }; let mut tab = create_new_tab(size); let new_pane_id_1 = PaneId::Terminal(2); - tab.vertical_split(new_pane_id_1); - tab.resize_increase(); + tab.vertical_split(new_pane_id_1, 1); + tab.resize_increase(1); // should behave like `resize_left_with_pane_to_the_left` assert_eq!( @@ -12449,11 +12449,11 @@ pub fn nondirectional_resize_increase_with_2_panes_to_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.move_focus_right(); - tab.resize_increase(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.move_focus_right(1); + tab.resize_increase(1); // should behave like `resize_left_with_multiple_panes_to_the_left` assert_eq!( @@ -12503,10 +12503,10 @@ pub fn nondirectional_resize_increase_with_1_pane_to_right_1_pane_above() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.move_focus_left(); - tab.horizontal_split(PaneId::Terminal(3)); - tab.resize_increase(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.move_focus_left(1); + tab.horizontal_split(PaneId::Terminal(3), 1); + tab.resize_increase(1); assert_eq!( tab.panes @@ -12555,10 +12555,10 @@ pub fn nondirectional_resize_increase_with_1_pane_to_right_1_pane_to_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_left(); - tab.resize_increase(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_left(1); + tab.resize_increase(1); assert_eq!( tab.panes @@ -12607,10 +12607,10 @@ pub fn nondirectional_resize_increase_with_pane_above_aligned_right_with_current rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2)); - tab.vertical_split(PaneId::Terminal(3)); - tab.move_focus_left(); - tab.resize_increase(); + tab.vertical_split(PaneId::Terminal(2), 1); + tab.vertical_split(PaneId::Terminal(3), 1); + tab.move_focus_left(1); + tab.resize_increase(1); assert_eq!( tab.panes diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 74923d4178..5e1b159d7d 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -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},