From abc700fc4d10d61c969ad94fa520d7d9336dcf14 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 1 Nov 2022 09:07:25 +0100 Subject: feat(command-panes): allow to start suspended (#1887) * feat(command-panes): allow panes to start suspended * style(fmt): remove unused code * style(fmt): rustfmt --- src/main.rs | 2 + src/tests/e2e/cases.rs | 19 +++ src/tests/e2e/remote_runner.rs | 3 +- ...__e2e__cases__send_command_through_the_cli.snap | 8 +- zellij-server/src/os_input_output.rs | 34 ++++ zellij-server/src/panes/floating_panes/mod.rs | 3 +- zellij-server/src/panes/grid.rs | 1 + zellij-server/src/panes/terminal_character.rs | 117 +++++++++++++- zellij-server/src/panes/terminal_pane.rs | 52 +++++- zellij-server/src/panes/tiled_panes/mod.rs | 3 +- zellij-server/src/pty.rs | 179 +++++++++++++++------ zellij-server/src/screen.rs | 84 ++++++++-- zellij-server/src/tab/mod.rs | 29 +++- .../src/tab/unit/tab_integration_tests.rs | 16 +- zellij-server/src/tab/unit/tab_tests.rs | 4 +- zellij-server/src/ui/pane_boundaries_frame.rs | 33 ++-- zellij-server/src/unit/screen_tests.rs | 9 +- ...sts__send_cli_edit_action_with_line_number.snap | 2 +- ...d_cli_new_pane_action_with_command_and_cwd.snap | 4 +- zellij-utils/src/cli.rs | 14 ++ zellij-utils/src/input/actions.rs | 3 + zellij-utils/src/input/command.rs | 5 + zellij-utils/src/input/layout.rs | 11 +- zellij-utils/src/input/unit/layout_test.rs | 13 ++ ...ayout_test__args_added_to_args_in_template.snap | 4 +- ...ayout_test__args_override_args_in_template.snap | 4 +- ...on_exit_added_to_close_on_exit_in_template.snap | 2 + ...n_exit_overrides_close_on_exit_in_template.snap | 2 + ..._layout_test__cwd_added_to_cwd_in_template.snap | 4 +- ..._layout_test__cwd_override_cwd_in_template.snap | 4 +- ...wd_prepended_to_panes_with_and_without_cwd.snap | 3 +- ...nes_with_and_without_cwd_in_pane_templates.snap | 3 +- ...anes_with_and_without_cwd_in_tab_templates.snap | 3 +- ...est__global_cwd_given_to_panes_without_cwd.snap | 3 +- ..._global_cwd_passed_from_layout_constructor.snap | 3 +- ...ructor_overrides_global_cwd_in_layout_file.snap | 3 +- ...st__global_cwd_prepended_to_panes_with_cwd.snap | 3 +- ...wd_with_tab_cwd_given_to_panes_without_cwd.snap | 3 +- ...ayout_with_command_panes_and_close_on_exit.snap | 1 + ...out_with_command_panes_and_start_suspended.snap | 42 +++++ ...t_test__layout_with_tab_and_pane_templates.snap | 3 +- ...cwd_is_overriden_by_its_consumers_bare_cwd.snap | 3 +- ...cwd_overriden_by_its_consumers_command_cwd.snap | 3 +- ...n_its_consumer_command_does_not_have_a_cwd.snap | 3 +- ...hout_cwd_is_overriden_by_its_consumers_cwd.snap | 3 +- ...ithout_cwd_receives_its_consumers_bare_cwd.snap | 3 +- ...ropagated_to_its_consumer_command_with_cwd.snap | 3 +- ...agated_to_its_consumer_command_without_cwd.snap | 3 +- ...t_test__tab_cwd_given_to_panes_without_cwd.snap | 3 +- ..._test__tab_cwd_prepended_to_panes_with_cwd.snap | 3 +- zellij-utils/src/kdl/kdl_layout_parser.rs | 28 +++- zellij-utils/src/kdl/mod.rs | 47 +++--- 52 files changed, 668 insertions(+), 172 deletions(-) create mode 100644 zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap diff --git a/src/main.rs b/src/main.rs index 3ed592c53..6203f0f16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ fn main() { floating, name, close_on_exit, + start_suspended, })) = opts.command { let command_cli_action = CliAction::NewPane { @@ -35,6 +36,7 @@ fn main() { floating, name, close_on_exit, + start_suspended, }; commands::send_action_to_session(command_cli_action, opts.session); std::process::exit(0); diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 44551d997..359d74162 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -1914,6 +1914,25 @@ pub fn send_command_through_the_cli() { step_is_complete }, }) + .add_step(Step { + name: "Initial run of suspended command", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("") + && remote_terminal.cursor_position_is(0, 0) + // cursor does not appear in + // suspend_start panes + { + remote_terminal.send_key(&SPACE); // 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 command to run", instruction: |mut remote_terminal: RemoteTerminal| -> bool { diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index dd2c800da..9b007989c 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -346,7 +346,8 @@ impl RemoteTerminal { let mut channel = self.channel.lock().unwrap(); channel .write_all( - format!("{} run -- \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes(), + // note that this is run with the -s flag that suspends the command on startup + format!("{} run -s -- \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes(), ) .unwrap(); channel.flush().unwrap(); 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 index 84d33a037..0603ac26e 100644 --- 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 @@ -1,14 +1,14 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1968 +assertion_line: 1998 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 │ -│$ ││█ │ -│ ││ │ +│ run -s -- "/usr/src/zellij/fixtures/append-echo-script.sh││foo │ +│" ││█ │ +│$ ││ │ │ ││ │ │ ││ │ │ ││ │ diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 9a8ce0088..02f53dbe5 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -280,6 +280,7 @@ fn spawn_terminal( args, cwd: None, hold_on_close: false, + hold_on_start: false, } }, TerminalAction::RunCommand(command) => command, @@ -381,6 +382,10 @@ pub trait ServerOsApi: Send + Sync { quit_cb: Box, RunCommand) + Send>, // u32 is the exit status default_editor: Option, ) -> Result<(u32, RawFd, RawFd), SpawnTerminalError>; + // reserves a terminal id without actually opening a terminal + fn reserve_terminal_id(&self) -> Result { + unimplemented!() + } /// 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 @@ -484,6 +489,35 @@ impl ServerOsApi for ServerOsInputOutput { None => Err(SpawnTerminalError::NoMoreTerminalIds), } } + fn reserve_terminal_id(&self) -> Result { + 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); + Ok(terminal_id) + }, + None => Err(SpawnTerminalError::NoMoreTerminalIds), + } + } fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { unistd::read(fd, buf) } diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index f686f40bb..d17be7557 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -156,11 +156,12 @@ impl FloatingPanes { &mut self, pane_id: PaneId, exit_status: Option, + is_first_run: bool, run_command: RunCommand, ) { self.panes .get_mut(&pane_id) - .map(|p| p.hold(exit_status, run_command)); + .map(|p| p.hold(exit_status, is_first_run, 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 67a882fb4..d9d60fefe 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1533,6 +1533,7 @@ impl Grid { self.sixel_scrolling = false; self.mouse_mode = MouseMode::NoEncoding; self.mouse_tracking = MouseTracking::Off; + self.cursor_is_hidden = false; if let Some(images_to_reap) = self.sixel_grid.clear() { self.sixel_grid.reap_images(images_to_reap); } diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs index c6fa0adf1..68813e7a9 100644 --- a/zellij-server/src/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -3,7 +3,12 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; use unicode_width::UnicodeWidthChar; -use zellij_utils::{data::PaletteColor, vte::ParamsIter}; +use unicode_width::UnicodeWidthStr; +use zellij_utils::input::command::RunCommand; +use zellij_utils::{ + data::{PaletteColor, Style}, + vte::ParamsIter, +}; use crate::panes::alacritty_functions::parse_sgr_color; @@ -736,3 +741,113 @@ impl ::std::fmt::Debug for TerminalCharacter { write!(f, "{}", self.character) } } + +pub fn render_first_run_banner( + columns: usize, + rows: usize, + style: &Style, + run_command: Option<&RunCommand>, +) -> String { + let middle_row = rows / 2; + let middle_column = columns / 2; + match run_command { + Some(run_command) => { + let bold_text = RESET_STYLES.bold(Some(AnsiCode::On)); + let command_color_text = RESET_STYLES + .foreground(Some(AnsiCode::from(style.colors.green))) + .bold(Some(AnsiCode::On)); + let waiting_to_run_text = "Waiting to run: "; + let command_text = run_command.to_string(); + let waiting_to_run_text_width = waiting_to_run_text.width() + command_text.width(); + let column_start_postion = middle_column.saturating_sub(waiting_to_run_text_width / 2); + let waiting_to_run_line = format!( + "\u{1b}[{};{}H{}{}{}{}{}", + middle_row, + column_start_postion, + bold_text, + waiting_to_run_text, + command_color_text, + command_text, + RESET_STYLES + ); + + let controls_bare_text_first_part = "<"; + let enter_bare_text = "ENTER"; + let controls_bare_text_second_part = "> to run, <"; + let ctrl_c_bare_text = "Ctrl-c"; + let controls_bare_text_third_part = "> to exit"; + let controls_color = RESET_STYLES + .foreground(Some(AnsiCode::from(style.colors.orange))) + .bold(Some(AnsiCode::On)); + let controls_line_length = controls_bare_text_first_part.len() + + enter_bare_text.len() + + controls_bare_text_second_part.len() + + ctrl_c_bare_text.len() + + controls_bare_text_third_part.len(); + let controls_column_start_position = + middle_column.saturating_sub(controls_line_length / 2); + let controls_line = format!( + "\u{1b}[{};{}H{}<{}{}{}{}> to run, <{}{}{}{}> to exit", + middle_row + 2, + controls_column_start_position, + bold_text, + controls_color, + enter_bare_text, + RESET_STYLES, + bold_text, + controls_color, + ctrl_c_bare_text, + RESET_STYLES, + bold_text + ); + format!( + "\u{1b}[?25l{}{}{}{}", + RESET_STYLES, waiting_to_run_line, controls_line, RESET_STYLES + ) + }, + None => { + let bare_text = format!("Waiting to start..."); + let bare_text_width = bare_text.width(); + let column_start_postion = middle_column.saturating_sub(bare_text_width / 2); + let bold_text = RESET_STYLES.bold(Some(AnsiCode::On)); + let waiting_to_run_line = format!( + "\u{1b}[?25l\u{1b}[{};{}H{}{}{}", + middle_row, column_start_postion, bold_text, bare_text, RESET_STYLES + ); + + let controls_bare_text_first_part = "<"; + let enter_bare_text = "ENTER"; + let controls_bare_text_second_part = "> to run, <"; + let ctrl_c_bare_text = "Ctrl-c"; + let controls_bare_text_third_part = "> to exit"; + let controls_color = RESET_STYLES + .foreground(Some(AnsiCode::from(style.colors.orange))) + .bold(Some(AnsiCode::On)); + let controls_line_length = controls_bare_text_first_part.len() + + enter_bare_text.len() + + controls_bare_text_second_part.len() + + ctrl_c_bare_text.len() + + controls_bare_text_third_part.len(); + let controls_column_start_position = + middle_column.saturating_sub(controls_line_length / 2); + let controls_line = format!( + "\u{1b}[{};{}H{}<{}{}{}{}> to run, <{}{}{}{}> to exit", + middle_row + 2, + controls_column_start_position, + bold_text, + controls_color, + enter_bare_text, + RESET_STYLES, + bold_text, + controls_color, + ctrl_c_bare_text, + RESET_STYLES, + bold_text + ); + format!( + "\u{1b}[?25l{}{}{}{}", + RESET_STYLES, waiting_to_run_line, controls_line, RESET_STYLES + ) + }, + } +} diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 631df0c84..5726ef44b 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -2,7 +2,7 @@ use crate::output::{CharacterChunk, SixelImageChunk}; use crate::panes::sixel::SixelImageStore; use crate::panes::{ grid::Grid, - terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER}, + terminal_character::{render_first_run_banner, TerminalCharacter, EMPTY_TERMINAL_CHARACTER}, }; use crate::panes::{AnsiCode, LinkHandler}; use crate::pty::VteBytes; @@ -83,6 +83,8 @@ pub enum PaneId { Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? } +type IsFirstRun = bool; + // FIXME: This should hold an os_api handle so that terminal panes can set their own size via FD in // their `reflow_lines()` method. Drop a Box in here somewhere. #[allow(clippy::too_many_arguments)] @@ -104,8 +106,10 @@ 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 + is_held: Option<(Option, IsFirstRun, RunCommand)>, // a "held" pane means that its command has either exited and the pane is waiting for a + // possible user instruction to be re-run, or that the command has not yet been run + banner: Option, // a banner to be rendered inside this TerminalPane, used for panes + // held on startup and can possibly be used to display some errors } impl Pane for TerminalPane { @@ -170,13 +174,14 @@ impl Pane for TerminalPane { // 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 let Some((_exit_status, run_command)) = &self.is_held { + if let Some((_exit_status, _is_first_run, run_command)) = &self.is_held { match input_bytes.as_slice() { 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); + self.remove_banner(); Some(AdjustedInput::ReRunCommandInThisPane(run_command)) }, CTRL_C => Some(AdjustedInput::CloseThisPane), @@ -395,8 +400,12 @@ impl Pane for TerminalPane { pane_title, frame_params, ); - if let Some((exit_status, _run_command)) = &self.is_held { - frame.add_exit_status(exit_status.as_ref().copied()); + if let Some((exit_status, is_first_run, _run_command)) = &self.is_held { + if *is_first_run { + frame.indicate_first_run(); + } else { + frame.add_exit_status(exit_status.as_ref().copied()); + } } let res = match self.frame.get(&client_id) { @@ -701,8 +710,11 @@ 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)); + fn hold(&mut self, exit_status: Option, is_first_run: bool, run_command: RunCommand) { + self.is_held = Some((exit_status, is_first_run, run_command)); + if is_first_run { + self.render_first_run_banner(); + } self.set_should_render(true); } } @@ -752,6 +764,7 @@ impl TerminalPane { fake_cursor_locations: HashSet::new(), search_term: String::new(), is_held: None, + banner: None, } } pub fn get_x(&self) -> usize { @@ -782,6 +795,10 @@ impl TerminalPane { let rows = self.get_content_rows(); let cols = self.get_content_columns(); self.grid.change_size(rows, cols); + if self.banner.is_some() { + self.grid.reset_terminal_state(); + self.render_first_run_banner(); + } self.set_should_render(true); } pub fn read_buffer_as_lines(&self) -> Vec> { @@ -791,6 +808,25 @@ impl TerminalPane { // (x, y) self.grid.cursor_coordinates() } + fn render_first_run_banner(&mut self) { + let columns = self.get_content_columns(); + let rows = self.get_content_rows(); + let banner = match &self.is_held { + Some((_exit_status, _is_first_run, run_command)) => { + render_first_run_banner(columns, rows, &self.style, Some(run_command)) + }, + None => render_first_run_banner(columns, rows, &self.style, None), + }; + self.banner = Some(banner.clone()); + self.handle_pty_bytes(banner.as_bytes().to_vec()); + } + fn remove_banner(&mut self) { + if self.banner.is_some() { + self.grid.reset_terminal_state(); + self.set_should_render(true); + self.banner = None; + } + } } #[cfg(test)] diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 59657c153..7e7b7a1f9 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -1010,11 +1010,12 @@ impl TiledPanes { &mut self, pane_id: PaneId, exit_status: Option, + is_first_run: bool, run_command: RunCommand, ) { self.panes .get_mut(&pane_id) - .map(|p| p.hold(exit_status, run_command)); + .map(|p| p.hold(exit_status, is_first_run, 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/pty.rs b/zellij-server/src/pty.rs index bb5beb5dd..0ca0b5860 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -108,25 +108,29 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { _ => (false, None, name), }; match pty.spawn_terminal(terminal_action, client_or_tab_index) { - Ok(pid) => { + Ok((pid, starts_held)) => { + let hold_for_command = if starts_held { run_command } else { None }; pty.bus .senders .send_to_screen(ScreenInstruction::NewPane( PaneId::Terminal(pid), pane_title, should_float, + hold_for_command, client_or_tab_index, )) .with_context(err_context)?; }, Err(SpawnTerminalError::CommandNotFound(pid)) => { if hold_on_close { + let hold_for_command = None; // we do not hold an "error" pane pty.bus .senders .send_to_screen(ScreenInstruction::NewPane( PaneId::Terminal(pid), pane_title, should_float, + hold_for_command, client_or_tab_index, )) .with_context(err_context)?; @@ -154,7 +158,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { Some(TerminalAction::OpenFile(temp_file, line_number)), ClientOrTabIndex::ClientId(client_id), ) { - Ok(pid) => { + Ok((pid, _starts_held)) => { pty.bus .senders .send_to_screen(ScreenInstruction::OpenInPlaceEditor( @@ -178,23 +182,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { _ => (false, None, name), }; match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) { - Ok(pid) => { + Ok((pid, starts_held)) => { + let hold_for_command = if starts_held { run_command } else { None }; pty.bus .senders .send_to_screen(ScreenInstruction::VerticalSplit( PaneId::Terminal(pid), pane_title, + hold_for_command, client_id, )) .with_context(err_context)?; }, Err(SpawnTerminalError::CommandNotFound(pid)) => { if hold_on_close { + let hold_for_command = None; // error panes are never held pty.bus .senders .send_to_screen(ScreenInstruction::VerticalSplit( PaneId::Terminal(pid), pane_title, + hold_for_command, client_id, )) .with_context(err_context)?; @@ -238,23 +246,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { _ => (false, None, name), }; match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) { - Ok(pid) => { + Ok((pid, starts_held)) => { + let hold_for_command = if starts_held { run_command } else { None }; pty.bus .senders .send_to_screen(ScreenInstruction::HorizontalSplit( PaneId::Terminal(pid), pane_title, + hold_for_command, client_id, )) .with_context(err_context)?; }, Err(SpawnTerminalError::CommandNotFound(pid)) => { if hold_on_close { + let hold_for_command = None; // error panes are never held pty.bus .senders .send_to_screen(ScreenInstruction::HorizontalSplit( PaneId::Terminal(pid), pane_title, + hold_for_command, client_id, )) .with_context(err_context)?; @@ -391,6 +403,7 @@ impl Pty { command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), cwd, // note: this might also be filled by the calling function, eg. spawn_terminal hold_on_close: false, + hold_on_start: false, }) } fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) { @@ -416,7 +429,8 @@ impl Pty { &mut self, terminal_action: Option, client_or_tab_index: ClientOrTabIndex, - ) -> Result { + ) -> Result<(u32, bool), SpawnTerminalError> { + // bool is starts_held // returns the terminal id let terminal_action = match client_or_tab_index { ClientOrTabIndex::ClientId(client_id) => { @@ -429,10 +443,20 @@ impl Pty { terminal_action.unwrap_or_else(|| self.get_default_terminal(None)) }, }; - let hold_on_close = match &terminal_action { - TerminalAction::RunCommand(run_command) => run_command.hold_on_close, - _ => false, + let (hold_on_start, hold_on_close) = match &terminal_action { + TerminalAction::RunCommand(run_command) => { + (run_command.hold_on_start, run_command.hold_on_close) + }, + _ => (false, false), }; + + if hold_on_start { + // we don't actually open a terminal in this case, just wait for the user to run it + let starts_held = hold_on_start; + let terminal_id = self.bus.os_input.as_mut().unwrap().reserve_terminal_id()?; + return Ok((terminal_id, starts_held)); + } + let quit_cb = Box::new({ let senders = self.bus.senders.clone(); move |pane_id, exit_status, command| { @@ -477,7 +501,8 @@ impl Pty { self.task_handles.insert(terminal_id, terminal_bytes); self.id_to_child_pid.insert(terminal_id, child_fd); - Ok(terminal_id) + let starts_held = false; + Ok((terminal_id, starts_held)) } pub fn spawn_terminals_for_layout( &mut self, @@ -489,10 +514,15 @@ impl Pty { let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None)); self.fill_cwd(&mut default_shell, client_id); let extracted_run_instructions = layout.extract_run_instructions(); - let mut new_pane_pids: Vec<(u32, Option, Result)> = - vec![]; // (terminal_id, - // run_command - // file_descriptor) + let mut new_pane_pids: Vec<( + u32, + bool, + Option, + Result, + )> = vec![]; // (terminal_id, + // starts_held, + // run_command, + // file_descriptor) for run_instruction in extracted_run_instructions { let quit_cb = Box::new({ let senders = self.bus.senders.clone(); @@ -502,6 +532,7 @@ impl Pty { }); match run_instruction { Some(Run::Command(command)) => { + let starts_held = command.hold_on_start; let hold_on_close = command.hold_on_close; let quit_cb = Box::new({ let senders = self.bus.senders.clone(); @@ -520,34 +551,56 @@ impl Pty { } }); let cmd = TerminalAction::RunCommand(command.clone()); - match self - .bus - .os_input - .as_mut() - .with_context(err_context)? - .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); - }, + if starts_held { + // we don't actually open a terminal in this case, just wait for the user to run it + match self.bus.os_input.as_mut().unwrap().reserve_terminal_id() { + Ok(terminal_id) => { + new_pane_pids.push(( + terminal_id, + starts_held, + Some(command.clone()), + Ok(terminal_id as i32), // this is not actually correct but gets + // stripped later + )); + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } + } else { + match self + .bus + .os_input + .as_mut() + .with_context(err_context)? + .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, + starts_held, + Some(command.clone()), + Ok(pid_primary), + )); + }, + Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { + let starts_held = false; // we do not hold error panes + new_pane_pids.push(( + terminal_id, + starts_held, + Some(command.clone()), + Err(SpawnTerminalError::CommandNotFound(terminal_id)), + )); + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } } }, Some(Run::Cwd(cwd)) => { + let starts_held = false; // we do not hold Cwd panes let shell = self.get_default_terminal(Some(cwd)); match self .bus @@ -558,11 +611,12 @@ impl Pty { { 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))); + new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); }, Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { new_pane_pids.push(( terminal_id, + starts_held, None, Err(SpawnTerminalError::CommandNotFound(terminal_id)), )); @@ -573,6 +627,7 @@ impl Pty { } }, Some(Run::EditFile(path_to_file, line_number)) => { + let starts_held = false; // we do not hold edit panes (for now?) match self .bus .os_input @@ -585,11 +640,13 @@ impl Pty { ) { 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))); + new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); }, Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { + let starts_held = false; // we do not hold error panes new_pane_pids.push(( terminal_id, + starts_held, None, Err(SpawnTerminalError::CommandNotFound(terminal_id)), )); @@ -600,6 +657,7 @@ impl Pty { } }, None => { + let starts_held = false; match self .bus .os_input @@ -609,11 +667,12 @@ impl Pty { { 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))); + new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); }, Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { new_pane_pids.push(( terminal_id, + starts_held, None, Err(SpawnTerminalError::CommandNotFound(terminal_id)), )); @@ -627,10 +686,17 @@ impl Pty { Some(Run::Plugin(_)) => {}, } } - let new_tab_pane_ids: Vec = new_pane_pids + // Option should only be Some if the pane starts held + let new_tab_pane_ids: Vec<(u32, Option)> = new_pane_pids .iter() - .map(|(terminal_id, _, _)| *terminal_id) - .collect::>(); + .map(|(terminal_id, starts_held, run_command, _)| { + if *starts_held { + (*terminal_id, run_command.clone()) + } else { + (*terminal_id, None) + } + }) + .collect(); self.bus .senders .send_to_screen(ScreenInstruction::NewTab( @@ -639,7 +705,11 @@ impl Pty { client_id, )) .with_context(err_context)?; - for (terminal_id, run_command, pid_primary) in new_pane_pids { + for (terminal_id, starts_held, run_command, pid_primary) in new_pane_pids { + if starts_held { + // we do not run a command or start listening for bytes on held panes + continue; + } match pid_primary { Ok(pid_primary) => { let terminal_bytes = task::spawn({ @@ -744,16 +814,21 @@ impl Pty { 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 hold_on_close = run_command.hold_on_close; 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, - )); + 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 diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index c1fcabc78..42d0c093c 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -112,19 +112,27 @@ macro_rules! active_tab_and_connected_client_id { }; } +type InitialTitle = String; +type ShouldFloat = bool; +type HoldForCommand = Option; + /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] pub enum ScreenInstruction { PtyBytes(u32, VteBytes), Render, - NewPane(PaneId, Option, Option, ClientOrTabIndex), // String is initial title, - // bool (if Some) is - // should_float + NewPane( + PaneId, + Option, + Option, + HoldForCommand, + ClientOrTabIndex, + ), OpenInPlaceEditor(PaneId, ClientId), TogglePaneEmbedOrFloating(ClientId), ToggleFloatingPanes(ClientId, Option), - HorizontalSplit(PaneId, Option, ClientId), // String is initial title - VerticalSplit(PaneId, Option, ClientId), // String is initial title + HorizontalSplit(PaneId, Option, HoldForCommand, ClientId), + VerticalSplit(PaneId, Option, HoldForCommand, ClientId), WriteCharacter(Vec, ClientId), ResizeLeft(ClientId), ResizeRight(ClientId), @@ -167,7 +175,7 @@ pub enum ScreenInstruction { HoldPane(PaneId, Option, RunCommand, Option), // Option is the exit status UpdatePaneName(Vec, ClientId), UndoRenamePane(ClientId), - NewTab(PaneLayout, Vec, ClientId), + NewTab(PaneLayout, Vec<(u32, HoldForCommand)>, ClientId), SwitchTabNext(ClientId), SwitchTabPrev(ClientId), ToggleActiveSyncTab(ClientId), @@ -811,7 +819,7 @@ impl Screen { pub fn new_tab( &mut self, layout: PaneLayout, - new_ids: Vec, + new_ids: Vec<(u32, HoldForCommand)>, client_id: ClientId, ) -> Result<()> { let client_id = if self.get_active_tab(client_id).is_some() { @@ -1227,6 +1235,7 @@ pub(crate) fn screen_thread_main( pid, initial_pane_title, should_float, + hold_for_command, client_or_tab_index, ) => { match client_or_tab_index { @@ -1237,10 +1246,27 @@ pub(crate) fn screen_thread_main( should_float, Some(client_id)), ?); + if let Some(hold_for_command) = hold_for_command { + let is_first_run = true; + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, _client_id: ClientId| tab.hold_pane( + pid, + None, + is_first_run, + hold_for_command + ) + ) + } }, ClientOrTabIndex::TabIndex(tab_index) => { if let Some(active_tab) = screen.tabs.get_mut(&tab_index) { active_tab.new_pane(pid, initial_pane_title, should_float, None)?; + if let Some(hold_for_command) = hold_for_command { + let is_first_run = true; + active_tab.hold_pane(pid, None, is_first_run, hold_for_command); + } } else { log::error!("Tab index not found: {:?}", tab_index); } @@ -1275,24 +1301,60 @@ pub(crate) fn screen_thread_main( screen.render()?; }, - ScreenInstruction::HorizontalSplit(pid, initial_pane_title, client_id) => { + ScreenInstruction::HorizontalSplit( + pid, + initial_pane_title, + hold_for_command, + client_id, + ) => { active_tab_and_connected_client_id!( screen, client_id, |tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, initial_pane_title, client_id), ? ); + if let Some(hold_for_command) = hold_for_command { + let is_first_run = true; + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, _client_id: ClientId| tab.hold_pane( + pid, + None, + is_first_run, + hold_for_command + ) + ); + } screen.unblock_input()?; screen.update_tabs()?; screen.render()?; }, - ScreenInstruction::VerticalSplit(pid, initial_pane_title, client_id) => { + ScreenInstruction::VerticalSplit( + pid, + initial_pane_title, + hold_for_command, + client_id, + ) => { active_tab_and_connected_client_id!( screen, client_id, |tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, initial_pane_title, client_id), ? ); + if let Some(hold_for_command) = hold_for_command { + let is_first_run = true; + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, _client_id: ClientId| tab.hold_pane( + pid, + None, + is_first_run, + hold_for_command + ) + ); + } screen.unblock_input()?; screen.update_tabs()?; screen.render()?; @@ -1644,18 +1706,20 @@ pub(crate) fn screen_thread_main( screen.unblock_input()?; }, ScreenInstruction::HoldPane(id, exit_status, run_command, client_id) => { + let is_first_run = false; match client_id { Some(client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab.hold_pane( id, exit_status, + is_first_run, 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); + tab.hold_pane(id, exit_status, is_first_run, run_command); break; } } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 9efcf9012..df12b2df9 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -61,6 +61,8 @@ macro_rules! resize_pty { }; } +type HoldForCommand = Option; + // FIXME: This should be replaced by `RESIZE_PERCENT` at some point pub const MIN_TERMINAL_HEIGHT: usize = 5; pub const MIN_TERMINAL_WIDTH: usize = 5; @@ -354,7 +356,7 @@ pub trait Pane { // False by default (only terminal-panes support alternate mode) false } - fn hold(&mut self, _exit_status: Option, _run_command: RunCommand) { + fn hold(&mut self, _exit_status: Option, _is_first_run: bool, _run_command: RunCommand) { // No-op by default, only terminal panes support holding } } @@ -470,7 +472,7 @@ impl Tab { pub fn apply_layout( &mut self, layout: PaneLayout, - new_ids: Vec, + new_ids: Vec<(u32, HoldForCommand)>, tab_index: usize, client_id: ClientId, ) -> Result<()> { @@ -532,7 +534,7 @@ 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 - if let Some(pid) = new_ids.next() { + if let Some((pid, hold_for_command)) = 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()), @@ -552,6 +554,9 @@ impl Tab { initial_title, ); new_pane.set_borderless(layout.borderless); + if let Some(held_command) = hold_for_command { + new_pane.hold(None, true, held_command.clone()); + } self.tiled_panes.add_pane_with_existing_geom( PaneId::Terminal(*pid), Box::new(new_pane), @@ -560,7 +565,7 @@ impl Tab { } } } - for unused_pid in new_ids { + 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 @@ -601,7 +606,7 @@ impl Tab { Ok(()) }, Err(e) => { - for unused_pid in new_ids { + for (unused_pid, _) in new_ids { self.senders .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid))) .with_context(err_context)?; @@ -1746,11 +1751,19 @@ impl Tab { closed_pane } } - pub fn hold_pane(&mut self, id: PaneId, exit_status: Option, run_command: RunCommand) { + pub fn hold_pane( + &mut self, + id: PaneId, + exit_status: Option, + is_first_run: bool, + run_command: RunCommand, + ) { if self.floating_panes.panes_contain(&id) { - self.floating_panes.hold_pane(id, exit_status, run_command); + self.floating_panes + .hold_pane(id, exit_status, is_first_run, run_command); } else { - self.tiled_panes.hold_pane(id, exit_status, run_command); + self.tiled_panes + .hold_pane(id, exit_status, is_first_run, run_command); } } pub fn replace_pane_with_suppressed_pane(&mut self, pane_id: PaneId) -> Option> { diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 5944f84d0..3f4290fa6 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -223,7 +223,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { terminal_emulator_colors, terminal_emulator_color_codes, ); - tab.apply_layout(PaneLayout::default(), vec![1], index, client_id) + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) .unwrap(); tab } @@ -277,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 u32) + .map(|(i, _)| (i as u32, None)) .collect(); tab.apply_layout(tab_layout, pane_ids, index, client_id) .unwrap(); @@ -332,14 +332,8 @@ fn create_new_tab_with_mock_pty_writer( terminal_emulator_colors, terminal_emulator_color_codes, ); - tab.apply_layout( - // LayoutTemplate::default().try_into().unwrap(), - PaneLayout::default(), - vec![1], - index, - client_id, - ) - .unwrap(); + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) + .unwrap(); tab } @@ -393,7 +387,7 @@ fn create_new_tab_with_sixel_support( terminal_emulator_colors, terminal_emulator_color_codes, ); - tab.apply_layout(PaneLayout::default(), vec![1], index, client_id) + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) .unwrap(); tab } diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 6b100cf62..2539b7a93 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -142,7 +142,7 @@ fn create_new_tab(size: Size) -> Tab { terminal_emulator_colors, terminal_emulator_color_codes, ); - tab.apply_layout(PaneLayout::default(), vec![1], index, client_id) + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) .unwrap(); tab } @@ -189,7 +189,7 @@ fn create_new_tab_with_cell_size( terminal_emulator_colors, terminal_emulator_color_codes, ); - tab.apply_layout(PaneLayout::default(), vec![1], index, client_id) + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) .unwrap(); tab } diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 61df8470a..6209466c4 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -89,6 +89,7 @@ pub struct PaneFrame { pub other_cursors_exist_in_session: bool, pub other_focused_clients: Vec, exit_status: Option, + is_first_run: bool, } impl PaneFrame { @@ -109,6 +110,7 @@ impl PaneFrame { other_focused_clients: frame_params.other_focused_clients, other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session, exit_status: None, + is_first_run: false, } } pub fn add_exit_status(&mut self, exit_status: Option) { @@ -117,6 +119,9 @@ impl PaneFrame { None => Some(ExitStatus::Exited), }; } + pub fn indicate_first_run(&mut self) { + self.is_first_run = true; + } 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)) @@ -611,11 +616,7 @@ impl PaneFrame { } fn render_held_undertitle(&self) -> Result> { let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners - let exit_status = self - .exit_status - .with_context(|| format!("failed to render command pane status '{}'", self.title))?; // 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 first_part, first_part_len) = self.first_exited_held_title_part_full(); let mut left_boundary = foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color); let mut right_boundary = @@ -683,7 +684,7 @@ impl PaneFrame { character_chunks.push(CharacterChunk::new(title, x, y)); } else if row == self.geom.rows - 1 { // bottom row - if self.exit_status.is_some() { + if self.exit_status.is_some() || self.is_first_run { let x = self.geom.x; let y = self.geom.y + row; character_chunks.push(CharacterChunk::new( @@ -727,13 +728,10 @@ impl PaneFrame { } Ok((character_chunks, None)) } - fn first_held_title_part_full( - &self, - exit_status: ExitStatus, - ) -> (Vec, usize) { + fn first_exited_held_title_part_full(&self) -> (Vec, usize) { // (title part, length) - match exit_status { - ExitStatus::Code(exit_code) => { + match self.exit_status { + Some(ExitStatus::Code(exit_code)) => { let mut first_part = vec![]; let left_bracket = " [ "; let exited_text = "EXIT CODE: "; @@ -759,7 +757,7 @@ impl PaneFrame { + right_bracket.len(), ) }, - ExitStatus::Exited => { + Some(ExitStatus::Exited) => { let mut first_part = vec![]; let left_bracket = " [ "; let exited_text = "EXITED"; @@ -775,15 +773,20 @@ impl PaneFrame { left_bracket.len() + exited_text.len() + right_bracket.len(), ) }, + None => (foreground_color(boundary_type::HORIZONTAL, self.color), 1), } } fn second_held_title_part_full(&self) -> (Vec, usize) { // (title part, length) let mut second_part = vec![]; - let left_enter_bracket = "<"; + let left_enter_bracket = if self.is_first_run { " <" } else { "<" }; let enter_text = "ENTER"; let right_enter_bracket = ">"; - let enter_tip = " to re-run, "; + let enter_tip = if self.is_first_run { + " to run, " + } else { + " to re-run, " + }; let left_break_bracket = "<"; let break_text = "Ctrl-c"; let right_break_bracket = ">"; diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 299bab6a3..8fd1a6217 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -284,7 +284,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 u32); + pane_ids.push((i as u32, None)); } let _ = self.to_screen.send(ScreenInstruction::NewTab( pane_layout, @@ -297,7 +297,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 u32); + pane_ids.push((i as u32, None)); } let _ = self.to_screen.send(ScreenInstruction::NewTab( tab_layout, @@ -427,7 +427,7 @@ macro_rules! log_actions_in_thread { fn new_tab(screen: &mut Screen, pid: u32) { let client_id = 1; screen - .new_tab(PaneLayout::default(), vec![pid], client_id) + .new_tab(PaneLayout::default(), vec![(pid, None)], client_id) .expect("TEST"); } @@ -1822,6 +1822,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() { floating: false, name: None, close_on_exit: false, + start_suspended: false, }; send_cli_action_to_server( &session_metadata, @@ -1861,6 +1862,7 @@ pub fn send_cli_new_pane_action_with_split_direction() { floating: false, name: None, close_on_exit: false, + start_suspended: false, }; send_cli_action_to_server( &session_metadata, @@ -1900,6 +1902,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() { floating: false, name: None, close_on_exit: false, + start_suspended: 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_line_number.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap index e8f819d8f..92e668f41 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: 1981 +assertion_line: 1989 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- [SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), Some(false), Some("Editing: /file/to/edit"), 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_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 62d76f63b..1e88d09ca 100644 --- a/zellij-server/src/unit