diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 785cd319b..baef63c5d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,8 @@ New or improved bindings ^^^^^^^^^^^^^^^^^^^^^^^^ - :kbd:`ctrl-z` (undo) after executing a command will restore the previous cursor position instead of placing the cursor at the end of the command line. - The OSC 133 prompt marking feature has learned about kitty's ``click_events=1`` flag, which allows moving fish's cursor by clicking. +- :kbd:`ctrl-l` no longer clears the screen but only pushes to the terminal's scrollback all text above the prompt (via a new special input function ``scrollback-push``). + You can restore previous behavior with `bind ctrl-l clear-screen`. Completions ^^^^^^^^^^^ diff --git a/doc_src/cmds/bind.rst b/doc_src/cmds/bind.rst index 9218123f7..02065308d 100644 --- a/doc_src/cmds/bind.rst +++ b/doc_src/cmds/bind.rst @@ -171,7 +171,10 @@ The following special input functions are available: make the current word begin with a capital letter ``clear-screen`` - clears the screen and redraws the prompt. if the terminal doesn't support clearing the screen it is the same as ``repaint``. + clears the screen and redraws the prompt. + +``scrollback-push`` + pushes earlier output to the terminal scrollback, positioning the prompt at the top. ``complete`` guess the remainder of the current token diff --git a/share/functions/__fish_shared_key_bindings.fish b/share/functions/__fish_shared_key_bindings.fish index 13faef8ca..c2d64e92a 100644 --- a/share/functions/__fish_shared_key_bindings.fish +++ b/share/functions/__fish_shared_key_bindings.fish @@ -66,7 +66,7 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod bind --preset $argv alt-l __fish_list_current_token bind --preset $argv alt-o __fish_preview_current_file bind --preset $argv alt-w __fish_whatis_current_token - bind --preset $argv ctrl-l clear-screen + bind --preset $argv ctrl-l scrollback-push repaint bind --preset $argv ctrl-c cancel-commandline bind --preset $argv ctrl-u backward-kill-line bind --preset $argv ctrl-w backward-kill-path-component diff --git a/src/curses.rs b/src/curses.rs index ef08f86fb..1ecd361ce 100644 --- a/src/curses.rs +++ b/src/curses.rs @@ -66,8 +66,10 @@ pub struct Term { pub cursor_down: Option, pub cursor_left: Option, pub cursor_right: Option, + pub parm_cursor_up: Option, pub parm_left_cursor: Option, pub parm_right_cursor: Option, + pub parm_index: Option, pub clr_eol: Option, pub clr_eos: Option, @@ -215,8 +217,10 @@ fn new(db: terminfo::Database) -> Self { cursor_down: get_str_cap(&db, "do"), cursor_left: get_str_cap(&db, "le"), cursor_right: get_str_cap(&db, "nd"), + parm_cursor_up: get_str_cap(&db, "UP"), parm_left_cursor: get_str_cap(&db, "LE"), parm_right_cursor: get_str_cap(&db, "RI"), + parm_index: get_str_cap(&db, "SF"), clr_eol: get_str_cap(&db, "ce"), clr_eos: get_str_cap(&db, "cd"), @@ -425,8 +429,10 @@ pub fn setup_fallback_term() -> Arc { cursor_down: Some(CString::new("\n").unwrap()), cursor_left: Some(CString::new("\x08").unwrap()), cursor_right: Some(CString::new("\x1b[C").unwrap()), + parm_cursor_up: Some(CString::new("\x1b[%p1%dA").unwrap()), parm_left_cursor: Some(CString::new("\x1b[%p1%dD").unwrap()), parm_right_cursor: Some(CString::new("\x1b[%p1%dC").unwrap()), + parm_index: Some(CString::new("\x1b[%p1%dS").unwrap()), clr_eol: Some(CString::new("\x1b[K").unwrap()), clr_eos: Some(CString::new("\x1b[J").unwrap()), max_colors: Some(256), diff --git a/src/input.rs b/src/input.rs index b5e49656e..ce8117563 100644 --- a/src/input.rs +++ b/src/input.rs @@ -198,6 +198,7 @@ const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadat make_md(L!("repaint-mode"), ReadlineCmd::RepaintMode), make_md(L!("repeat-jump"), ReadlineCmd::RepeatJump), make_md(L!("repeat-jump-reverse"), ReadlineCmd::ReverseRepeatJump), + make_md(L!("scrollback-push"), ReadlineCmd::ScrollbackPush), make_md(L!("self-insert"), ReadlineCmd::SelfInsert), make_md(L!("self-insert-notfirst"), ReadlineCmd::SelfInsertNotFirst), make_md(L!("suppress-autosuggestion"), ReadlineCmd::SuppressAutosuggestion), diff --git a/src/input_common.rs b/src/input_common.rs index abc3f8134..1996ca11e 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -131,6 +131,7 @@ pub enum ReadlineCmd { EndUndoGroup, RepeatJump, ClearScreenAndRepaint, + ScrollbackPush, // NOTE: This one has to be last. ReverseRepeatJump, } @@ -191,6 +192,8 @@ pub enum ImplicitEvent { DisableMouseTracking, /// Handle mouse left click. MouseLeftClickContinuation(ViewportPosition, ViewportPosition), + /// Push prompt to top. + ScrollbackPushContinuation(usize), } #[derive(Debug, Clone)] @@ -590,6 +593,7 @@ pub fn function_set_status(&mut self, status: bool) { pub enum WaitingForCursorPosition { MouseLeft(ViewportPosition), + ScrollbackPush, } /// A trait which knows how to produce a stream of input events. @@ -1025,6 +1029,9 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { *click_position, ) } + WaitingForCursorPosition::ScrollbackPush => { + ImplicitEvent::ScrollbackPushContinuation(y) + } }; self.push_front(CharEvent::Implicit(continuation)); return None; diff --git a/src/reader.rs b/src/reader.rs index f471b9198..d7a2676c6 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2275,6 +2275,10 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo self.mouse_left_click(cursor, click_position); self.stop_waiting_for_cursor_position(); } + ImplicitEvent::ScrollbackPushContinuation(cursor_y) => { + self.screen.push_to_scrollback(cursor_y); + self.stop_waiting_for_cursor_position(); + } }, } ControlFlow::Continue(()) @@ -3499,6 +3503,9 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { self.force_exec_prompt_and_repaint = false; self.parser.libdata_mut().is_repaint = false; } + rl::ScrollbackPush => { + self.request_cursor_position(WaitingForCursorPosition::ScrollbackPush); + } rl::SelfInsert | rl::SelfInsertNotFirst | rl::FuncAnd | rl::FuncOr => { // This can be reached via `commandline -f and` etc // panic!("should have been handled by inputter_t::readch"); diff --git a/src/screen.rs b/src/screen.rs index dcc571666..66421a3f6 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -488,6 +488,28 @@ pub fn move_to_end(&mut self) { self.r#move(0, self.actual.line_count()); } + pub fn push_to_scrollback(&mut self, cursor_y: usize) { + let mut prompt_y = self.command_line_y_given_cursor_y(cursor_y); + prompt_y -= calc_prompt_lines(&self.actual_left_prompt) - 1; + if prompt_y == 0 { + return; + } + let zelf = self.scoped_buffer(); + let Some(term) = term() else { + return; + }; + let mut out = zelf.outp.borrow_mut(); + let prompt_y = i32::try_from(prompt_y).unwrap(); + // Scroll down. + if let Some(scroll) = term.parm_index.as_ref() { + out.tputs_if_some(&tparm1(scroll, prompt_y)); + } + // Reposition cursor. + if let Some(up) = term.parm_cursor_up.as_ref() { + out.tputs_if_some(&tparm1(up, prompt_y)); + } + } + fn command_line_y_given_cursor_y(&mut self, viewport_cursor_y: usize) -> usize { let prompt_y = viewport_cursor_y.checked_sub(self.actual.cursor.y); prompt_y.unwrap_or_else(|| { diff --git a/tests/checks/tmux-complete.fish b/tests/checks/tmux-complete.fish index 9137a05b1..820af9619 100644 --- a/tests/checks/tmux-complete.fish +++ b/tests/checks/tmux-complete.fish @@ -43,7 +43,7 @@ isolated-tmux capture-pane -p | sed -n '1p;$p' # Also ensure that the pager is actually fully disclosed. # CHECK: rows 1 to {{\d+}} of {{\d+}} -# Canceling the pager removes the inserted completion, no mater what happens in the search field. +# Canceling the pager removes the inserted completion, no matter what happens in the search field. # The common prefix remains because it is inserted before the pager is shown. isolated-tmux send-keys C-c tmux-sleep diff --git a/tests/checks/tmux-history-search.fish b/tests/checks/tmux-history-search.fish index 07a887276..b9ae96900 100644 --- a/tests/checks/tmux-history-search.fish +++ b/tests/checks/tmux-history-search.fish @@ -47,8 +47,7 @@ isolated-tmux capture-pane -p | grep 'prompt 2>' isolated-tmux send-keys C-c isolated-tmux send-keys 'echo 1' Enter 'echo 2' Enter 'echo 3' Enter -isolated-tmux send-keys C-l echo Up -isolated-tmux send-keys echo M-d +isolated-tmux send-keys C-l echo Up M-d tmux-sleep isolated-tmux capture-pane -p #CHECK: prompt 5> echo 2 diff --git a/tests/pexpects/bind.py b/tests/pexpects/bind.py index 991325e33..198bd1556 100644 --- a/tests/pexpects/bind.py +++ b/tests/pexpects/bind.py @@ -22,6 +22,8 @@ send, sendline, sleep, expect_prompt, expect_re, expect_str = ( ) expect_prompt() +sendline("bind ctrl-l repaint") +expect_prompt() # Clear twice (regression test for #7280). send("\f") expect_prompt(increment=False)