diff --git a/src/main.rs b/src/main.rs index d2363e7b..895bc899 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ fn main() { commands::send_action_to_session(cli_action, opts.session); std::process::exit(0); } - if let Some(Command::Sessions(Sessions::Command { + if let Some(Command::Sessions(Sessions::Run { command, direction, cwd, diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 63e2d677..44551d99 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -13,6 +13,7 @@ use super::remote_runner::{RemoteRunner, RemoteTerminal, Step}; pub const QUIT: [u8; 1] = [17]; // ctrl-q pub const ESC: [u8; 1] = [27]; pub const ENTER: [u8; 1] = [10]; // char '\n' +pub const SPACE: [u8; 1] = [32]; pub const LOCK_MODE: [u8; 1] = [7]; // ctrl-g pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h @@ -1876,3 +1877,93 @@ pub fn undo_rename_pane() { }; assert_snapshot!(last_snapshot); } + +#[test] +#[ignore] +pub fn send_command_through_the_cli() { + // here we test the following flow: + // - send a command through the cli to run a bash script in a temporary folder + // - have it open a "command pane" that can be re-run with Enter + // - press Enter in the command pane to re-run the script + // + // the script appends the word "foo" to a temporary file and then `cat`s that file, + // so when we press "Enter", it will run again and we'll see two "foo"s one after the other, + // that's how we know the whole flow is working + 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: "Run command through the cli", + 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) + { + let fixture_folder = remote_terminal.path_to_fixture_folder(); + remote_terminal.send_command_through_the_cli(&format!( + "{}/append-echo-script.sh", + fixture_folder + )); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Wait for command to run", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("") + && remote_terminal.cursor_position_is(61, 3) + { + remote_terminal.send_key(&SPACE); // re-run script - here we use SPACE + // instead of the default ENTER because + // sending ENTER over SSH can be a little + // problematic (read: I couldn't get it + // to pass consistently) + step_is_complete = true + } + step_is_complete + }, + }) + .add_step(Step { + name: "Wait for script to run again", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("") + && remote_terminal.cursor_position_is(61, 4) + { + step_is_complete = true + } + step_is_complete + }, + }); + runner.run_all_steps(); + + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for script to run twice", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("foo") + && remote_terminal.cursor_position_is(61, 4) + { + step_is_complete = true + } + step_is_complete + }, + }); + + 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 b0bde1db..b500149f 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -64,6 +64,8 @@ fn stop_zellij(channel: &mut ssh2::Channel) { .write_all(b"find /tmp | grep status-bar-tips | xargs rm\n") .unwrap(); channel.write_all(b"killall -KILL zellij\n").unwrap(); + channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous + // tests } fn start_zellij(channel: &mut ssh2::Channel) { @@ -198,6 +200,7 @@ fn read_from_channel( sixel_image_store, Rc::new(RefCell::new(Palette::default())), Rc::new(RefCell::new(HashMap::new())), + None, ); // 0 is the pane index loop { if !should_keep_running.load(Ordering::SeqCst) { @@ -339,6 +342,16 @@ impl RemoteTerminal { channel.flush().unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } + pub fn send_command_through_the_cli(&mut self, command: &str) { + let mut channel = self.channel.lock().unwrap(); + channel + .write_all(format!("{} run \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes()) + .unwrap(); + channel.flush().unwrap(); + } + pub fn path_to_fixture_folder(&self) -> String { + ZELLIJ_FIXTURE_PATH.to_string() + } pub fn load_fixture(&mut self, name: &str) { let mut channel = self.channel.lock().unwrap(); channel diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap new file mode 100644 index 00000000..8f1e58cc --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +assertion_line: 1968 +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────┐┌ /usr/src/zellij/fixtures/append-echo-script.sh ──────────┐ +│$ /usr/src/zellij/x86_64-unknown-linux-musl/release/zellij││foo │ +│ run "/usr/src/zellij/fixtures/append-echo-script.sh" ││foo │ +│$ ││█ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] to re-run, to exit ────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|-> => resize pane. diff --git a/src/tests/fixtures/append-echo-script.sh b/src/tests/fixtures/append-echo-script.sh new file mode 100755 index 00000000..d6b900cb --- /dev/null +++ b/src/tests/fixtures/append-echo-script.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo foo >> /tmp/foo && cat /tmp/foo diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 8bd62a21..9d720c71 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::{fs::File, io::Write}; use crate::panes::PaneId; @@ -39,7 +39,7 @@ pub use nix::unistd::Pid; use crate::ClientId; -pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { +fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { // TODO: do this with the nix ioctl use libc::ioctl; use libc::TIOCSWINSZ; @@ -61,18 +61,19 @@ pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { /// Handle some signals for the child process. This will loop until the child /// process exits. -fn handle_command_exit(mut child: Child) { +fn handle_command_exit(mut child: Child) -> Option { + // returns the exit status, if any let mut should_exit = false; let mut attempts = 3; let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM]).unwrap(); 'handle_exit: loop { // test whether the child process has exited match child.try_wait() { - Ok(Some(_status)) => { + Ok(Some(status)) => { // if the child process has exited, break outside of the loop // and exit this function // TODO: handle errors? - break 'handle_exit; + break 'handle_exit status.code(); }, Ok(None) => { ::std::thread::sleep(::std::time::Duration::from_millis(10)); @@ -94,50 +95,80 @@ fn handle_command_exit(mut child: Child) { } else { // when I say whoa, I mean WHOA! let _ = child.kill(); - break 'handle_exit; + break 'handle_exit None; } } } +fn command_exists(cmd: &RunCommand) -> bool { + let command = &cmd.command; + match cmd.cwd.as_ref() { + Some(cwd) => { + if cwd.join(&command).exists() { + return true; + } + }, + None => { + if command.exists() { + return true; + } + }, + } + + if let Some(paths) = env::var_os("PATH") { + for path in env::split_paths(&paths) { + if path.join(command).exists() { + return true; + } + } + } + false +} + fn handle_openpty( open_pty_res: OpenptyResult, cmd: RunCommand, - quit_cb: Box, -) -> (RawFd, RawFd) { + quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + terminal_id: u32, +) -> Result<(RawFd, RawFd), SpawnTerminalError> { // primary side of pty and child fd let pid_primary = open_pty_res.master; let pid_secondary = open_pty_res.slave; - let mut child = unsafe { - let command = &mut Command::new(cmd.command); - if let Some(current_dir) = cmd.cwd { - if current_dir.exists() { - command.current_dir(current_dir); - } - } - command - .args(&cmd.args) - .pre_exec(move || -> std::io::Result<()> { - if libc::login_tty(pid_secondary) != 0 { - panic!("failed to set controlling terminal"); + if command_exists(&cmd) { + let mut child = unsafe { + let cmd = cmd.clone(); + let command = &mut Command::new(cmd.command); + if let Some(current_dir) = cmd.cwd { + if current_dir.exists() { + command.current_dir(current_dir); } - close_fds::close_open_fds(3, &[]); - Ok(()) - }) - .spawn() - .expect("failed to spawn") - }; + } + command + .args(&cmd.args) + .pre_exec(move || -> std::io::Result<()> { + if libc::login_tty(pid_secondary) != 0 { + panic!("failed to set controlling terminal"); + } + close_fds::close_open_fds(3, &[]); + Ok(()) + }) + .spawn() + .expect("failed to spawn") + }; - let child_id = child.id(); - std::thread::spawn(move || { - child.wait().unwrap(); - handle_command_exit(child); - let _ = nix::unistd::close(pid_primary); - let _ = nix::unistd::close(pid_secondary); - quit_cb(PaneId::Terminal(pid_primary)); - }); + let child_id = child.id(); + std::thread::spawn(move || { + child.wait().unwrap(); + let exit_status = handle_command_exit(child); + let _ = nix::unistd::close(pid_secondary); + quit_cb(PaneId::Terminal(terminal_id), exit_status, cmd); + }); - (pid_primary, child_id as RawFd) + Ok((pid_primary, child_id as RawFd)) + } else { + Err(SpawnTerminalError::CommandNotFound(terminal_id)) + } } /// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) @@ -147,16 +178,20 @@ fn handle_terminal( cmd: RunCommand, failover_cmd: Option, orig_termios: termios::Termios, - quit_cb: Box, -) -> (RawFd, RawFd) { - // Create a pipe to allow the child the communicate the shell's pid to it's + quit_cb: Box, RunCommand) + Send>, + terminal_id: u32, +) -> Result<(RawFd, RawFd), SpawnTerminalError> { + // Create a pipe to allow the child the communicate the shell's pid to its // parent. match openpty(None, Some(&orig_termios)) { - Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb), + Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb, terminal_id), Err(e) => match failover_cmd { - Some(failover_cmd) => handle_terminal(failover_cmd, None, orig_termios, quit_cb), + Some(failover_cmd) => { + handle_terminal(failover_cmd, None, orig_termios, quit_cb, terminal_id) + }, None => { - panic!("failed to start pty{:?}", e); + log::error!("Failed to start pty: {:?}", e); + Err(SpawnTerminalError::FailedToStartPty) }, }, } @@ -192,12 +227,15 @@ fn separate_command_arguments(command: &mut PathBuf, args: &mut Vec) { /// /// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not /// set. -pub fn spawn_terminal( +fn spawn_terminal( terminal_action: TerminalAction, orig_termios: termios::Termios, - quit_cb: Box, + quit_cb: Box, RunCommand) + Send>, // u32 is the exit_status default_editor: Option, -) -> Result<(RawFd, RawFd), &'static str> { + terminal_id: u32, +) -> Result<(RawFd, RawFd), SpawnTerminalError> { + // returns the terminal_id, the primary fd and the + // secondary fd let mut failover_cmd_args = None; let cmd = match terminal_action { TerminalAction::OpenFile(file_to_open, line_number) => { @@ -205,9 +243,7 @@ pub fn spawn_terminal( && 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", - ); + return Err(SpawnTerminalError::NoEditorFound); } let mut command = default_editor.unwrap_or_else(|| { @@ -239,6 +275,7 @@ pub fn spawn_terminal( command, args, cwd: None, + hold_on_close: false, } }, TerminalAction::RunCommand(command) => command, @@ -251,13 +288,52 @@ pub fn spawn_terminal( None }; - Ok(handle_terminal(cmd, failover_cmd, orig_termios, quit_cb)) + handle_terminal(cmd, failover_cmd, orig_termios, quit_cb, terminal_id) +} + +#[derive(Debug, Clone, Copy)] +pub enum SpawnTerminalError { + CommandNotFound(u32), // u32 is the terminal id + NoEditorFound, + NoMoreTerminalIds, + FailedToStartPty, + GenericSpawnError(&'static str), +} + +impl std::fmt::Display for SpawnTerminalError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + SpawnTerminalError::CommandNotFound(terminal_id) => { + write!(f, "Command not found for terminal_id: {}", terminal_id) + }, + SpawnTerminalError::NoEditorFound => { + write!( + f, + "No Editor found, consider setting a path to one in $EDITOR or $VISUAL" + ) + }, + SpawnTerminalError::NoMoreTerminalIds => { + write!(f, "No more terminal ids left to allocate.") + }, + SpawnTerminalError::FailedToStartPty => { + write!(f, "Failed to start pty") + }, + SpawnTerminalError::GenericSpawnError(msg) => { + write!(f, "{}", msg) + }, + } + } } #[derive(Clone)] pub struct ServerOsInputOutput { orig_termios: Arc>, client_senders: Arc>>>, + terminal_id_to_raw_fd: Arc>>>, // A value of None means the + // terminal_id exists but is + // not connected to an fd (eg. + // a command pane with a + // non-existing command) } // async fn in traits is not supported by rust, so dtolnay's excellent async_trait macro is being @@ -291,25 +367,24 @@ impl AsyncReader for RawFdAsyncReader { /// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that /// Zellij server requires. pub trait ServerOsApi: Send + Sync { - /// Sets the size of the terminal associated to file descriptor `fd`. - fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16); + fn set_terminal_size_using_terminal_id(&self, id: u32, cols: u16, rows: u16); /// Spawn a new terminal, with a terminal action. The returned tuple contains the master file /// descriptor of the forked pseudo terminal and a [ChildId] struct containing process id's for /// the forked child process. fn spawn_terminal( &self, terminal_action: TerminalAction, - quit_cb: Box, + quit_cb: Box, RunCommand) + Send>, // u32 is the exit status default_editor: Option, - ) -> Result<(RawFd, RawFd), &'static str>; + ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError>; /// 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; /// Creates an `AsyncReader` that can be used to read from `fd` in an async context fn async_file_reader(&self, fd: RawFd) -> Box; /// Write bytes to the standard input of the virtual terminal referred to by `fd`. - fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result; + fn write_to_tty_stdin(&self, terminal_id: u32, buf: &[u8]) -> Result; /// Wait until all output written to the object referred to by `fd` has been transmitted. - fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>; + fn tcdrain(&self, terminal_id: u32) -> Result<(), nix::Error>; /// Terminate the process with process ID `pid`. (SIGTERM) fn kill(&self, pid: Pid) -> Result<(), nix::Error>; /// Terminate the process with process ID `pid`. (SIGKILL) @@ -332,27 +407,78 @@ pub trait ServerOsApi: Send + Sync { fn get_cwd(&self, pid: Pid) -> Option; /// Writes the given buffer to a string fn write_to_file(&mut self, buf: String, file: Option); + + fn re_run_command_in_terminal( + &self, + terminal_id: u32, + run_command: RunCommand, + quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + ) -> Result<(RawFd, RawFd), SpawnTerminalError>; + fn clear_terminal_id(&self, terminal_id: u32); } impl ServerOsApi for ServerOsInputOutput { - fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) { - if cols > 0 && rows > 0 { - set_terminal_size_using_fd(fd, cols, rows); + fn set_terminal_size_using_terminal_id(&self, id: u32, cols: u16, rows: u16) { + match self.terminal_id_to_raw_fd.lock().unwrap().get(&id) { + Some(Some(fd)) => { + if cols > 0 && rows > 0 { + set_terminal_size_using_fd(*fd, cols, rows); + } + }, + _ => { + log::error!("Failed to find terminal fd for id: {id}, so cannot resize terminal"); + }, } } fn spawn_terminal( &self, terminal_action: TerminalAction, - quit_cb: Box, + quit_cb: Box, RunCommand) + Send>, // u32 is the exit status default_editor: Option, - ) -> Result<(RawFd, RawFd), &'static str> { + ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> { let orig_termios = self.orig_termios.lock().unwrap(); - spawn_terminal( - terminal_action, - orig_termios.clone(), - quit_cb, - default_editor, - ) + let mut terminal_id = None; + { + let current_ids: HashSet = self + .terminal_id_to_raw_fd + .lock() + .unwrap() + .keys() + .copied() + .collect(); + for i in 0..u32::MAX { + let i = i as u32; + if !current_ids.contains(&i) { + terminal_id = Some(i); + break; + } + } + } + match terminal_id { + Some(terminal_id) => { + self.terminal_id_to_raw_fd + .lock() + .unwrap() + .insert(terminal_id, None); + match spawn_terminal( + terminal_action, + orig_termios.clone(), + quit_cb, + default_editor, + terminal_id, + ) { + Ok((pid_primary, pid_secondary)) => { + self.terminal_id_to_raw_fd + .lock() + .unwrap() + .insert(terminal_id, Some(pid_primary)); + Ok((terminal_id, pid_primary, pid_secondary)) + }, + Err(e) => Err(e), + } + }, + None => Err(SpawnTerminalError::NoMoreTerminalIds), + } } fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { unistd::read(fd, buf) @@ -360,11 +486,25 @@ impl ServerOsApi for ServerOsInputOutput { fn async_file_reader(&self, fd: RawFd) -> Box { Box::new(RawFdAsyncReader::new(fd)) } - fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result { - unistd::write(fd, buf) + fn write_to_tty_stdin(&self, terminal_id: u32, buf: &[u8]) -> Result { + match self.terminal_id_to_raw_fd.lock().unwrap().get(&terminal_id) { + Some(Some(fd)) => unistd::write(*fd, buf), + _ => { + // TODO: propagate this error + log::error!("Failed to write to terminal with {terminal_id} - could not find its file descriptor"); + Ok(0) + }, + } } - fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> { - termios::tcdrain(fd) + fn tcdrain(&self, terminal_id: u32) -> Result<(), nix::Error> { + match self.terminal_id_to_raw_fd.lock().unwrap().get(&terminal_id) { + Some(Some(fd)) => termios::tcdrain(*fd), + _ => { + // TODO: propagate this error + log::error!("Failed to tcdrain to terminal with {terminal_id} - could not find its file descriptor"); + Ok(()) + }, + } } fn box_clone(&self) -> Box { Box::new((*self).clone()) @@ -430,6 +570,37 @@ impl ServerOsApi for ServerOsInputOutput { log::error!("could not write to file: {}", e); } } + fn re_run_command_in_terminal( + &self, + terminal_id: u32, + run_command: RunCommand, + quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + ) -> Result<(RawFd, RawFd), SpawnTerminalError> { + let orig_termios = self.orig_termios.lock().unwrap(); + let default_editor = None; // no need for a default editor when running an explicit command + match spawn_terminal( + TerminalAction::RunCommand(run_command), + orig_termios.clone(), + quit_cb, + default_editor, + terminal_id, + ) { + Ok((pid_primary, pid_secondary)) => { + self.terminal_id_to_raw_fd + .lock() + .unwrap() + .insert(terminal_id, Some(pid_primary)); + Ok((pid_primary, pid_secondary)) + }, + Err(e) => Err(e), + } + } + fn clear_terminal_id(&self, terminal_id: u32) { + self.terminal_id_to_raw_fd + .lock() + .unwrap() + .remove(&terminal_id); + } } impl Clone for Box { @@ -444,6 +615,7 @@ pub fn get_server_os_input() -> Result { Ok(ServerOsInputOutput { orig_termios, client_senders: Arc::new(Mutex::new(HashMap::new())), + terminal_id_to_raw_fd: Arc::new(Mutex::new(BTreeMap::new())), }) } diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 442a4dd6..650a763a 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -17,15 +17,16 @@ use std::rc::Rc; use std::time::Instant; use zellij_utils::{ data::{ModeInfo, Style}, + input::command::RunCommand, pane_size::{Offset, PaneGeom, Size, Viewport}, }; macro_rules! resize_pty { ($pane:expr, $os_input:expr) => { if let PaneId::Terminal(ref pid) = $pane.pid() { - // FIXME: This `set_terminal_size_using_fd` call would be best in + // FIXME: This `set_terminal_size_using_terminal_id` call would be best in // `TerminalPane::reflow_lines` - $os_input.set_terminal_size_using_fd( + $os_input.set_terminal_size_using_terminal_id( *pid, $pane.get_content_columns() as u16, $pane.get_content_rows() as u16, @@ -150,6 +151,16 @@ impl FloatingPanes { self.desired_pane_positions.remove(&pane_id); self.panes.remove(&pane_id) } + pub fn hold_pane( + &mut self, + pane_id: PaneId, + exit_status: Option, + run_command: RunCommand, + ) { + self.panes + .get_mut(&pane_id) + .map(|p| p.hold(exit_status, run_command)); + } pub fn get(&self, pane_id: &PaneId) -> Option<&Box> { self.panes.get(pane_id) } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 677eed16..12849f81 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1504,7 +1504,7 @@ impl Grid { pub fn mark_for_rerender(&mut self) { self.should_render = true; } - fn reset_terminal_state(&mut self) { + pub fn reset_terminal_state(&mut self) { self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap()); self.lines_below = vec![]; self.viewport = vec![Row::new(self.width).canonical()]; diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 2ee865fe..2bb78154 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -113,10 +113,6 @@ impl Pane for PluginPane { fn cursor_coordinates(&self) -> Option<(usize, usize)> { None } - fn adjust_input_to_terminal(&self, _input_bytes: Vec) -> Vec { - // noop - vec![] - } fn position_and_size(&self) -> PaneGeom { self.geom } diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 8b1beeac..95ddf982 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -6,14 +6,14 @@ use crate::panes::{ }; use crate::panes::{AnsiCode, LinkHandler}; use crate::pty::VteBytes; -use crate::tab::Pane; +use crate::tab::{AdjustedInput, Pane}; use crate::ClientId; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; -use std::os::unix::io::RawFd; use std::rc::Rc; use std::time::{self, Instant}; +use zellij_utils::input::command::RunCommand; use zellij_utils::pane_size::Offset; use zellij_utils::{ data::{InputMode, Palette, PaletteColor, Style}, @@ -37,6 +37,10 @@ const HOME_KEY: &[u8] = &[27, 91, 72]; const END_KEY: &[u8] = &[27, 91, 70]; const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126]; const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126]; +const ENTER_NEWLINE: &[u8] = &[10]; +const ENTER_CARRIAGE_RETURN: &[u8] = &[13]; +const SPACE: &[u8] = &[32]; +const CTRL_C: &[u8] = &[3]; // TODO: check this to be sure it fits all types of CTRL_C (with mac, etc) const TERMINATING_STRING: &str = "\0"; const DELETE_KEY: &str = "\u{007F}"; const BACKSPACE_KEY: &str = "\u{0008}"; @@ -74,7 +78,7 @@ impl AnsiEncoding { #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] pub enum PaneId { - Terminal(RawFd), + Terminal(u32), Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? } @@ -83,7 +87,7 @@ pub enum PaneId { #[allow(clippy::too_many_arguments)] pub struct TerminalPane { pub grid: Grid, - pub pid: RawFd, + pub pid: u32, pub selectable: bool, pub geom: PaneGeom, pub geom_override: Option, @@ -99,6 +103,8 @@ pub struct TerminalPane { borderless: bool, fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render search_term: String, + is_held: Option<(Option, RunCommand)>, // a "held" pane means that its command has exited and its waiting for a + // possible user instruction to be re-run } impl Pane for TerminalPane { @@ -157,53 +163,83 @@ impl Pane for TerminalPane { .cursor_coordinates() .map(|(x, y)| (x + left, y + top)) } - fn adjust_input_to_terminal(&self, input_bytes: Vec) -> Vec { + fn adjust_input_to_terminal(&mut self, input_bytes: Vec) -> Option { // there are some cases in which the terminal state means that input sent to it // needs to be adjusted. // here we match against those cases - if need be, we adjust the input and if not // we send back the original input - if self.grid.new_line_mode { - if let &[13] = input_bytes.as_slice() { - // LNM - carriage return is followed by linefeed - return "\u{0d}\u{0a}".as_bytes().to_vec(); - }; - } - if self.grid.cursor_key_mode { + if let Some((_exit_status, run_command)) = &self.is_held { match input_bytes.as_slice() { - LEFT_ARROW => { - return AnsiEncoding::Left.as_vec_bytes(); + ENTER_CARRIAGE_RETURN | ENTER_NEWLINE | SPACE => { + let run_command = run_command.clone(); + self.is_held = None; + self.grid.reset_terminal_state(); + self.set_should_render(true); + Some(AdjustedInput::ReRunCommandInThisPane(run_command)) }, - RIGHT_ARROW => { - return AnsiEncoding::Right.as_vec_bytes(); - }, - UP_ARROW => { - return AnsiEncoding::Up.as_vec_bytes(); - }, - DOWN_ARROW => { - return AnsiEncoding::Down.as_vec_bytes(); - }, - - HOME_KEY => { - return AnsiEncoding::Home.as_vec_bytes(); - }, - END_KEY => { - return AnsiEncoding::End.as_vec_bytes(); - }, - _ => {}, - }; - } - - if !self.grid.bracketed_paste_mode { - // Zellij itself operates in bracketed paste mode, so the terminal sends these - // instructions (bracketed paste start and bracketed paste end respectively) - // when pasting input. We only need to make sure not to send them to terminal - // panes who do not work in this mode - match input_bytes.as_slice() { - BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => return vec![], - _ => {}, + CTRL_C => Some(AdjustedInput::CloseThisPane), + _ => None, } + } else { + if self.grid.new_line_mode { + if let &[13] = input_bytes.as_slice() { + // LNM - carriage return is followed by linefeed + return Some(AdjustedInput::WriteBytesToTerminal( + "\u{0d}\u{0a}".as_bytes().to_vec(), + )); + }; + } + if self.grid.cursor_key_mode { + match input_bytes.as_slice() { + LEFT_ARROW => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::Left.as_vec_bytes(), + )); + }, + RIGHT_ARROW => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::Right.as_vec_bytes(), + )); + }, + UP_ARROW => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::Up.as_vec_bytes(), + )); + }, + DOWN_ARROW => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::Down.as_vec_bytes(), + )); + }, + + HOME_KEY => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::Home.as_vec_bytes(), + )); + }, + END_KEY => { + return Some(AdjustedInput::WriteBytesToTerminal( + AnsiEncoding::End.as_vec_bytes(), + )); + }, + _ => {}, + }; + } + + if !self.grid.bracketed_paste_mode { + // Zellij itself operates in bracketed paste mode, so the terminal sends these + // instructions (bracketed paste start and bracketed paste end respectively) + // when pasting input. We only need to make sure not to send them to terminal + // panes who do not work in this mode + match input_bytes.as_slice() { + BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => { + return Some(AdjustedInput::WriteBytesToTerminal(vec![])) + }, + _ => {}, + } + } + Some(AdjustedInput::WriteBytesToTerminal(input_bytes)) } - input_bytes } fn position_and_size(&self) -> PaneGeom { self.geom @@ -307,7 +343,9 @@ impl Pane for TerminalPane { input_mode: InputMode, ) -> Option<(Vec, Option)> { // TODO: remove the cursor stuff from here - let pane_title = if self.pane_name.is_empty() + let pane_title = if let Some((_exit_status, run_command)) = &self.is_held { + format!("{}", run_command) + } else if self.pane_name.is_empty() && input_mode == InputMode::RenamePane && frame_params.is_main_client { @@ -346,12 +384,15 @@ impl Pane for TerminalPane { self.pane_name.clone() }; - let frame = PaneFrame::new( + let mut frame = PaneFrame::new( self.current_geom().into(), self.grid.scrollback_position_and_length(), pane_title, frame_params, ); + if let Some((exit_status, _run_command)) = &self.is_held { + frame.add_exit_status(exit_status.as_ref().copied()); + } match self.frame.get(&client_id) { // TODO: use and_then or something? @@ -654,12 +695,16 @@ impl Pane for TerminalPane { fn is_alternate_mode_active(&self) -> bool { self.grid.is_alternate_mode_active() } + fn hold(&mut self, exit_status: Option, run_command: RunCommand) { + self.is_held = Some((exit_status, run_command)); + self.set_should_render(true); + } } impl TerminalPane { #[allow(clippy::too_many_arguments)] pub fn new( - pid: RawFd, + pid: u32, position_and_size: PaneGeom, style: Style, pane_index: usize, @@ -669,8 +714,10 @@ impl TerminalPane { sixel_image_store: Rc>, terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, + initial_pane_title: Option, ) -> TerminalPane { - let initial_pane_title = format!("Pane #{}", pane_index); + let initial_pane_title = + initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index)); let grid = Grid::new( position_and_size.rows.as_usize(), position_and_size.cols.as_usize(), @@ -698,6 +745,7 @@ impl TerminalPane { borderless: false, fake_cursor_locations: HashSet::new(), search_term: String::new(), + is_held: None, } } pub fn get_x(&self) -> usize { diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 81911e12..fa49710a 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -14,6 +14,7 @@ use std::rc::Rc; use std::time::Instant; use zellij_utils::{ data::{ModeInfo, Style}, + input::command::RunCommand, input::layout::SplitDirection, pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, }; @@ -21,9 +22,9 @@ use zellij_utils::{ macro_rules! resize_pty { ($pane:expr, $os_input:expr) => { if let PaneId::Terminal(ref pid) = $pane.pid() { - // FIXME: This `set_terminal_size_using_fd` call would be best in + // FIXME: This `set_terminal_size_using_terminal_id` call would be best in // `TerminalPane::reflow_lines` - $os_input.set_terminal_size_using_fd( + $os_input.set_terminal_size_using_terminal_id( *pid, $pane.get_content_columns() as u16, $pane.get_content_rows() as u16, @@ -993,6 +994,16 @@ impl TiledPanes { None } } + pub fn hold_pane( + &mut self, + pane_id: PaneId, + exit_status: Option, + run_command: RunCommand, + ) { + self.panes + .get_mut(&pane_id) + .map(|p| p.hold(exit_status, run_command)); + } pub fn panes_to_hide_contains(&self, pane_id: PaneId) -> bool { self.panes_to_hide.contains(&pane_id) } diff --git a/zellij-server/src/panes/unit/search_in_pane_tests.rs b/zellij-server/src/panes/unit/search_in_pane_tests.rs index cdcc6bc0..fb03b491 100644 --- a/zellij-server/src/panes/unit/search_in_pane_tests.rs +++ b/zellij-server/src/panes/unit/search_in_pane_tests.rs @@ -39,6 +39,7 @@ fn create_pane() -> TerminalPane { sixel_image_store, Rc::new(RefCell::new(Palette::default())), terminal_emulator_color_codes, + None, ); // 0 is the pane index let content = read_fixture(); terminal_pane.handle_pty_bytes(content); diff --git a/zellij-server/src/panes/unit/terminal_pane_tests.rs b/zellij-server/src/panes/unit/terminal_pane_tests.rs index e5b17081..744d2e90 100644 --- a/zellij-server/src/panes/unit/terminal_pane_tests.rs +++ b/zellij-server/src/panes/unit/terminal_pane_tests.rs @@ -47,6 +47,7 @@ pub fn scrolling_inside_a_pane() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..30 { @@ -87,6 +88,7 @@ pub fn sixel_image_inside_terminal_pane() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let sixel_image_bytes = "\u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 @@ -127,6 +129,7 @@ pub fn partial_sixel_image_inside_terminal_pane() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let pane_content = read_fixture("sixel-image-500px.six"); terminal_pane.handle_pty_bytes(pane_content); @@ -161,6 +164,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let pane_content = read_fixture("sixel-image-500px.six"); terminal_pane.handle_pty_bytes(pane_content); @@ -194,6 +198,7 @@ pub fn scrolling_through_a_sixel_image() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..30 { @@ -238,6 +243,7 @@ pub fn multiple_sixel_images_in_pane() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -280,6 +286,7 @@ pub fn resizing_pane_with_sixel_images() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -325,6 +332,7 @@ pub fn changing_character_cell_size_with_sixel_images() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -375,6 +383,7 @@ pub fn keep_working_after_corrupted_sixel_image() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq @@ -423,6 +432,7 @@ pub fn pane_with_frame_position_is_on_frame() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index terminal_pane.set_content_offset(Offset::frame(1)); @@ -507,6 +517,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index terminal_pane.set_content_offset(Offset::shift(1, 1)); @@ -591,6 +602,7 @@ pub fn frameless_pane_position_is_on_frame() { sixel_image_store, terminal_emulator_colors, terminal_emulator_color_codes, + None, ); // 0 is the pane index terminal_pane.set_content_offset(Offset::default()); diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index df4d270a..9df221ce 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,6 +1,10 @@ +use crate::os_input_output::SpawnTerminalError; use crate::terminal_bytes::TerminalBytes; use crate::{ - panes::PaneId, screen::ScreenInstruction, thread_bus::Bus, wasm_vm::PluginInstruction, + panes::PaneId, + screen::ScreenInstruction, + thread_bus::{Bus, ThreadSenders}, + wasm_vm::PluginInstruction, ClientId, ServerInstruction, }; use async_std::task::{self, JoinHandle}; @@ -27,7 +31,8 @@ pub enum ClientOrTabIndex { /// Instructions related to PTYs (pseudoterminals). #[derive(Clone, Debug)] pub(crate) enum PtyInstruction { - SpawnTerminal(Option, ClientOrTabIndex), + SpawnTerminal(Option, Option, ClientOrTabIndex), // bool (if Some) is + // should_float OpenInPlaceEditor(PathBuf, Option, ClientId), // Option is the optional line number SpawnTerminalVertically(Option, ClientId), SpawnTerminalHorizontally(Option, ClientId), @@ -41,6 +46,7 @@ pub(crate) enum PtyInstruction { ), // the String is the tab name ClosePane(PaneId), CloseTab(Vec), + ReRunCommandInPane(PaneId, RunCommand), Exit, } @@ -56,6 +62,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, + PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, PtyInstruction::Exit => PtyContext::Exit, } } @@ -64,9 +71,9 @@ impl From<&PtyInstruction> for PtyContext { pub(crate) struct Pty { pub active_panes: HashMap, pub bus: Bus, - pub id_to_child_pid: HashMap, // pty_primary => child raw fd + pub id_to_child_pid: HashMap, // terminal_id => child raw fd debug_to_file: bool, - task_handles: HashMap>, + task_handles: HashMap>, // terminal_id to join-handle default_editor: Option, } @@ -75,17 +82,53 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) { let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Pty((&event).into())); match event { - PtyInstruction::SpawnTerminal(terminal_action, client_or_tab_index) => { - let pid = pty - .spawn_terminal(terminal_action, client_or_tab_index) - .unwrap(); // TODO: handle error here - pty.bus - .senders - .send_to_screen(ScreenInstruction::NewPane( - PaneId::Terminal(pid), - client_or_tab_index, - )) - .unwrap(); + PtyInstruction::SpawnTerminal(terminal_action, should_float, client_or_tab_index) => { + let (hold_on_close, run_command, pane_title) = match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => ( + run_command.hold_on_close, + Some(run_command.clone()), + Some(run_command.to_string()), + ), + _ => (false, None, None), + }; + match pty.spawn_terminal(terminal_action, client_or_tab_index) { + Ok(pid) => { + pty.bus + .senders + .send_to_screen(ScreenInstruction::NewPane( + PaneId::Terminal(pid), + pane_title, + should_float, + client_or_tab_index, + )) + .unwrap(); + }, + Err(SpawnTerminalError::CommandNotFound(pid)) => { + if hold_on_close { + pty.bus + .senders + .send_to_screen(ScreenInstruction::NewPane( + PaneId::Terminal(pid), + pane_title, + should_float, + client_or_tab_index, + )) + .unwrap(); + if let Some(run_command) = run_command { + send_command_not_found_to_screen( + pty.bus.senders.clone(), + pid, + run_command.clone(), + ); + } + } else { + pty.close_pane(PaneId::Terminal(pid)); + } + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } }, PtyInstruction::OpenInPlaceEditor(temp_file, line_number, client_id) => { match pty.spawn_terminal( @@ -107,28 +150,124 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) { } }, PtyInstruction::SpawnTerminalVertically(terminal_action, client_id) => { - let pid = pty - .spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) - .unwrap(); // TODO: handle error here - pty.bus - .senders - .send_to_screen(ScreenInstruction::VerticalSplit( - PaneId::Terminal(pid), - client_id, - )) - .unwrap(); + let (hold_on_close, run_command, pane_title) = match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => ( + run_command.hold_on_close, + Some(run_command.clone()), + Some(run_command.to_string()), + ), + _ => (false, None, None), + }; + match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) { + Ok(pid) => { + pty.bus + .senders + .send_to_screen(ScreenInstruction::VerticalSplit( + PaneId::Terminal(pid), + pane_title, + client_id, + )) + .unwrap(); + }, + Err(SpawnTerminalError::CommandNotFound(pid)) => { + if hold_on_close { + pty.bus + .senders + .send_to_screen(ScreenInstruction::VerticalSplit( + PaneId::Terminal(pid), + pane_title, + client_id, + )) + .unwrap(); + if let Some(run_command) = run_command { + pty.bus + .senders + .send_to_screen(ScreenInstruction::PtyBytes( + pid, + format!( + "Command not found: {}", + run_command.command.display() + ) + .as_bytes() + .to_vec(), + )) + .unwrap(); + pty.bus + .senders + .send_to_screen(ScreenInstruction::HoldPane( + PaneId::Terminal(pid), + Some(2), // exit status + run_command, + None, + )) + .unwrap(); + } + } + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } }, PtyInstruction::SpawnTerminalHorizontally(terminal_action, client_id) => { - let pid = pty - .spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) - .unwrap(); // TODO: handle error here - pty.bus - .senders - .send_to_screen(ScreenInstruction::HorizontalSplit( - PaneId::Terminal(pid), - client_id, - )) - .unwrap(); + let (hold_on_close, run_command, pane_title) = match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => ( + run_command.hold_on_close, + Some(run_command.clone()), + Some(run_command.to_string()), + ), + _ => (false, None, None), + }; + match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) { + Ok(pid) => { + pty.bus + .senders + .send_to_screen(ScreenInstruction::HorizontalSplit( + PaneId::Terminal(pid), + pane_title, + client_id, + )) + .unwrap(); + }, + Err(SpawnTerminalError::CommandNotFound(pid)) => { + if hold_on_close { + pty.bus + .senders + .send_to_screen(ScreenInstruction::HorizontalSplit( + PaneId::Terminal(pid), + pane_title, + client_id, + )) + .unwrap(); + if let Some(run_command) = run_command { + pty.bus + .senders + .send_to_screen(ScreenInstruction::PtyBytes( + pid, + format!( + "Command not found: {}", + run_command.command.display() + ) + .as_bytes() + .to_vec(), + )) + .unwrap(); + pty.bus + .senders + .send_to_screen(ScreenInstruction::HoldPane( + PaneId::Terminal(pid), + Some(2), // exit status + run_command, + None, + )) + .unwrap(); + } + } + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } }, PtyInstruction::UpdateActivePane(pane_id, client_id) => { pty.set_active_pane(pane_id, client_id); @@ -175,6 +314,36 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) { .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); }, + PtyInstruction::ReRunCommandInPane(pane_id, run_command) => { + match pty.rerun_command_in_pane(pane_id, run_command.clone()) { + Ok(..) => {}, + Err(SpawnTerminalError::CommandNotFound(pid)) => { + if run_command.hold_on_close { + pty.bus + .senders + .send_to_screen(ScreenInstruction::PtyBytes( + pid, + format!("Command not found: {}", run_command.command.display()) + .as_bytes() + .to_vec(), + )) + .unwrap(); + pty.bus + .senders + .send_to_screen(ScreenInstruction::HoldPane( + PaneId::Terminal(pid), + Some(2), // exit status + run_command, + None, + )) + .unwrap(); + } + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } + }, PtyInstruction::Exit => break, } } @@ -200,6 +369,7 @@ impl Pty { args: vec![], command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), cwd: None, // this should be filled by the calling function, eg. spawn_terminal + hold_on_close: false, }) } fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) { @@ -225,7 +395,8 @@ impl Pty { &mut self, terminal_action: Option, client_or_tab_index: ClientOrTabIndex, - ) -> Result { + ) -> Result { + // returns the terminal id let terminal_action = match client_or_tab_index { ClientOrTabIndex::ClientId(client_id) => { let mut terminal_action = @@ -237,13 +408,26 @@ impl Pty { terminal_action.unwrap_or_else(|| self.get_default_terminal()) }, }; + let hold_on_close = match &terminal_action { + TerminalAction::RunCommand(run_command) => run_command.hold_on_close, + _ => false, + }; let quit_cb = Box::new({ let senders = self.bus.senders.clone(); - move |pane_id| { - let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + move |pane_id, exit_status, command| { + if hold_on_close { + let _ = senders.send_to_screen(ScreenInstruction::HoldPane( + pane_id, + exit_status, + command, + None, + )); + } else { + let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } } }); - let (pid_primary, child_fd): (RawFd, RawFd) = self + let (terminal_id, pid_primary, child_fd): (u32, RawFd, RawFd) = self .bus .os_input .as_mut() @@ -254,15 +438,15 @@ impl Pty { let os_input = self.bus.os_input.as_ref().unwrap().clone(); let debug_to_file = self.debug_to_file; async move { - TerminalBytes::new(pid_primary, senders, os_input, debug_to_file) + TerminalBytes::new(pid_primary, senders, os_input, debug_to_file, terminal_id) .listen() .await; } }); - self.task_handles.insert(pid_primary, terminal_bytes); - self.id_to_child_pid.insert(pid_primary, child_fd); - Ok(pid_primary) + self.task_handles.insert(terminal_id, terminal_bytes); + self.id_to_child_pid.insert(terminal_id, child_fd); + Ok(terminal_id) } pub fn spawn_terminals_for_layout( &mut self, @@ -273,77 +457,155 @@ impl Pty { let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); self.fill_cwd(&mut default_shell, client_id); let extracted_run_instructions = layout.extract_run_instructions(); - let mut new_pane_pids = vec![]; + let mut new_pane_pids: Vec<(u32, Option, Result)> = + vec![]; // (terminal_id, + // run_command + // file_descriptor) for run_instruction in extracted_run_instructions { let quit_cb = Box::new({ let senders = self.bus.senders.clone(); - move |pane_id| { + move |pane_id, _exit_status, _command| { let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); } }); match run_instruction { Some(Run::Command(command)) => { - let cmd = TerminalAction::RunCommand(command); - let (pid_primary, child_fd): (RawFd, RawFd) = self - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(cmd, quit_cb, self.default_editor.clone()) - .unwrap(); // TODO: handle error here - self.id_to_child_pid.insert(pid_primary, child_fd); - new_pane_pids.push(pid_primary); + let hold_on_close = command.hold_on_close; + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id, exit_status, command| { + if hold_on_close { + let _ = senders.send_to_screen(ScreenInstruction::HoldPane( + pane_id, + exit_status, + command, + None, + )); + } else { + let _ = senders + .send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } + } + }); + let cmd = TerminalAction::RunCommand(command.clone()); + match self.bus.os_input.as_mut().unwrap().spawn_terminal( + cmd, + quit_cb, + self.default_editor.clone(), + ) { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + new_pane_pids.push(( + terminal_id, + Some(command.clone()), + Ok(pid_primary), + )); + }, + Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { + new_pane_pids.push(( + terminal_id, + Some(command.clone()), + Err(SpawnTerminalError::CommandNotFound(terminal_id)), + )); + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } }, None => { - let (pid_primary, child_fd): (RawFd, RawFd) = self - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone()) - .unwrap(); // TODO: handle error here - self.id_to_child_pid.insert(pid_primary, child_fd); - new_pane_pids.push(pid_primary); + match self.bus.os_input.as_mut().unwrap().spawn_terminal( + default_shell.clone(), + quit_cb, + self.default_editor.clone(), + ) { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + new_pane_pids.push((terminal_id, None, Ok(pid_primary))); + }, + Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { + new_pane_pids.push(( + terminal_id, + None, + Err(SpawnTerminalError::CommandNotFound(terminal_id)), + )); + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } }, // Investigate moving plugin loading to here. Some(Run::Plugin(_)) => {}, } } + let new_tab_pane_ids: Vec = new_pane_pids + .iter() + .map(|(terminal_id, _, _)| *terminal_id) + .collect::>(); self.bus .senders .send_to_screen(ScreenInstruction::NewTab( layout, - new_pane_pids.clone(), + new_tab_pane_ids, client_id, )) .unwrap(); - for id in new_pane_pids { - let terminal_bytes = task::spawn({ - let senders = self.bus.senders.clone(); - let os_input = self.bus.os_input.as_ref().unwrap().clone(); - let debug_to_file = self.debug_to_file; - async move { - TerminalBytes::new(id, senders, os_input, debug_to_file) - .listen() - .await; - } - }); - self.task_handles.insert(id, terminal_bytes); + for (terminal_id, run_command, pid_primary) in new_pane_pids { + match pid_primary { + Ok(pid_primary) => { + let terminal_bytes = task::spawn({ + let senders = self.bus.senders.clone(); + let os_input = self.bus.os_input.as_ref().unwrap().clone(); + let debug_to_file = self.debug_to_file; + async move { + TerminalBytes::new( + pid_primary, + senders, + os_input, + debug_to_file, + terminal_id, + ) + .listen() + .await; + } + }); + self.task_handles.insert(terminal_id, terminal_bytes); + }, + _ => match run_command { + Some(run_command) => { + if run_command.hold_on_close { + send_command_not_found_to_screen( + self.bus.senders.clone(), + terminal_id, + run_command.clone(), + ); + } else { + self.close_pane(PaneId::Terminal(terminal_id)); + } + }, + None => { + self.close_pane(PaneId::Terminal(terminal_id)); + }, + }, + } } } pub fn close_pane(&mut self, id: PaneId) { match id { PaneId::Terminal(id) => { - let child_fd = self.id_to_child_pid.remove(&id).unwrap(); - self.task_handles.remove(&id).unwrap(); - task::block_on(async { - self.bus - .os_input - .as_mut() - .unwrap() - .kill(Pid::from_raw(child_fd)) - .unwrap(); - }); + self.task_handles.remove(&id); + if let Some(child_fd) = self.id_to_child_pid.remove(&id) { + task::block_on(async { + self.bus + .os_input + .as_mut() + .unwrap() + .kill(Pid::from_raw(child_fd)) + .unwrap(); + }); + } + self.bus.os_input.as_ref().unwrap().clear_terminal_id(id); }, PaneId::Plugin(pid) => drop( self.bus @@ -362,13 +624,84 @@ impl Pty { self.active_panes.insert(client_id, pane_id); } } + pub fn rerun_command_in_pane( + &mut self, + pane_id: PaneId, + run_command: RunCommand, + ) -> Result<(), SpawnTerminalError> { + match pane_id { + PaneId::Terminal(id) => { + let _ = self.task_handles.remove(&id); // if all is well, this shouldn't be here + let _ = self.id_to_child_pid.remove(&id); // if all is wlel, this shouldn't be here + + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id, exit_status, command| { + // we only re-run held panes, so we'll never close them from Pty + let _ = senders.send_to_screen(ScreenInstruction::HoldPane( + pane_id, + exit_status, + command, + None, + )); + } + }); + let (pid_primary, child_fd): (RawFd, RawFd) = + self.bus + .os_input + .as_mut() + .unwrap() + .re_run_command_in_terminal(id, run_command, quit_cb)?; + let terminal_bytes = task::spawn({ + let senders = self.bus.senders.clone(); + let os_input = self.bus.os_input.as_ref().unwrap().clone(); + let debug_to_file = self.debug_to_file; + async move { + TerminalBytes::new(pid_primary, senders, os_input, debug_to_file, id) + .listen() + .await; + } + }); + + self.task_handles.insert(id, terminal_bytes); + self.id_to_child_pid.insert(id, child_fd); + Ok(()) + }, + _ => Err(SpawnTerminalError::GenericSpawnError( + "Cannot respawn plugin panes", + )), + } + } } impl Drop for Pty { fn drop(&mut self) { - let child_ids: Vec = self.id_to_child_pid.keys().copied().collect(); + let child_ids: Vec = self.id_to_child_pid.keys().copied().collect(); for id in child_ids { self.close_pane(PaneId::Terminal(id)); } } } + +fn send_command_not_found_to_screen( + senders: ThreadSenders, + terminal_id: u32, + run_command: RunCommand, +) { + senders + .send_to_screen(ScreenInstruction::PtyBytes( + terminal_id, + format!("Command not found: {}", run_command.command.display()) + .as_bytes() + .to_vec(), + )) + .unwrap(); + senders + .send_to_screen(ScreenInstruction::HoldPane( + PaneId::Terminal(terminal_id), + Some(2), + run_command.clone(), + None, + )) + .unwrap(); +} diff --git a/zellij-server/src/pty_writer.rs b/zellij-server/src/pty_writer.rs index 089dfc75..353849b5 100644 --- a/zellij-server/src/pty_writer.rs +++ b/zellij-server/src/pty_writer.rs @@ -4,7 +4,7 @@ use crate::thread_bus::Bus; #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum PtyWriteInstruction { - Write(Vec, i32), + Write(Vec, u32), Exit, } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 75230488..fc8722b4 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -245,29 +245,17 @@ pub(crate) fn route_action( PtyInstruction::SpawnTerminalHorizontally(shell, client_id) }, // No direction specified - try to put it in the biggest available spot - None => PtyInstruction::SpawnTerminal(shell, ClientOrTabIndex::ClientId(client_id)), + None => PtyInstruction::SpawnTerminal( + shell, + None, + ClientOrTabIndex::ClientId(client_id), + ), }; session.senders.send_to_pty(pty_instr).unwrap(); }, Action::EditFile(path_to_file, line_number, split_direction, should_float) => { - match should_float { - Some(true) => { - session - .senders - .send_to_screen(ScreenInstruction::ShowFloatingPanes(client_id)) - .unwrap(); - }, - Some(false) => { - session - .senders - .send_to_screen(ScreenInstruction::HideFloatingPanes(client_id)) - .unwrap(); - }, - None => {}, - }; - let open_file = TerminalAction::OpenFile(path_to_file, line_number); - let pty_instr = match (split_direction, should_float.unwrap_or(false)) { + let pty_instr = match (split_direction, should_float) { (Some(Direction::Left), false) => { PtyInstruction::SpawnTerminalVertically(Some(open_file), client_id) }, @@ -283,6 +271,7 @@ pub(crate) fn route_action( // No direction specified or should float - defer placement to screen (None, _) | (_, true) => PtyInstruction::SpawnTerminal( Some(open_file), + Some(should_float), ClientOrTabIndex::ClientId(client_id), ), }; @@ -308,10 +297,7 @@ pub(crate) fn route_action( .unwrap(); }, Action::NewFloatingPane(run_command) => { - session - .senders - .send_to_screen(ScreenInstruction::ShowFloatingPanes(client_id)) - .unwrap(); + let should_float = true; let run_cmd = run_command .map(|cmd| TerminalAction::RunCommand(cmd.into())) .or_else(|| session.default_shell.clone()); @@ -319,15 +305,13 @@ pub(crate) fn route_action( .senders .send_to_pty(PtyInstruction::SpawnTerminal( run_cmd, + Some(should_float), ClientOrTabIndex::ClientId(client_id), )) .unwrap(); }, Action::NewTiledPane(direction, run_command) => { - session - .senders - .send_to_screen(ScreenInstruction::HideFloatingPanes(client_id)) - .unwrap(); + let should_float = false; let run_cmd = run_command .map(|cmd| TerminalAction::RunCommand(cmd.into())) .or_else(|| session.default_shell.clone()); @@ -345,9 +329,11 @@ pub(crate) fn route_action( PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id) }, // No direction specified - try to put it in the biggest available spot - None => { - PtyInstruction::SpawnTerminal(run_cmd, ClientOrTabIndex::ClientId(client_id)) - }, + None => PtyInstruction::SpawnTerminal( + run_cmd, + Some(should_float), + ClientOrTabIndex::ClientId(client_id), + ), }; session.senders.send_to_pty(pty_instr).unwrap(); }, @@ -394,9 +380,11 @@ pub(crate) fn route_action( PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id) }, // No direction specified - try to put it in the biggest available spot - None => { - PtyInstruction::SpawnTerminal(run_cmd, ClientOrTabIndex::ClientId(client_id)) - }, + None => PtyInstruction::SpawnTerminal( + run_cmd, + None, + ClientOrTabIndex::ClientId(client_id), + ), }; session.senders.send_to_pty(pty_instr).unwrap(); }, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index f4bc2904..3ab1521a 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -2,11 +2,11 @@ use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; -use std::os::unix::io::RawFd; use std::rc::Rc; use std::str; use zellij_utils::errors::prelude::*; +use zellij_utils::input::command::RunCommand; use zellij_utils::input::options::Clipboard; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::{input::command::TerminalAction, input::layout::PaneLayout, position::Position}; @@ -115,16 +115,16 @@ macro_rules! active_tab_and_connected_client_id { /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] pub enum ScreenInstruction { - PtyBytes(RawFd, VteBytes), + PtyBytes(u32, VteBytes), Render, - NewPane(PaneId, ClientOrTabIndex), + NewPane(PaneId, Option, Option, ClientOrTabIndex), // String is initial title, + // bool (if Some) is + // should_float OpenInPlaceEditor(PaneId, ClientId), TogglePaneEmbedOrFloating(ClientId), ToggleFloatingPanes(ClientId, Option), - ShowFloatingPanes(ClientId), - HideFloatingPanes(ClientId), - HorizontalSplit(PaneId, ClientId), - VerticalSplit(PaneId, ClientId), + HorizontalSplit(PaneId, Option, ClientId), // String is initial title + VerticalSplit(PaneId, Option, ClientId), // String is initial title WriteCharacter(Vec, ClientId), ResizeLeft(ClientId), ResizeRight(ClientId), @@ -164,9 +164,10 @@ pub enum ScreenInstruction { TogglePaneFrames, SetSelectable(PaneId, bool, usize), ClosePane(PaneId, Option), + HoldPane(PaneId, Option, RunCommand, Option), // Option is the exit status UpdatePaneName(Vec, ClientId), UndoRenamePane(ClientId), - NewTab(PaneLayout, Vec, ClientId), + NewTab(PaneLayout, Vec, ClientId), SwitchTabNext(ClientId), SwitchTabPrev(ClientId), ToggleActiveSyncTab(ClientId), @@ -217,8 +218,6 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenContext::TogglePaneEmbedOrFloating }, ScreenInstruction::ToggleFloatingPanes(..) => ScreenContext::ToggleFloatingPanes, - ScreenInstruction::ShowFloatingPanes(..) => ScreenContext::ShowFloatingPanes, - ScreenInstruction::HideFloatingPanes(..) => ScreenContext::HideFloatingPanes, ScreenInstruction::HorizontalSplit(..) => ScreenContext::HorizontalSplit, ScreenInstruction::VerticalSplit(..) => ScreenContext::VerticalSplit, ScreenInstruction::WriteCharacter(..) => ScreenContext::WriteCharacter, @@ -264,6 +263,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane, + ScreenInstruction::HoldPane(..) => ScreenContext::HoldPane, ScreenInstruction::UpdatePaneName(..) => ScreenContext::UpdatePaneName, ScreenInstruction::UndoRenamePane(..) => ScreenContext::UndoRenamePane, ScreenInstruction::NewTab(..) => ScreenContext::NewTab, @@ -811,7 +811,7 @@ impl Screen { pub fn new_tab( &mut self, layout: PaneLayout, - new_pids: Vec, + new_ids: Vec, client_id: ClientId, ) -> Result<()> { let client_id = if self.get_active_tab(client_id).is_some() { @@ -821,7 +821,7 @@ impl Screen { } else { client_id }; - let err_context = || format!("failed to create new tab for client {client_id:?}"); + let err_context = || format!("failed to create new tab for client {client_id:?}",); let tab_index = self.get_new_tab_index(); let position = self.tabs.len(); let mut tab = Tab::new( @@ -848,7 +848,7 @@ impl Screen { self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), ); - tab.apply_layout(layout, new_pids, tab_index, client_id) + tab.apply_layout(layout, new_ids, tab_index, client_id) .with_context(err_context)?; if self.session_is_mirrored { if let Some(active_tab) = self.get_active_tab_mut(client_id) { @@ -1002,9 +1002,8 @@ impl Screen { } }, } - self.update_tabs().with_context(|| { - format!("failed to update active tabs name for client id: {client_id:?}") - }) + self.update_tabs() + .context("failed to update active tabs name for client id: {client_id:?}") } else { log::error!("Active tab not found for client id: {client_id:?}"); Ok(()) @@ -1092,7 +1091,6 @@ impl Screen { } Ok(()) } - pub fn change_mode_for_all_clients(&mut self, mode_info: ModeInfo) -> Result<()> { let err_context = || { format!( @@ -1225,17 +1223,24 @@ pub(crate) fn screen_thread_main( ScreenInstruction::Render => { screen.render()?; }, - ScreenInstruction::NewPane(pid, client_or_tab_index) => { + ScreenInstruction::NewPane( + pid, + initial_pane_title, + should_float, + client_or_tab_index, + ) => { match client_or_tab_index { ClientOrTabIndex::ClientId(client_id) => { active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab .new_pane(pid, + initial_pane_title, + should_float, Some(client_id)), ?); }, ClientOrTabIndex::TabIndex(tab_index) => { if let Some(active_tab) = screen.tabs.get_mut(&tab_index) { - active_tab.new_pane(pid, None)?; + active_tab.new_pane(pid, initial_pane_title, should_float, None)?; } else { log::error!("Tab index not found: {:?}", tab_index); } @@ -1270,44 +1275,22 @@ pub(crate) fn screen_thread_main( screen.render()?; }, - ScreenInstruction::ShowFloatingPanes(client_id) => { + ScreenInstruction::HorizontalSplit(pid, initial_pane_title, client_id) => { active_tab_and_connected_client_id!( screen, client_id, - |tab: &mut Tab, _client_id: ClientId| tab.show_floating_panes() - ); - screen.unblock_input()?; - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins - - screen.render()?; - }, - ScreenInstruction::HideFloatingPanes(client_id) => { - active_tab_and_connected_client_id!( - screen, - client_id, - |tab: &mut Tab, _client_id: ClientId| tab.hide_floating_panes() - ); - screen.unblock_input()?; - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins - - screen.render()?; - }, - ScreenInstruction::HorizontalSplit(pid, client_id) => { - active_tab_and_connected_client_id!( - screen, - client_id, - |tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, client_id), + |tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, initial_pane_title, client_id), ? ); screen.unblock_input()?; screen.update_tabs()?; screen.render()?; }, - ScreenInstruction::VerticalSplit(pid, client_id) => { + ScreenInstruction::VerticalSplit(pid, initial_pane_title, client_id) => { active_tab_and_connected_client_id!( screen, client_id, - |tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, client_id), + |tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, initial_pane_title, client_id), ? ); screen.unblock_input()?; @@ -1657,6 +1640,27 @@ pub(crate) fn screen_thread_main( screen.update_tabs()?; screen.unblock_input()?; }, + ScreenInstruction::HoldPane(id, exit_status, run_command, client_id) => { + match client_id { + Some(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab.hold_pane( + id, + exit_status, + run_command + )); + }, + None => { + for tab in screen.tabs.values_mut() { + if tab.get_all_pane_ids().contains(&id) { + tab.hold_pane(id, exit_status, run_command); + break; + } + } + }, + } + screen.update_tabs()?; + screen.unblock_input()?; + }, ScreenInstruction::UpdatePaneName(c, client_id) => { active_tab_and_connected_client_id!( screen, diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index d80256aa..40879d37 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -8,6 +8,7 @@ use copy_command::CopyCommand; use std::env::temp_dir; use uuid::Uuid; use zellij_utils::errors::prelude::*; +use zellij_utils::input::command::RunCommand; use zellij_utils::position::{Column, Line}; use zellij_utils::{position::Position, serde}; @@ -29,7 +30,6 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use std::cell::RefCell; -use std::os::unix::io::RawFd; use std::rc::Rc; use std::sync::mpsc::channel; use std::time::Instant; @@ -50,9 +50,9 @@ use zellij_utils::{ macro_rules! resize_pty { ($pane:expr, $os_input:expr) => { if let PaneId::Terminal(ref pid) = $pane.pid() { - // FIXME: This `set_terminal_size_using_fd` call would be best in + // FIXME: This `set_terminal_size_using_terminal_id` call would be best in // `TerminalPane::reflow_lines` - $os_input.set_terminal_size_using_fd( + $os_input.set_terminal_size_using_terminal_id( *pid, $pane.get_content_columns() as u16, $pane.get_content_rows() as u16, @@ -89,7 +89,7 @@ pub(crate) struct Tab { pub style: Style, connected_clients: Rc>>, draw_pane_frames: bool, - pending_vte_events: HashMap>, + pending_vte_events: HashMap>, pub selecting_with_mouse: bool, // this is only pub for the tests TODO: remove this once we combine write_text_to_clipboard with render link_handler: Rc>, clipboard_provider: ClipboardProvider, @@ -100,6 +100,7 @@ pub(crate) struct Tab { last_mouse_hold_position: Option, terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, + pids_waiting_resize: HashSet, // u32 is the terminal_id } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -127,7 +128,9 @@ pub trait Pane { fn set_geom_override(&mut self, pane_geom: PaneGeom); fn handle_pty_bytes(&mut self, bytes: VteBytes); fn cursor_coordinates(&self) -> Option<(usize, usize)>; - fn adjust_input_to_terminal(&self, input_bytes: Vec) -> Vec; + fn adjust_input_to_terminal(&mut self, _input_bytes: Vec) -> Option { + None + } fn position_and_size(&self) -> PaneGeom; fn current_geom(&self) -> PaneGeom; fn geom_override(&self) -> Option; @@ -351,6 +354,16 @@ pub trait Pane { // False by default (only terminal-panes support alternate mode) false } + fn hold(&mut self, _exit_status: Option, _run_command: RunCommand) { + // No-op by default, only terminal panes support holding + } +} + +#[derive(Clone, Debug)] +pub enum AdjustedInput { + WriteBytesToTerminal(Vec), + ReRunCommandInThisPane(RunCommand), + CloseThisPane, } impl Tab { @@ -450,13 +463,14 @@ impl Tab { last_mouse_hold_position: None, terminal_emulator_colors, terminal_emulator_color_codes, + pids_waiting_resize: HashSet::new(), } } pub fn apply_layout( &mut self, layout: PaneLayout, - new_pids: Vec, + new_ids: Vec, tab_index: usize, client_id: ClientId, ) -> Result<()> { @@ -481,7 +495,7 @@ impl Tab { let positions_in_layout = layout.position_panes_in_space(&free_space); let positions_and_size = positions_in_layout.iter(); - let mut new_pids = new_pids.iter(); + let mut new_ids = new_ids.iter(); let mut focus_pane_id: Option = None; let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| { @@ -516,27 +530,33 @@ impl Tab { set_focus_pane_id(layout, PaneId::Plugin(pid)); } else { // there are still panes left to fill, use the pids we received in this method - let pid = new_pids.next().with_context(err_context)?; // if this crashes it means we got less pids than there are panes in this layout - let next_terminal_position = self.get_next_terminal_position(); - let mut new_pane = TerminalPane::new( - *pid, - *position_and_size, - self.style, - next_terminal_position, - layout.name.clone().unwrap_or_default(), - self.link_handler.clone(), - self.character_cell_size.clone(), - self.sixel_image_store.clone(), - self.terminal_emulator_colors.clone(), - self.terminal_emulator_color_codes.clone(), - ); - new_pane.set_borderless(layout.borderless); - self.tiled_panes - .add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane)); - set_focus_pane_id(layout, PaneId::Terminal(*pid)); + if let Some(pid) = new_ids.next() { + let next_terminal_position = self.get_next_terminal_position(); + let initial_title = match &layout.run { + Some(Run::Command(run_command)) => Some(run_command.to_string()), + _ => None, + }; + let mut new_pane = TerminalPane::new( + *pid, + *position_and_size, + self.style, + next_terminal_position, + layout.name.clone().unwrap_or_default(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + initial_title, + ); + new_pane.set_borderless(layout.borderless); + self.tiled_panes + .add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane)); + set_focus_pane_id(layout, PaneId::Terminal(*pid)); + } } } - for unused_pid in new_pids { + for unused_pid in new_ids { // this is a bit of a hack and happens because we don't have any central location that // can query the screen as to how many panes it needs to create a layout // fixing this will require a bit of an architecture change @@ -745,18 +765,10 @@ impl Tab { } }, None => { - // there aren't any floating panes, we need to open a new one - // - // ************************************************************************************************ - // BEWARE - THIS IS NOT ATOMIC - this sends an instruction to the pty thread to open a new terminal - // the pty thread will do its thing and eventually come back to the new_pane - // method on this tab which will open a new floating pane because we just - // toggled their visibility above us. - // If the pty thread takes too long, weird things can happen... - // ************************************************************************************************ - // + let should_float = true; let instruction = PtyInstruction::SpawnTerminal( default_shell, + Some(should_float), ClientOrTabIndex::ClientId(client_id), ); self.senders.send_to_pty(instruction).with_context(|| { @@ -769,18 +781,18 @@ impl Tab { self.set_force_render(); Ok(()) } - - pub fn show_floating_panes(&mut self) { - self.floating_panes.toggle_show_panes(true); - self.set_force_render(); - } - - pub fn hide_floating_panes(&mut self) { - self.floating_panes.toggle_show_panes(false); - self.set_force_render(); - } - - pub fn new_pane(&mut self, pid: PaneId, client_id: Option) -> Result<()> { + pub fn new_pane( + &mut self, + pid: PaneId, + initial_pane_title: Option, + should_float: Option, + client_id: Option, + ) -> Result<()> { + match should_float { + Some(true) => self.floating_panes.toggle_show_panes(true), + Some(false) => self.floating_panes.toggle_show_panes(false), + None => {}, + }; self.close_down_to_max_terminals() .with_context(|| format!("failed to create new pane with id {pid:?}"))?; if self.floating_panes.panes_are_visible() { @@ -798,6 +810,7 @@ impl Tab { self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), + initial_pane_title, ); new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame resize_pty!(new_pane, self.os_api); @@ -823,6 +836,7 @@ impl Tab { self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), + initial_pane_title, ); self.tiled_panes.insert_pane(pid, Box::new(new_terminal)); self.should_clear_display_before_rendering = true; @@ -841,7 +855,7 @@ impl Tab { match pid { PaneId::Terminal(pid) => { let next_terminal_position = self.get_next_terminal_position(); // TODO: this is not accurate in this case - let new_pane = TerminalPane::new( + let mut new_pane = TerminalPane::new( pid, PaneGeom::default(), // the initial size will be set later self.style, @@ -852,7 +866,11 @@ impl Tab { self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), + None, ); + new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the + // constructor so it won't be overrided + // by the editor let replaced_pane = if self.floating_panes.panes_are_visible() { self.floating_panes .replace_active_pane(Box::new(new_pane), client_id) @@ -882,11 +900,14 @@ impl Tab { } Ok(()) } - - pub fn horizontal_split(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> { + pub fn horizontal_split( + &mut self, + pid: PaneId, + initial_pane_title: Option, + client_id: ClientId, + ) -> Result<()> { let err_context = || format!("failed to split pane {pid:?} horizontally for client {client_id}"); - if self.floating_panes.panes_are_visible() { return Ok(()); } @@ -909,6 +930,7 @@ impl Tab { self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), + initial_pane_title, ); self.tiled_panes .split_pane_horizontally(pid, Box::new(new_terminal), client_id); @@ -918,11 +940,14 @@ impl Tab { } Ok(()) } - - pub fn vertical_split(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> { + pub fn vertical_split( + &mut self, + pid: PaneId, + initial_pane_title: Option, + client_id: ClientId, + ) -> Result<()> { let err_context = || format!("failed to split pane {pid:?} vertically for client {client_id}"); - if self.floating_panes.panes_are_visible() { return Ok(()); } @@ -945,6 +970,7 @@ impl Tab { self.sixel_image_store.clone(), self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), + initial_pane_title, ); self.tiled_panes .split_pane_vertically(pid, Box::new(new_terminal), client_id); @@ -990,14 +1016,14 @@ impl Tab { self.tiled_panes.get_active_pane_id(client_id) } } - fn get_active_terminal_id(&self, client_id: ClientId) -> Option { + fn get_active_terminal_id(&self, client_id: ClientId) -> Option { if let Some(PaneId::Terminal(pid)) = self.get_active_pane_id(client_id) { Some(pid) } else { None } } - pub fn has_terminal_pid(&self, pid: RawFd) -> bool { + pub fn has_terminal_pid(&self, pid: u32) -> bool { self.tiled_panes.panes_contain(&PaneId::Terminal(pid)) || self.floating_panes.panes_contain(&PaneId::Terminal(pid)) || self @@ -1005,9 +1031,8 @@ impl Tab { .values() .any(|s_p| s_p.pid() == PaneId::Terminal(pid)) } - pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) -> Result<()> { + pub fn handle_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> { let err_context = || format!("failed to handle pty bytes from fd {pid}"); - if let Some(terminal_output) = self .tiled_panes .get_pane_mut(PaneId::Terminal(pid)) @@ -1034,8 +1059,7 @@ impl Tab { } self.process_pty_bytes(pid, bytes).with_context(err_context) } - - pub fn process_pending_vte_events(&mut self, pid: RawFd) -> Result<()> { + pub fn process_pending_vte_events(&mut self, pid: u32) -> Result<()> { if let Some(pending_vte_events) = self.pending_vte_events.get_mut(&pid) { let vte_events: Vec = pending_vte_events.drain(..).collect(); for vte_event in vte_events { @@ -1045,10 +1069,8 @@ impl Tab { } Ok(()) } - - fn process_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) -> Result<()> { + fn process_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> { let err_context = || format!("failed to process pty bytes from pid {pid}"); - if let Some(terminal_output) = self .tiled_panes .get_pane_mut(PaneId::Terminal(pid)) @@ -1059,6 +1081,9 @@ impl Tab { .find(|s_p| s_p.pid() == PaneId::Terminal(pid)) }) { + if self.pids_waiting_resize.remove(&pid) { + resize_pty!(terminal_output, self.os_api); + } terminal_output.handle_pty_bytes(bytes); let messages_to_pty = terminal_output.drain_messages_to_pty(); let clipboard_update = terminal_output.drain_clipboard_update(); @@ -1149,19 +1174,34 @@ impl Tab { PaneId::Terminal(active_terminal_id) => { let active_terminal = self .floating_panes - .get(&pane_id) - .or_else(|| self.tiled_panes.get_pane(pane_id)) - .or_else(|| self.suppressed_panes.get(&pane_id)) + .get_mut(&pane_id) + .or_else(|| self.tiled_panes.get_pane_mut(pane_id)) + .or_else(|| self.suppressed_panes.get_mut(&pane_id)) .ok_or_else(|| anyhow!(format!("failed to find pane with id {pane_id:?}"))) .with_context(err_context)?; - let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); - - self.senders - .send_to_pty_writer(PtyWriteInstruction::Write( - adjusted_input, - active_terminal_id, - )) - .with_context(err_context)?; + match active_terminal.adjust_input_to_terminal(input_bytes) { + Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => { + self.senders + .send_to_pty_writer(PtyWriteInstruction::Write( + adjusted_input, + active_terminal_id, + )) + .with_context(err_context)?; + }, + Some(AdjustedInput::ReRunCommandInThisPane(command)) => { + self.pids_waiting_resize.insert(active_terminal_id); + self.senders + .send_to_pty(PtyInstruction::ReRunCommandInPane( + PaneId::Terminal(active_terminal_id), + command, + )) + .with_context(err_context)?; + }, + Some(AdjustedInput::CloseThisPane) => { + self.close_pane(PaneId::Terminal(active_terminal_id), false); + }, + None => {}, + } }, PaneId::Plugin(pid) => { for key in parse_keys(&input_bytes) { @@ -1686,6 +1726,13 @@ impl Tab { closed_pane } } + pub fn hold_pane(&mut self, id: PaneId, exit_status: Option, run_command: RunCommand) { + if self.floating_panes.panes_contain(&id) { + self.floating_panes.hold_pane(id, exit_status, run_command); + } else { + self.tiled_panes.hold_pane(id, exit_status, run_command); + } + } pub fn replace_pane_with_suppressed_pane(&mut self, pane_id: PaneId) -> Option> { self.suppressed_panes .remove(&pane_id) @@ -2335,7 +2382,6 @@ impl Tab { let err_context = || { format!("failed to handle left mouse hold at position {position_on_screen:?} for client {client_id}") }; - // return value indicates whether we should trigger a render // determine if event is repeated to enable smooth scrolling let is_repeated = if let Some(last_position) = self.last_mouse_hold_position { diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_floting_pane_is_suppressed.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_floting_pane_is_suppressed.snap index a339044f..d4e106aa 100644 --- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_floting_pane_is_suppressed.snap +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_floting_pane_is_suppressed.snap @@ -1,9 +1,9 @@ --- source: zellij-server/src/tab/./unit/tab_integration_tests.rs -assertion_line: 1474 +assertion_line: 1801 expression: snapshot --- -00 (C): ┌ Pane #1 ────────────────────┌ Pane #3 ─────────────────────────────────────────────────┐─────────┐ +00 (C): ┌ Pane #1 ────────────────────┌ EDITING SCROLLBACK ──────────────────────────────────────┐─────────┐ 01 (C): │ │ │ │ 02 (C): │ │ │ │ 03 (C): │ │ │ │ diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_tiled_pane_is_suppressed.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_tiled_pane_is_suppressed.snap index 8d17504f..6e05157f 100644 --- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_tiled_pane_is_suppressed.snap +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__resize_whole_tab_while_tiled_pane_is_suppressed.snap @@ -1,9 +1,9 @@ --- source: zellij-server/src/tab/./unit/tab_integration_tests.rs -assertion_line: 1444 +assertion_line: 1771 expression: snapshot --- -00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────────┐ +00 (C): ┌ EDITING SCROLLBACK ──────────────────────────────────────────────────────────────────────────────┐ 01 (C): │ │ 02 (C): │ │ 03 (C): │ │ diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane.snap index 2b7d6a21..6ac31044 100644 --- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane.snap +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_floating_pane.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/tab/./unit/tab_integration_tests.rs -assertion_line: 1288 +assertion_line: 1639 expression: snapshot --- 00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -8,7 +8,7 @@ expression: snapshot 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ ┌ Pane #3 ─────────────────────────────────────────────────┐ │ +05 (C): │ ┌ EDITING SCROLLBACK ──────────────────────────────────────┐ │ 06 (C): │ │ │ │ 07 (C): │ │ │ │ 08 (C): │ │ │ │ diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane.snap index 9e91a126..005592f6 100644 --- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane.snap +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__suppress_tiled_pane.snap @@ -1,9 +1,9 @@ --- source: zellij-server/src/tab/./unit/tab_integration_tests.rs -assertion_line: 1259 +assertion_line: 1613 expression: snapshot --- -00 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +00 (C): ┌ EDITING SCROLLBACK ───────────────────────────────────────────────────────────────────────────────────────────────────┐ 01 (C): │ │ 02 (C): │ │ 03 (C): │ │ diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 632b65b1..f1efe339 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -4,7 +4,7 @@ use crate::screen::CopyOptions; use crate::Arc; use crate::Mutex; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, Pid, ServerOsApi, SpawnTerminalError}, panes::PaneId, thread_bus::ThreadSenders, ClientId, @@ -30,7 +30,7 @@ use zellij_utils::nix; use zellij_utils::{ data::{InputMode, ModeInfo, Palette, Style}, - input::command::TerminalAction, + input::command::{RunCommand, TerminalAction}, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, }; @@ -41,15 +41,15 @@ struct FakeInputOutput { } impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { + fn set_terminal_size_using_terminal_id(&self, _id: u32, _cols: u16, _rows: u16) { // noop } fn spawn_terminal( &self, _file_to_open: TerminalAction, - _quit_cb: Box, + _quit_db: Box, RunCommand) + Send>, _default_editor: Option, - ) -> Result<(RawFd, RawFd), &'static str> { + ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -58,10 +58,10 @@ impl ServerOsApi for FakeInputOutput { fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result { + fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result { unimplemented!() } - fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { + fn tcdrain(&self, _id: u32) -> Result<(), nix::Error> { unimplemented!() } fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { @@ -103,6 +103,17 @@ impl ServerOsApi for FakeInputOutput { }; self.file_dumps.lock().unwrap().insert(f, buf); } + fn re_run_command_in_terminal( + &self, + _terminal_id: u32, + _run_command: RunCommand, + _quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + ) -> Result<(RawFd, RawFd), SpawnTerminalError> { + unimplemented!() + } + fn clear_terminal_id(&self, _terminal_id: u32) { + unimplemented!() + } } struct MockPtyInstructionBus { @@ -266,7 +277,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) .extract_run_instructions() .iter() .enumerate() - .map(|(i, _)| i as i32) + .map(|(i, _)| i as u32) .collect(); tab.apply_layout(tab_layout, pane_ids, index, client_id) .unwrap(); @@ -491,7 +502,8 @@ fn dump_screen() { file_dumps: map.clone(), }); let new_pane_id = PaneId::Terminal(2); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes())) .unwrap(); let file = "/tmp/log.sh"; @@ -514,7 +526,8 @@ fn new_floating_pane() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -541,7 +554,8 @@ fn floating_panes_persist_across_toggles() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.toggle_floating_panes(client_id, None).unwrap(); // here we send bytes to the pane when it's not visible to make sure they're still handled and // we see them once we toggle the panes back @@ -572,7 +586,8 @@ fn toggle_floating_panes_off() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -600,7 +615,8 @@ fn toggle_floating_panes_on() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -633,11 +649,16 @@ fn five_new_floating_panes() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -672,7 +693,8 @@ fn increase_floating_pane_size() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -700,7 +722,8 @@ fn decrease_floating_pane_size() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -728,7 +751,8 @@ fn resize_floating_pane_left() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -756,7 +780,8 @@ fn resize_floating_pane_right() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -784,7 +809,8 @@ fn resize_floating_pane_up() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -812,7 +838,8 @@ fn resize_floating_pane_down() { let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -844,11 +871,16 @@ fn move_floating_pane_focus_left() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -894,11 +926,16 @@ fn move_floating_pane_focus_right() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -945,11 +982,16 @@ fn move_floating_pane_focus_up() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -995,11 +1037,16 @@ fn move_floating_pane_focus_down() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1046,11 +1093,16 @@ fn move_floating_pane_focus_with_mouse() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1099,11 +1151,16 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1152,11 +1209,16 @@ fn drag_pane_with_mouse() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1205,11 +1267,16 @@ fn mark_text_inside_floating_pane() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1266,11 +1333,16 @@ fn resize_tab_with_floating_panes() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1314,11 +1386,16 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() { let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1359,11 +1436,16 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_b let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id_1, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_2, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_3, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_4, Some(client_id)).unwrap(); - tab.new_pane(new_pane_id_5, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_3, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_4, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_5, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1404,7 +1486,8 @@ fn embed_floating_pane() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), @@ -1431,7 +1514,8 @@ fn float_embedded_pane() { let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am an embedded pane".as_bytes()), @@ -1533,7 +1617,8 @@ fn rename_floating_pane() { let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am a floating pane".as_bytes()), @@ -1618,7 +1703,8 @@ fn move_floating_pane_with_sixel_image() { let mut output = Output::new(sixel_image_store.clone(), character_cell_size); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(2, fixture).unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) @@ -1655,7 +1741,8 @@ fn floating_pane_above_sixel_image() { let mut output = Output::new(sixel_image_store.clone(), character_cell_size); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(1, fixture).unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) @@ -1711,7 +1798,8 @@ fn suppress_floating_pane() { let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); @@ -1764,7 +1852,8 @@ fn close_suppressing_floating_pane() { let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); @@ -1821,7 +1910,8 @@ fn suppress_floating_pane_embed_it_and_close_it() { let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); @@ -1879,7 +1969,8 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() { let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); @@ -1978,7 +2069,8 @@ fn enter_search_floating_pane() { let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None).unwrap(); - tab.new_pane(new_pane_id, Some(client_id)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(client_id)) + .unwrap(); let pane_content = read_fixture("grid_copy"); tab.handle_pty_bytes(2, pane_content).unwrap(); diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 3f20ded9..2b8d51a1 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -2,7 +2,7 @@ use super::Tab; use crate::panes::sixel::SixelImageStore; use crate::screen::CopyOptions; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, Pid, ServerOsApi, SpawnTerminalError}, panes::PaneId, thread_bus::ThreadSenders, ClientId, @@ -21,7 +21,7 @@ use zellij_utils::nix; use zellij_utils::{ data::{ModeInfo, Palette, Style}, - input::command::TerminalAction, + input::command::{RunCommand, TerminalAction}, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, }; @@ -30,15 +30,15 @@ use zellij_utils::{ struct FakeInputOutput {} impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { + fn set_terminal_size_using_terminal_id(&self, _id: u32, _cols: u16, _rows: u16) { // noop } fn spawn_terminal( &self, _file_to_open: TerminalAction, - _quit_cb: Box, + _quit_cb: Box, RunCommand) + Send>, _default_editor: Option, - ) -> Result<(RawFd, RawFd), &'static str> { + ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -47,10 +47,10 @@ impl ServerOsApi for FakeInputOutput { fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result { + fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result { unimplemented!() } - fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { + fn tcdrain(&self, _id: u32) -> Result<(), nix::Error> { unimplemented!() } fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { @@ -89,6 +89,17 @@ impl ServerOsApi for FakeInputOutput { fn write_to_file(&mut self, _buf: String, _name: Option) { unimplemented!() } + fn re_run_command_in_terminal( + &self, + _terminal_id: u32, + _run_command: RunCommand, + _quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + ) -> Result<(RawFd, RawFd), SpawnTerminalError> { + unimplemented!() + } + fn clear_terminal_id(&self, _terminal_id: u32) { + unimplemented!() + } } fn create_new_tab(size: Size) -> Tab { @@ -190,7 +201,7 @@ fn write_to_suppressed_pane() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); // Suppress pane 2 and remove it from active panes tab.suppress_active_pane(PaneId::Terminal(2), 1).unwrap(); @@ -211,7 +222,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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "The tab has two panes"); assert_eq!( tab.tiled_panes @@ -307,7 +318,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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "The tab has two panes"); assert_eq!( @@ -406,7 +417,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, Some(1)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(1)).unwrap(); } assert_eq!(tab.tiled_panes.panes.len(), 4, "The tab has four panes"); @@ -587,7 +598,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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); assert_eq!( tab.tiled_panes.panes.len(), 1, @@ -599,7 +610,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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); assert_eq!( tab.tiled_panes.panes.len(), 1, @@ -611,7 +622,8 @@ 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), Some(1)).unwrap(); + tab.new_pane(PaneId::Terminal(2), None, None, Some(1)) + .unwrap(); assert_eq!( tab.tiled_panes.panes.len(), 1, @@ -628,7 +640,7 @@ 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, Some(1)).unwrap(); + tab.new_pane(new_pane_id, None, None, Some(1)).unwrap(); } tab.toggle_active_pane_fullscreen(1); assert_eq!( @@ -701,10 +713,18 @@ fn switch_to_next_pane_fullscreen() { let mut active_tab = create_new_tab(size); - active_tab.new_pane(PaneId::Terminal(1), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(3), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(4), Some(1)).unwrap(); + active_tab + .new_pane(PaneId::Terminal(1), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(2), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(3), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(4), None, None, Some(1)) + .unwrap(); active_tab.toggle_active_pane_fullscreen(1); // order is now 1 ->2 -> 3 -> 4 due to how new panes are inserted @@ -733,10 +753,18 @@ fn switch_to_prev_pane_fullscreen() { //testing four consecutive switches in fullscreen mode - active_tab.new_pane(PaneId::Terminal(1), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(3), Some(1)).unwrap(); - active_tab.new_pane(PaneId::Terminal(4), Some(1)).unwrap(); + active_tab + .new_pane(PaneId::Terminal(1), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(2), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(3), None, None, Some(1)) + .unwrap(); + active_tab + .new_pane(PaneId::Terminal(4), None, None, Some(1)) + .unwrap(); active_tab.toggle_active_pane_fullscreen(1); // order is now 1 2 3 4 @@ -771,7 +799,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 1, "One pane left in tab"); @@ -836,7 +864,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_focus_up(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 1, "One pane left in tab"); @@ -899,7 +927,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 1, "One pane left in tab"); @@ -961,7 +989,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.move_focus_left(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 1, "One pane left in tab"); @@ -1027,9 +1055,9 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_2, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_down(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "Two panes left in tab"); @@ -1138,8 +1166,8 @@ 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, 1).unwrap(); - tab.vertical_split(new_pane_id_2, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_up(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "Two panes left in tab"); @@ -1248,9 +1276,9 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_right(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "Two panes left in tab"); @@ -1359,8 +1387,8 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_left(1); tab.close_focused_pane(1).unwrap(); assert_eq!(tab.tiled_panes.panes.len(), 2, "Two panes left in tab"); @@ -1474,19 +1502,19 @@ 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, 1).unwrap(); - tab.vertical_split(new_pane_id_2, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_left(1); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(new_pane_id_4, 1).unwrap(); + tab.horizontal_split(new_pane_id_4, None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(new_pane_id_5, 1).unwrap(); + tab.horizontal_split(new_pane_id_5, None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_up(1); tab.resize_down(1); - tab.vertical_split(new_pane_id_6, 1).unwrap(); + tab.vertical_split(new_pane_id_6, None, 1).unwrap(); tab.move_focus_down(1); tab.close_focused_pane(1).unwrap(); @@ -1774,18 +1802,18 @@ 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, 1).unwrap(); - tab.vertical_split(new_pane_id_2, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_left(1); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(new_pane_id_4, 1).unwrap(); + tab.horizontal_split(new_pane_id_4, None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(new_pane_id_5, 1).unwrap(); + tab.horizontal_split(new_pane_id_5, None, 1).unwrap(); tab.move_focus_left(1); tab.resize_up(1); - tab.vertical_split(new_pane_id_6, 1).unwrap(); + tab.vertical_split(new_pane_id_6, None, 1).unwrap(); tab.move_focus_up(1); tab.close_focused_pane(1).unwrap(); @@ -2075,21 +2103,21 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(new_pane_id_4, 1).unwrap(); + tab.vertical_split(new_pane_id_4, None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(new_pane_id_5, 1).unwrap(); + tab.vertical_split(new_pane_id_5, None, 1).unwrap(); 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).unwrap(); + tab.horizontal_split(new_pane_id_6, None, 1).unwrap(); tab.move_focus_right(1); tab.close_focused_pane(1).unwrap(); @@ -2379,20 +2407,20 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(new_pane_id_4, 1).unwrap(); + tab.vertical_split(new_pane_id_4, None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(new_pane_id_5, 1).unwrap(); + tab.vertical_split(new_pane_id_5, None, 1).unwrap(); 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).unwrap(); + tab.horizontal_split(new_pane_id_6, None, 1).unwrap(); tab.move_focus_left(1); tab.close_focused_pane(1).unwrap(); @@ -2666,7 +2694,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_down(1); @@ -2688,9 +2716,9 @@ 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, 1).unwrap(); - tab.vertical_split(new_pane_id_2, 1).unwrap(); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_down(1); @@ -2715,7 +2743,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_focus_up(1); assert_eq!( @@ -2736,10 +2764,10 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_2, 1).unwrap(); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_down(1); tab.move_focus_up(1); @@ -2764,7 +2792,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.move_focus_left(1); assert_eq!( @@ -2785,10 +2813,10 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_right(1); tab.move_focus_left(1); @@ -2813,7 +2841,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_right(1); @@ -2835,9 +2863,9 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_right(1); @@ -2862,7 +2890,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_focus_up(1); tab.move_active_pane_down(1); @@ -2889,9 +2917,9 @@ 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, 1).unwrap(); - tab.vertical_split(new_pane_id_2, 1).unwrap(); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_up(1); tab.move_active_pane_down(1); @@ -2921,7 +2949,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_active_pane_up(1); assert_eq!( @@ -2947,10 +2975,10 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_2, 1).unwrap(); - tab.vertical_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); + tab.vertical_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_down(1); tab.move_active_pane_up(1); @@ -2981,7 +3009,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.move_active_pane_left(1); assert_eq!( @@ -3007,10 +3035,10 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_right(1); tab.move_active_pane_left(1); @@ -3041,7 +3069,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id, None, 1).unwrap(); tab.move_focus_left(1); tab.move_active_pane_right(1); @@ -3068,9 +3096,9 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); - tab.horizontal_split(new_pane_id_3, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_3, None, 1).unwrap(); tab.move_focus_left(1); tab.move_active_pane_right(1); @@ -3108,7 +3136,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.resize_down(1); assert_eq!( @@ -3214,7 +3242,7 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id, None, 1).unwrap(); tab.move_focus_up(1); tab.resize_down(1); @@ -3327,8 +3355,8 @@ 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, 1).unwrap(); - tab.horizontal_split(new_pane_id_2, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); + tab.horizontal_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_up(1); tab.resize_down(1); @@ -3480,9 +3508,9 @@ 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, 1).unwrap(); + tab.horizontal_split(new_pane_id_1, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(new_pane_id_2, 1).unwrap(); + tab.vertical_split(new_pane_id_2, None, 1).unwrap(); tab.move_focus_down(1); tab.resize_down(1); @@ -3635,10 +3663,10 @@ 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, 1).unwrap(); - tab.vertical_split(focused_pane, 1).unwrap(); + tab.horizontal_split(pane_to_the_left, None, 1).unwrap(); + tab.vertical_split(focused_pane, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(pane_above, 1).unwrap(); + tab.vertical_split(pane_above, None, 1).unwrap(); tab.move_focus_down(1); tab.resize_down(1); @@ -3835,10 +3863,10 @@ 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, 1).unwrap(); - tab.vertical_split(pane_below, 1).unwrap(); + tab.horizontal_split(pane_below_and_left, None, 1).unwrap(); + tab.vertical_split(pane_below, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(focused_pane, 1).unwrap(); + tab.vertical_split(focused_pane, None, 1).unwrap(); tab.resize_down(1); assert_eq!( @@ -4034,10 +4062,10 @@ 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, 1).unwrap(); - tab.vertical_split(pane_to_the_right, 1).unwrap(); + tab.horizontal_split(focused_pane, None, 1).unwrap(); + tab.vertical_split(pane_to_the_right, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(pane_above_and_right, 1).unwrap(); + tab.vertical_split(pane_above_and_right, None, 1).unwrap(); tab.move_focus_down(1); tab.move_focus_left(1); tab.resize_down(1); @@ -4235,10 +4263,10 @@ 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, 1).unwrap(); - tab.vertical_split(pane_below_and_right, 1).unwrap(); + tab.horizontal_split(pane_below, None, 1).unwrap(); + tab.vertical_split(pane_below_and_right, None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(pane_to_the_right, 1).unwrap(); + tab.vertical_split(pane_to_the_right, None, 1).unwrap(); tab.move_focus_left(1); tab.resize_down(1); @@ -4431,12 +4459,12 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_down(1); tab.resize_down(1); @@ -4716,12 +4744,12 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_down(1); @@ -5000,16 +5028,16 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); - tab.vertical_split(PaneId::Terminal(7), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_down(1); @@ -5374,16 +5402,16 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_left(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(7), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_up(1); tab.move_focus_left(1); @@ -5748,7 +5776,7 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_down(1); @@ -5790,7 +5818,7 @@ pub fn resize_left_with_pane_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_left(1); assert_eq!( @@ -5893,7 +5921,7 @@ pub fn resize_left_with_pane_to_the_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_left(1); @@ -5998,8 +6026,8 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_left(1); @@ -6146,9 +6174,9 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); tab.resize_left(1); @@ -6296,10 +6324,10 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_left(1); @@ -6490,10 +6518,10 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_down(1); tab.move_focus_left(1); tab.resize_left(1); @@ -6685,10 +6713,10 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.resize_left(1); assert_eq!( @@ -6878,10 +6906,10 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_left(1); @@ -7074,13 +7102,13 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_left(1); @@ -7359,13 +7387,13 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_down(1); tab.move_focus_left(1); tab.resize_left(1); @@ -7645,17 +7673,17 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_down(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(7), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_left(1); @@ -8020,18 +8048,18 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_down(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(7), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(8), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_left(1); @@ -8391,7 +8419,7 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_left(1); assert_eq!( @@ -8432,7 +8460,7 @@ pub fn resize_right_with_pane_to_the_left() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_right(1); assert_eq!( @@ -8536,7 +8564,7 @@ pub fn resize_right_with_pane_to_the_right() { rows: 20, }; let mut tab = create_new_tab(size); - tab.vertical_split(PaneId::Terminal(2), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_right(1); @@ -8641,8 +8669,8 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_right(1); @@ -8790,9 +8818,9 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); tab.resize_right(1); @@ -8940,11 +8968,11 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(4), None, 1).unwrap(); tab.resize_right(1); assert_eq!( @@ -9133,11 +9161,11 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_right(1); @@ -9328,11 +9356,11 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_right(1); @@ -9523,11 +9551,11 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); - tab.horizontal_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_left(1); tab.resize_right(1); @@ -9721,13 +9749,13 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_right(1); @@ -10005,13 +10033,13 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_down(1); tab.move_focus_left(1); tab.resize_right(1); @@ -10290,17 +10318,17 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(7), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_right(1); @@ -10664,18 +10692,18 @@ 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), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_up(1); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(7), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(8), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_right(1); @@ -11034,7 +11062,7 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_right(1); assert_eq!( @@ -11076,7 +11104,7 @@ pub fn resize_up_with_pane_above() { rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_up(1); assert_eq!( @@ -11181,7 +11209,7 @@ pub fn resize_up_with_pane_below() { rows: 20, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_up(1); @@ -11290,8 +11318,8 @@ pub fn resize_up_with_panes_above_and_below() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2), 1).unwrap(); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_up(1); @@ -11439,9 +11467,9 @@ pub fn resize_up_with_multiple_panes_above() { rows: 30, }; let mut tab = create_new_tab(size); - tab.horizontal_split(PaneId::Terminal(2), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_down(1); tab.resize_up(1); @@ -11588,11 +11616,11 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.resize_up(1); assert_eq!( @@ -11783,11 +11811,11 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); tab.resize_up(1); @@ -11979,11 +12007,11 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_up(1); @@ -12175,11 +12203,11 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_up(1); tab.resize_up(1); @@ -12372,12 +12400,12 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_up(1); @@ -12655,12 +12683,12 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); tab.move_focus_up(1); tab.resize_up(1); @@ -12939,16 +12967,16 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_left(1); - tab.vertical_split(PaneId::Terminal(7), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_up(1); @@ -13312,17 +13340,17 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_up(1); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(4), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(4), None, 1).unwrap(); tab.move_focus_down(1); - tab.vertical_split(PaneId::Terminal(5), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(6), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(5), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(6), None, 1).unwrap(); tab.move_focus_up(1); tab.move_focus_left(1); - tab.vertical_split(PaneId::Terminal(7), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(8), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(7), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(8), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_up(1); @@ -13685,7 +13713,7 @@ 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), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(2), None, 1).unwrap(); tab.resize_down(1); assert_eq!( @@ -13742,7 +13770,7 @@ 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, 1).unwrap(); + tab.vertical_split(new_pane_id_1, None, 1).unwrap(); tab.resize_increase(1); // should behave like `resize_left_with_pane_to_the_left` @@ -13775,9 +13803,9 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_right(1); tab.resize_increase(1); @@ -13833,9 +13861,9 @@ 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), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); tab.move_focus_left(1); - tab.horizontal_split(PaneId::Terminal(3), 1).unwrap(); + tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap(); tab.resize_increase(1); assert_eq!( @@ -13889,8 +13917,8 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_increase(1); @@ -13945,8 +13973,8 @@ 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), 1).unwrap(); - tab.vertical_split(PaneId::Terminal(3), 1).unwrap(); + tab.vertical_split(PaneId::Terminal(2), None, 1).unwrap(); + tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap(); tab.move_focus_left(1); tab.resize_increase(1); diff --git a/zellij-server/src/terminal_bytes.rs b/zellij-server/src/terminal_bytes.rs index f716491e..4fa1e456 100644 --- a/zellij-server/src/terminal_bytes.rs +++ b/zellij-server/src/terminal_bytes.rs @@ -31,6 +31,7 @@ impl From> for ReadResult { pub(crate) struct TerminalBytes { pid: RawFd, + terminal_id: u32, senders: ThreadSenders, async_reader: Box, debug: bool, @@ -47,9 +48,11 @@ impl TerminalBytes { senders: ThreadSenders, os_input: Box, debug: bool, + terminal_id: u32, ) -> Self { TerminalBytes { pid, + terminal_id, senders, debug, async_reader: os_input.async_file_reader(pid), @@ -77,7 +80,6 @@ impl TerminalBytes { let mut buf = [0u8; 65536]; loop { match self.deadline_read(&mut buf).await { - // match deadline_read(async_reader.as_mut(), self.render_deadline, &mut buf).await { ReadResult::Ok(0) | ReadResult::Err(_) => break, // EOF or error ReadResult::Timeout => { let time_to_send_render = @@ -93,7 +95,7 @@ impl TerminalBytes { let _ = debug_to_file(bytes, self.pid); } self.async_send_to_screen(ScreenInstruction::PtyBytes( - self.pid, + self.terminal_id, bytes.to_vec(), )) .await; diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 8f25c98f..186d4d18 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -61,6 +61,12 @@ fn background_color(characters: &str, color: Option) -> Vec, pub is_main_client: bool, @@ -81,6 +87,7 @@ pub struct PaneFrame { pub is_main_client: bool, pub other_cursors_exist_in_session: bool, pub other_focused_clients: Vec, + exit_status: Option, } impl PaneFrame { @@ -100,8 +107,15 @@ impl PaneFrame { is_main_client: frame_params.is_main_client, other_focused_clients: frame_params.other_focused_clients, other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session, + exit_status: None, } } + pub fn add_exit_status(&mut self, exit_status: Option) { + self.exit_status = match exit_status { + Some(exit_status) => Some(ExitStatus::Code(exit_status)), + None => Some(ExitStatus::Exited), + }; + } fn client_cursor(&self, client_id: ClientId) -> Vec { let color = client_id_to_colors(client_id, self.style.colors); background_color(" ", color.map(|c| c.0)) @@ -594,6 +608,65 @@ impl PaneFrame { .or_else(|| Some(self.title_line_without_middle())) .unwrap() } + fn render_held_undertitle(&self) -> Vec { + let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners + let exit_status = self.exit_status.unwrap(); // unwrap is safe because we only call this if + + let (mut first_part, first_part_len) = self.first_held_title_part_full(exit_status); + let mut left_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color); + let mut right_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color); + if self.is_main_client { + let (mut second_part, second_part_len) = self.second_held_title_part_full(); + let full_text_len = first_part_len + second_part_len; + if full_text_len <= max_undertitle_length { + // render exit status and tips + let mut padding = String::new(); + for _ in full_text_len..max_undertitle_length { + padding.push_str(boundary_type::HORIZONTAL); + } + let mut ret = vec![]; + ret.append(&mut left_boundary); + ret.append(&mut first_part); + ret.append(&mut second_part); + ret.append(&mut foreground_color(&padding, self.color)); + ret.append(&mut right_boundary); + ret + } else if first_part_len <= max_undertitle_length { + // render only exit status + let mut padding = String::new(); + for _ in first_part_len..max_undertitle_length { + padding.push_str(boundary_type::HORIZONTAL); + } + let mut ret = vec![]; + ret.append(&mut left_boundary); + ret.append(&mut first_part); + ret.append(&mut foreground_color(&padding, self.color)); + ret.append(&mut right_boundary); + ret + } else { + self.empty_undertitle(max_undertitle_length) + } + } else { + if first_part_len <= max_undertitle_length { + // render first part + let full_text_len = first_part_len; + let mut padding = String::new(); + for _ in full_text_len..max_undertitle_length { + padding.push_str(boundary_type::HORIZONTAL); + } + let mut ret = vec![]; + ret.append(&mut left_boundary); + ret.append(&mut first_part); + ret.append(&mut foreground_color(&padding, self.color)); + ret.append(&mut right_boundary); + ret + } else { + self.empty_undertitle(max_undertitle_length) + } + } + } pub fn render(&self) -> (Vec, Option) { let mut character_chunks = vec![]; for row in 0..self.geom.rows { @@ -605,24 +678,30 @@ impl PaneFrame { character_chunks.push(CharacterChunk::new(title, x, y)); } else if row == self.geom.rows - 1 { // bottom row - let mut bottom_row = vec![]; - for col in 0..self.geom.cols { - let boundary = if col == 0 { - // bottom left corner - self.get_corner(boundary_type::BOTTOM_LEFT) - } else if col == self.geom.cols - 1 { - // bottom right corner - self.get_corner(boundary_type::BOTTOM_RIGHT) - } else { - boundary_type::HORIZONTAL - }; + if self.exit_status.is_some() { + let x = self.geom.x; + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new(self.render_held_undertitle(), x, y)); + } else { + let mut bottom_row = vec![]; + for col in 0..self.geom.cols { + let boundary = if col == 0 { + // bottom left corner + self.get_corner(boundary_type::BOTTOM_LEFT) + } else if col == self.geom.cols - 1 { + // bottom right corner + self.get_corner(boundary_type::BOTTOM_RIGHT) + } else { + boundary_type::HORIZONTAL + }; - let mut boundary_character = foreground_color(boundary, self.color); - bottom_row.append(&mut boundary_character); + let mut boundary_character = foreground_color(boundary, self.color); + bottom_row.append(&mut boundary_character); + } + let x = self.geom.x; + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new(bottom_row, x, y)); } - let x = self.geom.x; - let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new(bottom_row, x, y)); } else { let boundary_character_left = foreground_color(boundary_type::VERTICAL, self.color); let boundary_character_right = @@ -639,4 +718,106 @@ impl PaneFrame { } (character_chunks, None) } + fn first_held_title_part_full( + &self, + exit_status: ExitStatus, + ) -> (Vec, usize) { + // (title part, length) + match exit_status { + ExitStatus::Code(exit_code) => { + let mut first_part = vec![]; + let left_bracket = " [ "; + let exited_text = "EXIT CODE: "; + let exit_code_text = format!("{}", exit_code); + let exit_code_color = if exit_code == 0 { + self.style.colors.green + } else { + self.style.colors.red + }; + let right_bracket = " ] "; + first_part.append(&mut foreground_color(left_bracket, self.color)); + first_part.append(&mut foreground_color(exited_text, self.color)); + first_part.append(&mut foreground_color( + &exit_code_text, + Some(exit_code_color), + )); + first_part.append(&mut foreground_color(right_bracket, self.color)); + ( + first_part, + left_bracket.len() + + exited_text.len() + + exit_code_text.len() + + right_bracket.len(), + ) + }, + ExitStatus::Exited => { + let mut first_part = vec![]; + let left_bracket = " [ "; + let exited_text = "EXITED"; + let right_bracket = " ] "; + first_part.append(&mut foreground_color(left_bracket, self.color)); + first_part.append(&mut foreground_color( + exited_text, + Some(self.style.colors.red), + )); + first_part.append(&mut foreground_color(right_bracket, self.color)); + ( + first_part, + left_bracket.len() + exited_text.len() + right_bracket.len(), + ) + }, + } + } + fn second_held_title_part_full(&self) -> (Vec, usize) { + // (title part, length) + let mut second_part = vec![]; + let left_enter_bracket = "<"; + let enter_text = "ENTER"; + let right_enter_bracket = ">"; + let enter_tip = " to re-run, "; + let left_break_bracket = "<"; + let break_text = "Ctrl-c"; + let right_break_bracket = ">"; + let break_tip = " to exit "; + second_part.append(&mut foreground_color(left_enter_bracket, self.color)); + second_part.append(&mut foreground_color( + enter_text, + Some(self.style.colors.orange), + )); + second_part.append(&mut foreground_color(right_enter_bracket, self.color)); + second_part.append(&mut foreground_color(enter_tip, self.color)); + second_part.append(&mut foreground_color(left_break_bracket, self.color)); + second_part.append(&mut foreground_color( + break_text, + Some(self.style.colors.orange), + )); + second_part.append(&mut foreground_color(right_break_bracket, self.color)); + second_part.append(&mut foreground_color(break_tip, self.color)); + ( + second_part, + left_enter_bracket.len() + + enter_text.len() + + right_enter_bracket.len() + + enter_tip.len() + + left_break_bracket.len() + + break_text.len() + + right_break_bracket.len() + + break_tip.len(), + ) + } + fn empty_undertitle(&self, max_undertitle_length: usize) -> Vec { + let mut left_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color); + let mut right_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color); + let mut ret = vec![]; + let mut padding = String::new(); + for _ in 0..max_undertitle_length { + padding.push_str(boundary_type::HORIZONTAL); + } + ret.append(&mut left_boundary); + ret.append(&mut foreground_color(&padding, self.color)); + ret.append(&mut right_boundary); + ret + } } diff --git a/zellij-server/src/unit/os_input_output_tests.rs b/zellij-server/src/unit/os_input_output_tests.rs index b0e741d8..58dbf777 100644 --- a/zellij-server/src/unit/os_input_output_tests.rs +++ b/zellij-server/src/unit/os_input_output_tests.rs @@ -38,6 +38,7 @@ fn get_cwd() { let server = ServerOsInputOutput { orig_termios: Arc::new(Mutex::new(test_termios)), client_senders: Arc::default(), + terminal_id_to_raw_fd: Arc::default(), }; let pid = nix::unistd::getpid(); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 9a08be2a..85e7b4a0 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -2,7 +2,7 @@ use super::{screen_thread_main, CopyOptions, Screen, ScreenInstruction}; use crate::panes::PaneId; use crate::{ channels::SenderWithContext, - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, Pid, ServerOsApi, SpawnTerminalError}, route::route_action, thread_bus::Bus, ClientId, ServerInstruction, SessionMetaData, ThreadSenders, @@ -12,7 +12,7 @@ use std::path::PathBuf; use zellij_utils::cli::CliAction; use zellij_utils::errors::ErrorContext; use zellij_utils::input::actions::{Action, Direction, ResizeDirection}; -use zellij_utils::input::command::TerminalAction; +use zellij_utils::input::command::{RunCommand, TerminalAction}; use zellij_utils::input::layout::{PaneLayout, SplitDirection}; use zellij_utils::input::options::Options; use zellij_utils::ipc::IpcReceiverWithContext; @@ -126,15 +126,15 @@ struct FakeInputOutput { } impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { + fn set_terminal_size_using_terminal_id(&self, _terminal_id: u32, _cols: u16, _rows: u16) { // noop } fn spawn_terminal( &self, _file_to_open: TerminalAction, - _quit_db: Box, + _quit_db: Box, RunCommand) + Send>, _default_editor: Option, - ) -> Result<(RawFd, RawFd), &'static str> { + ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -143,10 +143,10 @@ impl ServerOsApi for FakeInputOutput { fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result { + fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result { unimplemented!() } - fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { + fn tcdrain(&self, _id: u32) -> Result<(), nix::Error> { unimplemented!() } fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { @@ -195,6 +195,17 @@ impl ServerOsApi for FakeInputOutput { .insert(filename, contents); } } + fn re_run_command_in_terminal( + &self, + _terminal_id: u32, + _run_command: RunCommand, + _quit_cb: Box, RunCommand) + Send>, // u32 is the exit status + ) -> Result<(RawFd, RawFd), SpawnTerminalError> { + unimplemented!() + } + fn clear_terminal_id(&self, _terminal_id: u32) { + unimplemented!() + } } fn create_new_screen(size: Size) -> Screen { @@ -272,7 +283,7 @@ impl MockScreen { let pane_count = pane_layout.extract_run_instructions().len(); let mut pane_ids = vec![]; for i in 0..pane_count { - pane_ids.push(i as i32); + pane_ids.push(i as u32); } let _ = self.to_screen.send(ScreenInstruction::NewTab( pane_layout, @@ -285,7 +296,7 @@ impl MockScreen { let pane_count = tab_layout.extract_run_instructions().len(); let mut pane_ids = vec![]; for i in 0..pane_count { - pane_ids.push(i as i32); + pane_ids.push(i as u32); } let _ = self.to_screen.send(ScreenInstruction::NewTab( tab_layout, @@ -412,7 +423,7 @@ macro_rules! log_actions_in_thread { }; } -fn new_tab(screen: &mut Screen, pid: i32) { +fn new_tab(screen: &mut Screen, pid: u32) { let client_id = 1; screen .new_tab(PaneLayout::default(), vec![pid], client_id) @@ -746,7 +757,9 @@ fn switch_to_tab_with_fullscreen() { new_tab(&mut screen, 1); { let active_tab = screen.get_active_tab_mut(1).unwrap(); - active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap(); + active_tab + .new_pane(PaneId::Terminal(2), None, None, Some(1)) + .unwrap(); active_tab.toggle_active_pane_fullscreen(1); } new_tab(&mut screen, 2); @@ -859,7 +872,9 @@ fn attach_after_first_tab_closed() { new_tab(&mut screen, 1); { let active_tab = screen.get_active_tab_mut(1).unwrap(); - active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap(); + active_tab + .new_pane(PaneId::Terminal(2), None, None, Some(1)) + .unwrap(); active_tab.toggle_active_pane_fullscreen(1); } new_tab(&mut screen, 2); @@ -1802,7 +1817,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() { direction: None, command: None, cwd: None, - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, @@ -1839,7 +1854,7 @@ pub fn send_cli_new_pane_action_with_split_direction() { direction: Some(Direction::Right), command: None, cwd: None, - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, @@ -1876,7 +1891,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() { direction: Some(Direction::Right), command: Some("htop".into()), cwd: Some("/some/folder".into()), - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, @@ -1913,7 +1928,7 @@ pub fn send_cli_edit_action_with_default_parameters() { file: PathBuf::from("/file/to/edit"), direction: None, line_number: None, - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, @@ -1950,7 +1965,7 @@ pub fn send_cli_edit_action_with_line_number() { file: PathBuf::from("/file/to/edit"), direction: None, line_number: Some(100), - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, @@ -1987,7 +2002,7 @@ pub fn send_cli_edit_action_with_split_direction() { file: PathBuf::from("/file/to/edit"), direction: Some(Direction::Down), line_number: None, - floating: None, + floating: false, }; send_cli_action_to_server( &session_metadata, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap index 776d37ff..15573f90 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1618 +assertion_line: 1937 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[SpawnTerminal(Some(OpenFile("/file/to/edit", None)), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] +[SpawnTerminal(Some(OpenFile("/file/to/edit", None)), Some(false), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap index 9d4ecb19..aed92a47 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1650 +assertion_line: 1974 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] +[SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), Some(false), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap index 1d793cf6..9bdefbb1 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1682 +assertion_line: 2011 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- [SpawnTerminalHorizontally(Some(OpenFile("/file/to/edit", None)), 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap index 38b18ce7..315ba02d 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1889 +assertion_line: 1900 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder") })), 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] +[SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true })), 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap index eedc6c50..6153c4c1 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1566 +assertion_line: 1826 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[SpawnTerminal(None, ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] +[SpawnTerminal(None, Some(false), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap index de7a2465..f66107e9 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1599 +assertion_line: 1863 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[SpawnTerminalVertically(None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] +[SpawnTerminalVertically(None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit] diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 4ab6c03d..cfef982a 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -394,6 +394,7 @@ fn host_open_file(plugin_env: &PluginEnv) { .senders .send_to_pty(PtyInstruction::SpawnTerminal( Some(TerminalAction::OpenFile(path, None)), + None, ClientOrTabIndex::TabIndex(plugin_env.tab_index), )) .unwrap(); diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 24616bd1..bef1afb3 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -124,18 +124,18 @@ pub enum Sessions { #[clap(visible_alias = "ac")] #[clap(subcommand)] Action(CliAction), - /// Send actions to a specific session - #[clap(visible_alias = "c")] - Command { + /// Run a command in a new pane + #[clap(visible_alias = "r")] + Run { command: Option, #[clap(short, long, value_parser, conflicts_with("floating"))] direction: Option, #[clap(long, value_parser)] cwd: Option, - #[clap(short, long, value_parser, default_missing_value("true"))] - floating: Option, + #[clap(short, long, value_parser, default_value("false"), takes_value(false))] + floating: bool, }, - /// Edit file with default $EDITOR / $VISUAL in a specific session + /// Edit file with default $EDITOR / $VISUAL #[clap(visible_alias = "e")] Edit { file: PathBuf, @@ -143,8 +143,8 @@ pub enum Sessions { line_number: Option, #[clap(short, long, value_parser, conflicts_with("floating"))] direction: Option, - #[clap(short, long, value_parser, default_missing_value("true"))] - floating: Option, + #[clap(short, long, value_parser, default_value("false"), takes_value(false))] + floating: bool, }, ConvertConfig { old_config_file: PathBuf, @@ -201,7 +201,7 @@ pub enum CliAction { TogglePaneFrames, /// Toggle between sending text commands to all panes on the current tab and normal mode. ToggleActiveSyncTab, - /// Open a new pane in the specified direction [right|left|up|down] + /// Open a new pane in the specified direction [right|down] /// If no direction is specified, will try to use the biggest available space. NewPane { #[clap(short, long, value_parser, conflicts_with("floating"))] @@ -210,8 +210,8 @@ pub enum CliAction { command: Option, #[clap(long, value_parser)] cwd: Option, - #[clap(short, long, value_parser, default_missing_value("true"))] - floating: Option, + #[clap(short, long, value_parser, default_value("false"), takes_value(false))] + floating: bool, }, /// Open the specified file in a new zellij pane with your default EDITOR Edit { @@ -220,8 +220,8 @@ pub enum CliAction { direction: Option, #[clap(short, long, value_parser)] line_number: Option, - #[clap(short, long, value_parser, default_missing_value("true"))] - floating: Option, + #[clap(short, long, value_parser, default_value("false"), takes_value(false))] + floating: bool, }, /// Switch input mode of all connected clients [locked|pane|tab|resize|move|search|session] SwitchMode { input_mode: InputMode }, diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 5e961888..5c872741 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -273,6 +273,7 @@ pub enum ScreenContext { SetFixedHeight, SetFixedWidth, ClosePane, + HoldPane, UpdatePaneName, UndoRenamePane, NewTab, @@ -326,6 +327,7 @@ pub enum PtyContext { NewTab, ClosePane, CloseTab, + ReRunCommandInPane, Exit, } diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 8dacdc65..de1350dd 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -200,7 +200,7 @@ pub enum Action { /// If no direction is specified, will try to use the biggest available space. NewPane(Option), /// Open the file in a new pane using the default editor - EditFile(PathBuf, Option, Option, Option), // usize is an optional line number, bool is floating true/false + EditFile(PathBuf, Option, Option, bool), // usize is an optional line number, bool is floating true/false /// Open a new floating pane NewFloatingPane(Option), /// Open a new tiled (embedded, non-floating) pane @@ -288,23 +288,29 @@ impl Action { } => match command { Some(command) => { let (command, args) = split_command_and_args(command); + let cwd = cwd.or_else(|| std::env::current_dir().ok()); let run_command_action = RunCommandAction { command, args, cwd, direction, + hold_on_close: true, }; - match floating { - Some(true) => Ok(vec![Action::NewFloatingPane(Some(run_command_action))]), - _ => Ok(vec![Action::NewTiledPane( + if floating { + Ok(vec![Action::NewFloatingPane(Some(run_command_action))]) + } else { + Ok(vec![Action::NewTiledPane( direction, Some(run_command_action), - )]), + )]) } }, - None => match floating { - Some(true) => Ok(vec![Action::NewFloatingPane(None)]), - _ => Ok(vec![Action::NewTiledPane(direction, None)]), + None => { + if floating { + Ok(vec![Action::NewFloatingPane(None)]) + } else { + Ok(vec![Action::NewTiledPane(direction, None)]) + } }, }, CliAction::Edit { diff --git a/zellij-utils/src/input/command.rs b/zellij-utils/src/input/command.rs index 6f093a4a..abb7c1e7 100644 --- a/zellij-utils/src/input/command.rs +++ b/zellij-utils/src/input/command.rs @@ -17,6 +17,24 @@ pub struct RunCommand { pub args: Vec, #[serde(default)] pub cwd: Option, + #[serde(default)] + pub hold_on_close: bool, +} + +impl std::fmt::Display for RunCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut command: String = self + .command + .as_path() + .as_os_str() + .to_string_lossy() + .to_string(); + for arg in &self.args { + command.push(' '); + command.push_str(arg); + } + write!(f, "{}", command) + } } /// Intermediate representation @@ -30,6 +48,8 @@ pub struct RunCommandAction { pub cwd: Option, #[serde(default)] pub direction: Option, + #[serde(default)] + pub hold_on_close: bool, } impl From for RunCommand { @@ -38,6 +58,7 @@ impl From for RunCommand { command: action.command, args: action.args, cwd: action.cwd, + hold_on_close: action.hold_on_close, } } } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index b4b309ba..ffd35f20 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -201,6 +201,7 @@ fn layout_with_command_panes() { children: vec![PaneLayout { run: Some(Run::Command(RunCommand { command: PathBuf::from("htop"), + hold_on_close: true, ..Default::default() })), ..Default::default() @@ -226,6 +227,7 @@ fn layout_with_command_panes_and_cwd() { run: Some(Run::Command(RunCommand { command: PathBuf::from("htop"), cwd: Some(PathBuf::from("/path/to/my/cwd")), + hold_on_close: true, ..Default::default() })), ..Default::default() @@ -254,6 +256,7 @@ fn layout_with_command_panes_and_cwd_and_args() { command: PathBuf::from("htop"), cwd: Some(PathBuf::from("/path/to/my/cwd")), args: vec![String::from("-h"), String::from("-v")], + hold_on_close: true, ..Default::default() })), ..Default::default() diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap index 72101338..f32b346d 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 648 +assertion_line: 570 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -40,6 +40,7 @@ Layout { command: "htop", args: [], cwd: None, + hold_on_close: true, }, ), ), diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index 4bf3aea6..f572c356 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -177,6 +177,7 @@ impl<'a> KdlLayoutParser<'a> { command, args: args.unwrap_or_else(|| vec![]), cwd, + hold_on_close: true, }))), _ => Ok(None), } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 7092653c..b8d76c7b 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -590,12 +590,10 @@ impl TryFrom<(&str, &KdlDocument)> for PaletteColor { } impl TryFrom<&KdlNode> for Action { - // type Error = Box; type Error = ConfigError; fn try_from(kdl_action: &KdlNode) -> Result { let action_name = kdl_name!(kdl_action); let action_arguments: Vec<&KdlEntry> = kdl_argument_values!(kdl_action); - // let action_arguments: Vec<&KdlValue> = kdl_argument_values!(kdl_action); let action_children: Vec<&KdlDocument> = kdl_children!(kdl_action); match action_name { "Quit" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action), @@ -744,6 +742,7 @@ impl TryFrom<&KdlNode> for Action { args, cwd, direction, + hold_on_close: true, }; Ok(Action::Run(run_command_action)) }, @@ -1473,7 +1472,12 @@ impl RunCommand { .collect(), None => vec![], }; - Ok(RunCommand { command, args, cwd }) + Ok(RunCommand { + command, + args, + cwd, + hold_on_close: true, + }) } } diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index 37d999d1..f89acaf5 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -62,7 +62,7 @@ pub mod colors { pub const GREEN: u8 = 154; pub const GRAY: u8 = 238; pub const BRIGHT_GRAY: u8 = 245; - pub const RED: u8 = 88; + pub const RED: u8 = 124; pub const ORANGE: u8 = 166; pub const BLACK: u8 = 16; pub const MAGENTA: u8 = 201;