diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-10-17 11:55:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-17 11:55:38 +0200 |
commit | 69eb904426e64649fc7228b0d6803469911267d7 (patch) | |
tree | a4c1448de8301f91856917dfcbe3c0f13d0e25ae | |
parent | 8378f146c124e40ceed1f63628b9254bafe19c2f (diff) |
feat(panes): Add an option to press <ESC> and drop to shell in command panes (#2872)
* feat(panes): ESC to drop to default shell on command panes
* style(fmt): rustfmt
21 files changed, 194 insertions, 82 deletions
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index c5110b691..be337ea92 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -2070,7 +2070,7 @@ pub fn send_command_through_the_cli() { // 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, + cols: 150, rows: 24, }; let mut test_attempts = 10; @@ -2118,7 +2118,7 @@ pub fn send_command_through_the_cli() { instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.snapshot_contains("<Ctrl-c>") - && remote_terminal.cursor_position_is(61, 3) + && remote_terminal.cursor_position_is(76, 3) { remote_terminal.send_key(&SPACE); // re-run script - here we use SPACE // instead of the default ENTER because @@ -2135,7 +2135,7 @@ pub fn send_command_through_the_cli() { instruction: |mut remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.snapshot_contains("<Ctrl-c>") - && remote_terminal.cursor_position_is(61, 4) + && remote_terminal.cursor_position_is(76, 4) { step_is_complete = true } @@ -2149,7 +2149,7 @@ pub fn send_command_through_the_cli() { instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; if remote_terminal.snapshot_contains("foo") - && remote_terminal.cursor_position_is(61, 4) + && remote_terminal.cursor_position_is(76, 4) { step_is_complete = true } diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap index a7c58010e..fd55ac42a 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap @@ -17,10 +17,10 @@ expression: last_snapshot │ │ │ │ │────────────────────────────┘ │ │ │ Waiting to run: top │ │────────────────────────────┐ │ │ │ │ │ │ -│ │ │ <ENTER> to run, <Ctrl-c> to exit │ │ │ +│ │ │ <ENTER> run, <ESC> drop to shell, <Ctrl-c> exit │ │ │ │ └─│ │ │ │ │ │ │ │ │ -│ └─ <ENTER> to run, <Ctrl-c> to exit ─────────────────────┘ │ │ +│ └─ <ENTER> run, <ESC> drop to shell, <Ctrl-c> exit ──────┘ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ ││ │ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap index 510313378..f36773605 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap @@ -17,10 +17,10 @@ expression: last_snapshot │ │ │ │ │────────────────────────────┘ │ │ │ Waiting to run: top │ │────────────────────────────┐ │ │ │ │ │ │ -│ │ │ <ENTER> to run, <Ctrl-c> to exit │ │ │ +│ │ │ <ENTER> run, <ESC> drop to shell, <Ctrl-c> exit │ │ │ │ └─│ │ │ │ │ │ │ │ │ -│ └─ <ENTER> to run, <Ctrl-c> to exit ─────────────────────┘ │ │ +│ └─ <ENTER> run, <ESC> drop to shell, <Ctrl-c> exit ──────┘ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ ││ │ 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 343e574a3..4d2555214 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,29 +1,29 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 2031 +assertion_line: 2175 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 -s -- "/usr/src/zellij/fixtures/append-echo-script.sh││foo │ -│" ││█ │ -│$ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -│ ││ │ -└──────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] <ENTER> to re-run, <Ctrl-c> to exit ────┘ - Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT - Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane. +┌ Pane #1 ────────────────────────────────────────────────────────────────┐┌ /usr/src/zellij/fixtures/append-echo-script.sh ─────────────────────────┐ +│$ /usr/src/zellij/x86_64-unknown-linux-musl/release/zellij run -s -- "/us││foo │ +│r/src/zellij/fixtures/append-echo-script.sh" ││foo │ +│$ ││█ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└─────────────────────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] <ENTER> re-run, <ESC> drop to shell, <Ctrl-c> exit ────┘ + Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT Alt + <[]> VERTICAL + Tip: Alt + <n> => open new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate between panes. Alt + <+|-> => increase/decrease pane size. diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 46a0d9974..793e5b665 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -31,7 +31,7 @@ use zellij_utils::{ }; use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{BTreeMap, BTreeSet, HashMap}, env, fs::File, io::Write, @@ -581,7 +581,7 @@ impl ServerOsApi for ServerOsInputOutput { .with_context(err_context)?; let mut terminal_id = None; { - let current_ids: HashSet<u32> = self + let current_ids: BTreeSet<u32> = self .terminal_id_to_raw_fd .lock() .to_anyhow() @@ -589,13 +589,7 @@ impl ServerOsApi for ServerOsInputOutput { .keys() .copied() .collect(); - for i in 0..u32::MAX { - let i = i as u32; - if !current_ids.contains(&i) { - terminal_id = Some(i); - break; - } - } + terminal_id = current_ids.last().map(|l| l + 1).or(Some(0)); } match terminal_id { Some(terminal_id) => { @@ -628,7 +622,7 @@ impl ServerOsApi for ServerOsInputOutput { let mut terminal_id = None; { - let current_ids: HashSet<u32> = self + let current_ids: BTreeSet<u32> = self .terminal_id_to_raw_fd .lock() .to_anyhow() @@ -636,13 +630,7 @@ impl ServerOsApi for ServerOsInputOutput { .keys() .copied() .collect(); - for i in 0..u32::MAX { - let i = i as u32; - if !current_ids.contains(&i) { - terminal_id = Some(i); - break; - } - } + terminal_id = current_ids.last().map(|l| l + 1).or(Some(0)); } match terminal_id { Some(terminal_id) => { diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs index 23ea1a524..71aec7a45 100644 --- a/zellij-server/src/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -773,21 +773,25 @@ pub fn render_first_run_banner( let controls_bare_text_first_part = "<"; let enter_bare_text = "ENTER"; - let controls_bare_text_second_part = "> to run, <"; + let controls_bare_text_second_part = "> run, <"; + let esc_bare_text = "ESC"; + let controls_bare_text_third_part = "> drop to shell, <"; let ctrl_c_bare_text = "Ctrl-c"; - let controls_bare_text_third_part = "> to exit"; + let controls_bare_text_fourth_part = "> 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() + + esc_bare_text.len() + + controls_bare_text_third_part.len() + ctrl_c_bare_text.len() - + controls_bare_text_third_part.len(); + + controls_bare_text_fourth_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", + "\u{1b}[{};{}H{}<{}{}{}{}> run, <{}{}{}{}> drop to shell, <{}{}{}{}> exit", middle_row + 2, controls_column_start_position, bold_text, @@ -796,6 +800,10 @@ pub fn render_first_run_banner( RESET_STYLES, bold_text, controls_color, + esc_bare_text, + RESET_STYLES, + bold_text, + controls_color, ctrl_c_bare_text, RESET_STYLES, bold_text @@ -817,21 +825,25 @@ pub fn render_first_run_banner( let controls_bare_text_first_part = "<"; let enter_bare_text = "ENTER"; - let controls_bare_text_second_part = "> to run, <"; + let controls_bare_text_second_part = "> run, <"; + let esc_bare_text = "ESC"; + let controls_bare_text_third_part = "> drop to shell, <"; let ctrl_c_bare_text = "Ctrl-c"; - let controls_bare_text_third_part = "> to exit"; + let controls_bare_text_fourth_part = "> 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() + + esc_bare_text.len() + + controls_bare_text_third_part.len() + ctrl_c_bare_text.len() - + controls_bare_text_third_part.len(); + + controls_bare_text_fourth_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", + "\u{1b}[{};{}H{}<{}{}{}{}> run, <{}{}{}{}> drop to shell, <{}{}{}{}> exit", middle_row + 2, controls_column_start_position, bold_text, @@ -840,6 +852,10 @@ pub fn render_first_run_banner( RESET_STYLES, bold_text, controls_color, + esc_bare_text, + RESET_STYLES, + bold_text, + controls_color, ctrl_c_bare_text, RESET_STYLES, bold_text diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 6c405b6e5..ac4c6da63 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -40,6 +40,7 @@ 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 ESC: &[u8] = &[27]; 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) @@ -192,6 +193,13 @@ impl Pane for TerminalPane { self.remove_banner(); Some(AdjustedInput::ReRunCommandInThisPane(run_command)) }, + ESC => { + self.is_held = None; + self.grid.reset_terminal_state(); + self.set_should_render(true); + self.remove_banner(); + Some(AdjustedInput::DropToShellInThisPane) + }, CTRL_C => Some(AdjustedInput::CloseThisPane), _ => None, } diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index e355e8c55..6dd38e9fe 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -66,6 +66,7 @@ pub enum PtyInstruction { ClosePane(PaneId), CloseTab(Vec<PaneId>), ReRunCommandInPane(PaneId, RunCommand), + DropToShellInPane(PaneId, Option<PathBuf>), // Option<PathBuf> - default shell SpawnInPlaceTerminal( Option<TerminalAction>, Option<String>, @@ -89,6 +90,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, + PtyInstruction::DropToShellInPane(..) => PtyContext::DropToShellInPane, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout, PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd, @@ -532,6 +534,55 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> { }, } }, + PtyInstruction::DropToShellInPane(pane_id, default_shell) => { + let err_context = || format!("failed to rerun command in pane {:?}", pane_id); + + // TODO: get configured default_shell from screen/tab as an option and default to + // this otherwise (also look for a place that turns get_default_shell into a + // RunCommand, we might have done this before) + let run_command = RunCommand { + command: default_shell.unwrap_or_else(|| get_default_shell()), + hold_on_close: false, + hold_on_start: false, + // TODO: cwd + ..Default::default() + }; + match pty + .rerun_command_in_pane(pane_id, run_command.clone()) + .with_context(err_context) + { + Ok(..) => {}, + Err(err) => match err.downcast_ref::<ZellijError>() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => { + if run_command.hold_on_close { + pty.bus + .senders + .send_to_screen(ScreenInstruction::PtyBytes( + *terminal_id, + form |