diff --git a/src/client/mod.rs b/src/client/mod.rs index 5fb40096..ae60fc87 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -31,8 +31,9 @@ pub enum ClientInstruction { } pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { - let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l"; + let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; + let bracketed_paste = "\u{1b}[?2004h"; os_input.unset_raw_mode(0); let _ = os_input .get_stdout_writer() @@ -57,6 +58,10 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C config_options, )); os_input.set_raw_mode(0); + let _ = os_input + .get_stdout_writer() + .write(bracketed_paste.as_bytes()) + .unwrap(); let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< ClientInstruction, diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 68a75448..62e2e909 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -23,6 +23,7 @@ struct InputHandler { command_is_executing: CommandIsExecuting, send_client_instructions: SenderWithContext, should_exit: bool, + pasting: bool, } impl InputHandler { @@ -40,6 +41,7 @@ impl InputHandler { command_is_executing, send_client_instructions, should_exit: false, + pasting: false, } } @@ -49,6 +51,8 @@ impl InputHandler { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); let alt_left_bracket = vec![27, 91]; + let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ + let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201 loop { if self.should_exit { break; @@ -67,6 +71,10 @@ impl InputHandler { if unsupported_key == alt_left_bracket { let key = Key::Alt('['); self.handle_key(&key, raw_bytes); + } else if unsupported_key == bracketed_paste_start { + self.pasting = true; + } else if unsupported_key == bracketed_paste_end { + self.pasting = false; } } termion::event::Event::Mouse(_) => { @@ -81,10 +89,20 @@ impl InputHandler { } fn handle_key(&mut self, key: &Key, raw_bytes: Vec) { let keybinds = &self.config.keybinds; - for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { - let should_exit = self.dispatch_action(action); - if should_exit { - self.should_exit = true; + if self.pasting { + // we're inside a paste block, if we're in a mode that allows sending text to the + // terminal, send all text directly without interpreting it + // otherwise, just discard the input + if self.mode == InputMode::Normal || self.mode == InputMode::Locked { + let action = Action::Write(raw_bytes); + self.dispatch_action(action); + } + } else { + for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { + let should_exit = self.dispatch_action(action); + if should_exit { + self.should_exit = true; + } } } } diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index c8305cab..5b72dbfe 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -4,10 +4,10 @@ use ::insta::assert_snapshot; use crate::common::input::config::Config; use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::commands::{ - PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, - SCROLL_PAGE_UP_IN_SCROLL_MODE, SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, - SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, - TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, + BRACKETED_PASTE_END, BRACKETED_PASTE_START, PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, + SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, SCROLL_PAGE_UP_IN_SCROLL_MODE, + SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, SPLIT_DOWN_IN_PANE_MODE, + SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; @@ -441,3 +441,43 @@ pub fn toggle_focused_pane_fullscreen() { get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); assert_snapshot!(snapshot_before_quit); } + +#[test] +pub fn bracketed_paste() { + // bracketed paste (https://xfree86.org/current/ctlseqs.html#Bracketed%20Paste%20Mode) + // makes sure that text the user pastes is not interpreted as commands by the running program + // (zellij in this case) + // this tests makes sure the "SPLIT_RIGHT_IN_PANE_MODE" command is not interpreted as Zellij, + // since it's inside a bracketed paste block, while the "QUIT" command is, since it is already + // past the block + let fake_win_size = PositionAndSize { + columns: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut fake_input_output = get_fake_os_input(&fake_win_size); + fake_input_output.add_terminal_input(&[ + &PANE_MODE, + &BRACKETED_PASTE_START, + &SPLIT_RIGHT_IN_PANE_MODE, + &BRACKETED_PASTE_END, + &QUIT, + ]); + start( + Box::new(fake_input_output.clone()), + CliArgs::default(), + Box::new(fake_input_output.clone()), + Config::default(), + ); + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + let snapshot_before_quit = + get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); + assert_snapshot!(snapshot_before_quit); +} diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap new file mode 100644 index 00000000..1eec02f4 --- /dev/null +++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap @@ -0,0 +1,25 @@ +--- +source: src/tests/integration/basic.rs +expression: snapshot_before_quit + +--- +line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ █ diff --git a/src/tests/utils.rs b/src/tests/utils.rs index 10b243a7..f01c1ac5 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -81,5 +81,8 @@ pub mod commands { pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x + + pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~ + pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201 pub const SLEEP: [u8; 0] = []; }