Render overflown commandline in entirety just before executing
Some checks are pending
make test / ubuntu (push) Waiting to run
make test / ubuntu-32bit-static-pcre2 (push) Waiting to run
make test / ubuntu-asan (push) Waiting to run
make test / macos (push) Waiting to run
Rust checks / rustfmt (push) Waiting to run
Rust checks / clippy (push) Waiting to run

As of 04c913427 (Limit command line rendering to $LINES lines,
2024-10-25), we only render a part of the command line.  This removes
valuable information from scrollback.
The reasons for the limit were
1. to enable redrawing the commandline (can't do that if part of it
   is off-screen).
2. if the cursor is at the beginning of the command-line, we can't
   really render the off-screen suffix (unless we can tell the terminal
   to scroll back after doing that).

Fortunately these don't matter for the very last rendering of a
command line.  Let's render the entire command just before executing,
fixing the scrollback for executed commands.

In future, we should fix it also for pre-execution renderings. This
needs a terminal command to clear part of the scrollback.  Can't find
anything on https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
There is "Erase Saved Lines" but that deletes the entire scrollback.

See the discussion in #10827
This commit is contained in:
Johannes Altmanninger 2024-11-02 02:53:52 +01:00
parent 04d97e936a
commit cfcf415db7
2 changed files with 40 additions and 21 deletions

View File

@ -1456,12 +1456,17 @@ impl<'a> Reader<'a> {
/// If `mcolors` has a value, then apply it; otherwise extend existing colors. /// If `mcolors` has a value, then apply it; otherwise extend existing colors.
fn layout_and_repaint(&mut self, reason: &wstr) { fn layout_and_repaint(&mut self, reason: &wstr) {
self.rendered_layout = self.make_layout_data(); self.rendered_layout = self.make_layout_data();
self.paint_layout(reason); self.paint_layout(reason, false);
}
fn layout_and_repaint_before_execution(&mut self) {
self.rendered_layout = self.make_layout_data();
self.paint_layout(L!("prepare to execute"), true);
} }
/// Paint the last rendered layout. /// Paint the last rendered layout.
/// `reason` is used in FLOG to explain why. /// `reason` is used in FLOG to explain why.
fn paint_layout(&mut self, reason: &wstr) { fn paint_layout(&mut self, reason: &wstr, is_final_rendering: bool) {
FLOGF!(reader_render, "Repainting from %ls", reason); FLOGF!(reader_render, "Repainting from %ls", reason);
let data = &self.data.rendered_layout; let data = &self.data.rendered_layout;
let cmd_line = &self.data.command_line; let cmd_line = &self.data.command_line;
@ -1526,6 +1531,7 @@ impl<'a> Reader<'a> {
self.parser.vars(), self.parser.vars(),
pager, pager,
current_page_rendering, current_page_rendering,
is_final_rendering,
); );
} }
} }
@ -1949,8 +1955,9 @@ impl<'a> Reader<'a> {
// Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the // Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the
// user presses enter. // user presses enter.
if zelf.is_repaint_needed(None) || zelf.conf.inputfd != STDIN_FILENO { if zelf.is_repaint_needed(None) || zelf.screen.scrolled || zelf.conf.inputfd != STDIN_FILENO
zelf.layout_and_repaint(L!("prepare to execute")); {
zelf.layout_and_repaint_before_execution();
} }
// Finish syntax highlighting (but do not wait forever). // Finish syntax highlighting (but do not wait forever).
@ -3635,7 +3642,7 @@ impl<'a> Reader<'a> {
data.colors[i].background = HighlightRole::search_match; data.colors[i].background = HighlightRole::search_match;
} }
self.rendered_layout = data.clone(); // need to copy the data since we will use it again. self.rendered_layout = data.clone(); // need to copy the data since we will use it again.
self.paint_layout(L!("flash")); self.paint_layout(L!("flash"), false);
let _old_data = std::mem::take(&mut self.rendered_layout); let _old_data = std::mem::take(&mut self.rendered_layout);
@ -3644,7 +3651,7 @@ impl<'a> Reader<'a> {
// Re-render with our saved data. // Re-render with our saved data.
data.colors = saved_colors; data.colors = saved_colors;
self.rendered_layout = data; self.rendered_layout = data;
self.paint_layout(L!("unflash")); self.paint_layout(L!("unflash"), false);
// Save the time we stopped flashing as the time of the most recent flash. We can't just // Save the time we stopped flashing as the time of the most recent flash. We can't just
// increment the old `now` value because the sleep is non-deterministic. // increment the old `now` value because the sleep is non-deterministic.

View File

@ -177,6 +177,8 @@ impl ScreenData {
pub struct Screen { pub struct Screen {
/// Whether the last-drawn autosuggestion (if any) is truncated, or hidden entirely. /// Whether the last-drawn autosuggestion (if any) is truncated, or hidden entirely.
pub autosuggestion_is_truncated: bool, pub autosuggestion_is_truncated: bool,
/// True if the last rendering was so large we could only display part of the command line.
pub scrolled: bool,
/// Receiver for our output. /// Receiver for our output.
outp: &'static RefCell<Outputter>, outp: &'static RefCell<Outputter>,
@ -213,6 +215,7 @@ impl Screen {
Self { Self {
outp: Outputter::stdoutput(), outp: Outputter::stdoutput(),
autosuggestion_is_truncated: Default::default(), autosuggestion_is_truncated: Default::default(),
scrolled: Default::default(),
desired: Default::default(), desired: Default::default(),
actual: Default::default(), actual: Default::default(),
actual_left_prompt: Default::default(), actual_left_prompt: Default::default(),
@ -250,6 +253,7 @@ impl Screen {
vars: &dyn Environment, vars: &dyn Environment,
pager: &mut Pager, pager: &mut Pager,
page_rendering: &mut PageRendering, page_rendering: &mut PageRendering,
is_final_rendering: bool,
) { ) {
let curr_termsize = termsize_last(); let curr_termsize = termsize_last();
let screen_width = curr_termsize.width; let screen_width = curr_termsize.width;
@ -360,15 +364,19 @@ impl Screen {
break scrolled_cursor.unwrap(); break scrolled_cursor.unwrap();
} }
if !self.desired_append_char( if !self.desired_append_char(
scrolled_cursor if is_final_rendering {
.map(|sc| { usize::MAX
if sc.scroll_amount != 0 { } else {
sc.cursor.y scrolled_cursor
} else { .map(|sc| {
screen_height - 1 if sc.scroll_amount != 0 {
} sc.cursor.y
}) } else {
.unwrap_or(usize::MAX), screen_height - 1
}
})
.unwrap_or(usize::MAX)
},
effective_commandline.as_char_slice()[i], effective_commandline.as_char_slice()[i],
colors[i], colors[i],
usize::try_from(indent[i]).unwrap(), usize::try_from(indent[i]).unwrap(),
@ -405,7 +413,9 @@ impl Screen {
scroll_amount, scroll_amount,
} = scrolled_cursor; } = scrolled_cursor;
if scroll_amount != 0 { if scroll_amount != 0 {
self.desired.line_datas = self.desired.line_datas.split_off(scroll_amount); if !is_final_rendering {
self.desired.line_datas = self.desired.line_datas.split_off(scroll_amount);
}
cursor.y -= scroll_amount; cursor.y -= scroll_amount;
} }
cursor cursor
@ -423,11 +433,13 @@ impl Screen {
// Append pager_data (none if empty). // Append pager_data (none if empty).
self.desired.append_lines(&page_rendering.screen_data); self.desired.append_lines(&page_rendering.screen_data);
self.scrolled = scrolled_cursor.scroll_amount != 0;
self.update( self.update(
vars, vars,
&layout.left_prompt, &layout.left_prompt,
&layout.right_prompt, &layout.right_prompt,
scrolled_cursor.scroll_amount != 0, is_final_rendering,
); );
self.save_status(); self.save_status();
} }
@ -852,7 +864,7 @@ impl Screen {
vars: &dyn Environment, vars: &dyn Environment,
left_prompt: &wstr, left_prompt: &wstr,
right_prompt: &wstr, right_prompt: &wstr,
scrolled: bool, is_final_rendering: bool,
) { ) {
// Helper function to set a resolved color, using the caching resolver. // Helper function to set a resolved color, using the caching resolver.
let mut color_resolver = HighlightColorResolver::new(); let mut color_resolver = HighlightColorResolver::new();
@ -909,14 +921,14 @@ impl Screen {
let term = term.as_ref(); let term = term.as_ref();
// Output the left prompt if it has changed. // Output the left prompt if it has changed.
if scrolled { if zelf.scrolled && !is_final_rendering {
zelf.r#move(0, 0); zelf.r#move(0, 0);
zelf.outp zelf.outp
.borrow_mut() .borrow_mut()
.tputs_if_some(&term.and_then(|term| term.clr_eol.as_ref())); .tputs_if_some(&term.and_then(|term| term.clr_eol.as_ref()));
zelf.actual_left_prompt.clear(); zelf.actual_left_prompt.clear();
zelf.actual.cursor.x = 0; zelf.actual.cursor.x = 0;
} else if left_prompt != zelf.actual_left_prompt { } else if left_prompt != zelf.actual_left_prompt || (zelf.scrolled && is_final_rendering) {
zelf.r#move(0, 0); zelf.r#move(0, 0);
let mut start = 0; let mut start = 0;
let osc_133_prompt_start = let osc_133_prompt_start =
@ -966,7 +978,7 @@ impl Screen {
// Note that skip_remaining is a width, not a character count. // Note that skip_remaining is a width, not a character count.
let mut skip_remaining = start_pos; let mut skip_remaining = start_pos;
let shared_prefix = if scrolled { let shared_prefix = if zelf.scrolled {
0 0
} else { } else {
line_shared_prefix(o_line(&zelf, i), s_line(&zelf, i)) line_shared_prefix(o_line(&zelf, i), s_line(&zelf, i))