summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCosmin Popescu <cosminadrianpopescu@gmail.com>2022-06-06 09:20:07 +0200
committerGitHub <noreply@github.com>2022-06-06 09:20:07 +0200
commite1fcf3a6dbc7a65a341127fab2fca7b4fb2d081b (patch)
treeec1061b57347348f081d64c532f7fc12ff21c4dd
parent58cc8fb2e1a091dca72fdbaf9150b200e130fa73 (diff)
feat(scroll): edit scrollback with default editor (#1456)
* initial commit for opening the current buffer in an editor * fix(editor): take hidden panes into consideration when manipulating tiled grid * when closing an edit buffer, take the geometry of the replaced buffer from the closed buffer * if the floating panels are displayed, don't add to hidden panels the current buffer * strategy changing - put the panels inside a suppressed_panels HashMap instead of hidden_panels * Revert "strategy changing - put the panels inside a suppressed_panels HashMap instead of hidden_panels" This reverts commit c52a203a20cf4c87c147be8b9c193ed6458c1038. * remove the floating panes by moving them to the tiled_panes in hidden_panels * feat(edit): open editor to correct line and don't crash when none is set * formatting * feat(edit): use suppressed panes * style(fmt): rustfmt and logs * style(fmt): clean up unused code * test(editor): integration test for suppressing/closing suppressed pane * test(e2e): editor e2e test * style(fmt): rustfmt * feat(edit): update ui and setup * style(fmt): rustfmt * feat(config): allow configuring scrollback_editor explicitly * style(fmt): rustfmt * chore(repo): build after merging Co-authored-by: Aram Drevekenin <aram@poor.dev>
-rw-r--r--Cargo.lock11
-rw-r--r--docs/MANPAGE.md1
-rw-r--r--src/tests/e2e/cases.rs49
-rw-r--r--src/tests/e2e/remote_runner.rs37
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap4
-rw-r--r--zellij-server/Cargo.toml1
-rw-r--r--zellij-server/src/lib.rs1
-rw-r--r--zellij-server/src/os_input_output.rs69
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs60
-rw-r--r--zellij-server/src/panes/grid.rs3
-rw-r--r--zellij-server/src/panes/terminal_pane.rs4
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs100
-rw-r--r--zellij-server/src/panes/tiled_panes/tiled_pane_grid.rs7
-rw-r--r--zellij-server/src/pty.rs55
-rw-r--r--zellij-server/src/route.rs6
-rw-r--r--zellij-server/src/screen.rs34
-rw-r--r--zellij-server/src/tab/mod.rs134
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__close_suppressing_floating_pane.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__close_suppressing_tiled_pane.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_floting_pane_is_suppressed.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_tiled_pane_is_suppressed.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane_embed_it_and_close_it.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane.snap26
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane_float_it_and_close.snap26
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs213
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs3
-rw-r--r--zellij-server/src/unit/screen_tests.rs3
-rw-r--r--zellij-server/src/wasm_vm.rs2
-rw-r--r--zellij-utils/assets/config/default.yaml5
-rw-r--r--zellij-utils/src/errors.rs3
-rw-r--r--zellij-utils/src/input/actions.rs1
-rw-r--r--zellij-utils/src/input/command.rs2
-rw-r--r--zellij-utils/src/input/mod.rs4
-rw-r--r--zellij-utils/src/input/options.rs13
-rw-r--r--zellij-utils/src/setup.rs4
36 files changed, 978 insertions, 59 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b7edee505..9f734a1e7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2727,6 +2727,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom 0.2.6",
+ "serde",
+]
+
+[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3262,6 +3272,7 @@ dependencies = [
"typetag",
"unicode-width",
"url",
+ "uuid",
"wasmer",
"wasmer-wasi",
"zellij-tile",
diff --git a/docs/MANPAGE.md b/docs/MANPAGE.md
index 8f24bbc5b..877141b9a 100644
--- a/docs/MANPAGE.md
+++ b/docs/MANPAGE.md
@@ -152,6 +152,7 @@ ACTIONS
* __MoveFocus: <Direction\>__ - moves focus in the specified direction (Left,
Right, Up, Down).
* __DumpScreen: <File\>__ - dumps the screen in the specified file.
+* __EditScrollback__ - replaces the current pane with the scrollback buffer.
* __ScrollUp__ - scrolls up 1 line in the focused pane.
* __ScrollDown__ - scrolls down 1 line in the focused pane.
* __PageScrollUp__ - scrolls up 1 page in the focused pane.
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs
index 92438525c..00cf27153 100644
--- a/src/tests/e2e/cases.rs
+++ b/src/tests/e2e/cases.rs
@@ -38,6 +38,7 @@ pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k
pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j
pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b
pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f
+pub const EDIT_SCROLLBACK: [u8; 1] = [101]; // e
pub const RESIZE_MODE: [u8; 1] = [14]; // ctrl-n
pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j
@@ -1804,3 +1805,51 @@ pub fn tmux_mode() {
};
assert_snapshot!(last_snapshot);
}
+
+#[test]
+#[ignore]
+pub fn edit_scrollback() {
+ 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);
+ 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(&SCROLL_MODE);
+ remote_terminal.send_key(&EDIT_SCROLLBACK);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ });
+ runner.run_all_steps();
+ let last_snapshot = runner.take_snapshot_after(Step {
+ name: "Wait for editor to appear",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.snapshot_contains(".dump") {
+ // the .dump is an indication we get on the bottom line of vi when editing a
+ // file
+ // the temp file name is randomly generated, so we don't assert the whole snapshot
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ });
+ if runner.test_timed_out && test_attempts > 0 {
+ test_attempts -= 1;
+ continue;
+ } else {
+ break last_snapshot;
+ }
+ };
+ assert!(last_snapshot.contains(".dump"));
+}
diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs
index 466b18052..564714bb7 100644
--- a/src/tests/e2e/remote_runner.rs
+++ b/src/tests/e2e/remote_runner.rs
@@ -17,6 +17,7 @@ use std::cell::RefCell;
use std::rc::Rc;
const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij";
+const SET_ENV_VARIABLES: &str = "EDITOR=/usr/bin/vi";
const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts";
const ZELLIJ_DATA_DIR: &str = "/usr/src/zellij/e2e-data";
const CONNECTION_STRING: &str = "127.0.0.1:2222";
@@ -64,8 +65,8 @@ fn start_zellij(channel: &mut ssh2::Channel) {
channel
.write_all(
format!(
- "{} --session {} --data-dir {}\n",
- ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
+ "{} {} --session {} --data-dir {}\n",
+ SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
)
.as_bytes(),
)
@@ -78,8 +79,8 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
channel
.write_all(
format!(
- "{} --session {} --data-dir {} options --mirror-session true\n",
- ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
+ "{} {} --session {} --data-dir {} options --mirror-session true\n",
+ SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
)
.as_bytes(),
)
@@ -92,8 +93,12 @@ fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirr
channel
.write_all(
format!(
- "{} --session {} --data-dir {} options --mirror-session {}\n",
- ZELLIJ_EXECUTABLE_LOCATION, session_name, ZELLIJ_DATA_DIR, mirrored
+ "{} {} --session {} --data-dir {} options --mirror-session {}\n",
+ SET_ENV_VARIABLES,
+ ZELLIJ_EXECUTABLE_LOCATION,
+ session_name,
+ ZELLIJ_DATA_DIR,
+ mirrored
)
.as_bytes(),
)
@@ -103,7 +108,13 @@ fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirr
fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) {
channel
- .write_all(format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, session_name).as_bytes())
+ .write_all(
+ format!(
+ "{} {} attach {}\n",
+ SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, session_name
+ )
+ .as_bytes(),
+ )
.unwrap();
channel.flush().unwrap();
}
@@ -113,8 +124,8 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
channel
.write_all(
format!(
- "{} --session {} --data-dir {} options --no-pane-frames\n",
- ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
+ "{} {} --session {} --data-dir {} options --no-pane-frames\n",
+ SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
)
.as_bytes(),
)
@@ -127,8 +138,12 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
channel
.write_all(
format!(
- "{} --layout {} --session {} --data-dir {}\n",
- ZELLIJ_EXECUTABLE_LOCATION, layout_path, SESSION_NAME, ZELLIJ_DATA_DIR
+ "{} {} --layout {} --session {} --data-dir {}\n",
+ SET_ENV_VARIABLES,
+ ZELLIJ_EXECUTABLE_LOCATION,
+ layout_path,
+ SESSION_NAME,
+ ZELLIJ_DATA_DIR
)
.as_bytes(),
)
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap
index 664e881b4..422a5616c 100644
--- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap
@@ -1,7 +1,7 @@
---
source: src/tests/e2e/cases.rs
+assertion_line: 295
expression: last_snapshot
-
---
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
@@ -26,4 +26,4 @@ expression: last_snapshot
│ ││line19 00000000000000000000000000000000000000000000000000█│
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
- <↓↑> Scroll / <PgUp/PgDn> Scroll Page / <u/d> Scroll Half Page / <ENTER> Select pane
+ <↓↑> Scroll / <PgUp/PgDn> Scroll / <u/d> Scroll / <e> Edit / <ENTER> Select pane
diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml
index d1cf9eb25..74767439d 100644
--- a/zellij-server/Cargo.toml
+++ b/zellij-server/Cargo.toml
@@ -28,6 +28,7 @@ typetag = "0.1.7"
chrono = "0.4.19"
close_fds = "0.3.2"
sysinfo = "0.22.5"
+uuid = { version = "0.8.2", features = ["serde", "v4"] }
[dev-dependencies]
insta = "1.6.0"
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 81b66801e..0a587d7c6 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -620,6 +620,7 @@ fn init_session(
Some(os_input.clone()),
),
opts.debug,
+ config_options.scrollback_editor.clone(),
);
move || pty_thread_main(pty, layout)
diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs
index 1e43e47fa..b757ad460 100644
--- a/zellij-server/src/os_input_output.rs
+++ b/zellij-server/src/os_input_output.rs
@@ -145,6 +145,7 @@ fn handle_openpty(
///
fn handle_terminal(
cmd: RunCommand,
+ failover_cmd: Option<RunCommand>,
orig_termios: termios::Termios,
quit_cb: Box<dyn Fn(PaneId) + Send>,
) -> (RawFd, RawFd) {
@@ -152,9 +153,12 @@ fn handle_terminal(
// parent.
match openpty(None, Some(&orig_termios)) {
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb),
- Err(e) => {
- panic!("failed to start pty{:?}", e);
- }
+ Err(e) => match failover_cmd {
+ Some(failover_cmd) => handle_terminal(failover_cmd, None, orig_termios, quit_cb),
+ None => {
+ panic!("failed to start pty{:?}", e);
+ }
+ },
}
}
@@ -174,19 +178,40 @@ pub fn spawn_terminal(
terminal_action: TerminalAction,
orig_termios: termios::Termios,
quit_cb: Box<dyn Fn(PaneId) + Send>,
-) -> (RawFd, RawFd) {
+ default_editor: Option<PathBuf>,
+) -> Result<(RawFd, RawFd), &'static str> {
+ let mut failover_cmd_args = None;
let cmd = match terminal_action {
- TerminalAction::OpenFile(file_to_open) => {
- if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() {
- panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)");
+ TerminalAction::OpenFile(file_to_open, line_number) => {
+ if default_editor.is_none()
+ && env::var("EDITOR").is_err()
+ && env::var("VISUAL").is_err()
+ {
+ return Err(
+ "No Editor found, consider setting a path to one in $EDITOR or $VISUAL",
+ );
}
- let command =
- PathBuf::from(env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()));
+ let command = default_editor.unwrap_or_else(|| {
+ PathBuf::from(env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()))
+ });
- let args = vec![file_to_open
+ let mut args = vec![];
+ let file_to_open = file_to_open
.into_os_string()
.into_string()
- .expect("Not valid Utf8 Encoding")];
+ .expect("Not valid Utf8 Encoding");
+ if let Some(line_number) = line_number {
+ if command.ends_with("vim")
+ || command.ends_with("nvim")
+ || command.ends_with("emacs")
+ || command.ends_with("nano")
+ || command.ends_with("kak")
+ {
+ failover_cmd_args = Some(vec![file_to_open.clone()]);
+ args.push(format!("+{}", line_number));
+ }
+ }
+ args.push(file_to_open);
RunCommand {
command,
args,
@@ -195,8 +220,15 @@ pub fn spawn_terminal(
}
TerminalAction::RunCommand(command) => command,
};
+ let failover_cmd = if let Some(failover_cmd_args) = failover_cmd_args {
+ let mut cmd = cmd.clone();
+ cmd.args = failover_cmd_args;
+ Some(cmd)
+ } else {
+ None
+ };
- handle_terminal(cmd, orig_termios, quit_cb)
+ Ok(handle_terminal(cmd, failover_cmd, orig_termios, quit_cb))
}
#[derive(Clone)]
@@ -245,7 +277,8 @@ pub trait ServerOsApi: Send + Sync {
&self,
terminal_action: TerminalAction,
quit_cb: Box<dyn Fn(PaneId) + Send>,
- ) -> (RawFd, RawFd);
+ default_editor: Option<PathBuf>,
+ ) -> Result<(RawFd, RawFd), &'static str>;
/// Read bytes from the standard output of the virtual terminal referred to by `fd`.
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
/// Creates an `AsyncReader` that can be used to read from `fd` in an async context
@@ -284,9 +317,15 @@ impl ServerOsApi for ServerOsInputOutput {
&self,
terminal_action: TerminalAction,
quit_cb: Box<dyn Fn(PaneId) + Send>,
- ) -> (RawFd, RawFd) {
+ default_editor: Option<PathBuf>,
+ ) -> Result<(RawFd, RawFd), &'static str> {
let orig_termios = self.orig_termios.lock().unwrap();
- spawn_terminal(terminal_action, orig_termios.clone(), quit_cb)
+ spawn_terminal(
+ terminal_action,
+ orig_termios.clone(),
+ quit_cb,
+ default_editor,
+ )
}
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
unistd::read(fd, buf)
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index e4925c365..f8bd46bc3 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -101,6 +101,49 @@ impl FloatingPanes {
self.panes.insert(pane_id, pane);
self.z_indices.push(pane_id);
}
+ pub fn replace_active_pane(
+ &mut self,
+ pane: Box<dyn Pane>,
+ client_id: ClientId,
+ ) -> Option<Box<dyn Pane>> {
+ self.active_panes
+ .get(&client_id)
+ .copied()
+ .and_then(|active_pane_id| self.replace_pane(active_pane_id, pane))
+ }
+ pub fn replace_pane(
+ &mut self,
+ pane_id: PaneId,
+ mut with_pane: Box<dyn Pane>,
+ ) -> Option<Box<dyn Pane>> {
+ let with_pane_id = with_pane.pid();
+ with_pane.set_content_offset(Offset::frame(1));
+ let removed_pane = self.panes.remove(&pane_id).map(|removed_pane| {
+ let removed_pane_id = removed_pane.pid();
+ let with_pane_id = with_pane.pid();
+ let removed_pane_geom = removed_pane.current_geom();
+ with_pane.set_geom(removed_pane_geom);
+ self.panes.insert(with_pane_id, with_pane);
+ let z_index = self
+ .z_indices
+ .iter()
+ .position(|pane_id| pane_id == &removed_pane_id)
+ .unwrap();
+ self.z_indices.remove(z_index);
+ self.z_indices.insert(z_index, with_pane_id);
+ removed_pane
+ });
+
+ // update the desired_pane_positions to relate to the new pane
+ if let Some(desired_pane_position) = self.desired_pane_positions.remove(&pane_id) {
+ self.desired_pane_positions
+ .insert(with_pane_id, desired_pane_position);
+ }
+
+ // move clients from the previously active pane to the new pane we just inserted
+ self.move_clients_between_panes(pane_id, with_pane_id);
+ removed_pane
+ }
pub fn rem