ctrl-l to scroll content instead of erasing screen

On ctrl-l we send `\e[2J` (Erase in Display).  Some terminals interpret
this to scroll the screen content instead of clearing it. This happens
on VTE-based terminals like gnome-terminal for example.

The traditional behavior of ctrl-l erasing the screen (but not the
rest of the scrollback) is weird because:

1. `ctrl-l` is the easiest and most portable way to push the prompt
   to the top (and repaint after glitches I guess). But it's also a
   destructive action, truncating scrollback. I use it for scrolling
   and am frequently surprised when my scroll back is missing
   information.
2. the amount of lines erased depends on the window size.
   It would be more intuitive to erase by prompts, or erase the text
   in the terminal selection.

Let's use scrolling behavior on all terminals.

The new command could also be named "push-to-scrollback", for
consistency with others. But if we anticipate a want to add other
scrollback-related commands, "scrollback-push" is better.

This causes tests/checks/tmux-history-search.fish to fail; that test
seems pretty broken; M-d (alt-d) is supposed to delete the current
search match but there is a rogue "echo" that is supposed to invalidate
the search match.  I'm not sure how that ever worked.

Also, pexepect doesn't seem to support cursor position reporting,
so work around that.

Ref: https://codeberg.org/dnkl/foot/wiki#how-do-i-make-ctrl-l-scroll-the-content-instead-of-erasing-it
as of wiki commit b57489e298f95d037fdf34da00ea60a5e8eafd6d

Closes #10934
This commit is contained in:
Johannes Altmanninger 2024-12-21 19:41:41 +01:00
parent 84f19a931d
commit 83b0294fc9
11 changed files with 54 additions and 5 deletions

View file

@ -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
^^^^^^^^^^^

View file

@ -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

View file

@ -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

View file

@ -66,8 +66,10 @@ pub struct Term {
pub cursor_down: Option<CString>,
pub cursor_left: Option<CString>,
pub cursor_right: Option<CString>,
pub parm_cursor_up: Option<CString>,
pub parm_left_cursor: Option<CString>,
pub parm_right_cursor: Option<CString>,
pub parm_index: Option<CString>,
pub clr_eol: Option<CString>,
pub clr_eos: Option<CString>,
@ -215,8 +217,10 @@ impl Term {
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<Term> {
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),

View file

@ -198,6 +198,7 @@ const INPUT_FUNCTION_METADATA: &[InputFunctionMetadata] = &[
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),

View file

@ -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 @@ impl InputData {
pub enum WaitingForCursorPosition {
MouseLeft(ViewportPosition),
ScrollbackPush,
}
/// A trait which knows how to produce a stream of input events.
@ -1025,6 +1029,9 @@ pub trait InputEventQueuer {
*click_position,
)
}
WaitingForCursorPosition::ScrollbackPush => {
ImplicitEvent::ScrollbackPushContinuation(y)
}
};
self.push_front(CharEvent::Implicit(continuation));
return None;

View file

@ -2275,6 +2275,10 @@ impl<'a> Reader<'a> {
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 @@ impl<'a> Reader<'a> {
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");

View file

@ -488,6 +488,28 @@ impl Screen {
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(|| {

View file

@ -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

View file

@ -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

View file

@ -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)