From 289057f981cc6ff125bfbde87e798027eb69fa88 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Tue, 11 Nov 2025 18:09:29 +0100 Subject: [PATCH] reset_abandoning_line: actually clear line on first prompt Frequently when I launch a new shell and type away, my input is echoed in the terminal before fish gets a chance to set shell modes (turn off ECHO and put the terminal into non-canonical mode). This means that fish assumption about the cursor x=0 is wrong, which causes the prompt (here '$ ') to be draw at the wrong place, making it look like this: ececho hello This seems to have been introduced in 4.1.0 in a0e687965e8 (Fix unsaved screen modification, 2025-01-14). Not sure how it wasn't a problem before. Fix this by clearing to the beginning of the line after turning off ECHO but before we draw anything to the screen. This turns this comment in the patch context into a true statement: > This means that `printf %s foo; fish` will overwrite the `foo` Note that this currently applies per-reader, so builtin "read" will also clear the line before doing anything. We could potentially change this in future, if we both 1. query the cursor x before we output anything 2. refactor the screen module to properly deal with x>0 states. But even if we did that, it wouldn't change the fact that we want to force x=0 if the cursor has only been moved due to ECHO, so I'm not sure. --- CHANGELOG.rst | 13 +++++++++++++ src/reader/reader.rs | 15 +++++++-------- src/screen.rs | 18 +++++++++++++++--- tests/checks/tmux-first-prompt.fish | 15 +++++++++++++++ tests/test_functions/isolated-tmux-start.fish | 4 ++++ 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 tests/checks/tmux-first-prompt.fish diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3fd4c739..506dc31e7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,19 @@ fish ?.?.? (released ???) ========================= +Notable improvements and fixes +------------------------------ + +Deprecations and removed features +--------------------------------- + +Interactive improvements +------------------------ +- When typing immediately after starting fish, the first prompt is now rendered correctly. + +For distributors and developers +------------------------------- + fish 4.2.1 (released November 13, 2025) ======================================= diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 38d16464a..2d39822d2 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -392,7 +392,7 @@ pub fn reader_pop() { if let Some(new_reader) = current_data() { new_reader .screen - .reset_abandoning_line(termsize_last().width()); + .reset_abandoning_line(Some(termsize_last().width())); } else { Outputter::stdoutput().borrow_mut().reset_text_face(); *commandline_state_snapshot() = CommandlineState::new(); @@ -2338,12 +2338,9 @@ fn readline( // This means that `printf %s foo; fish` will overwrite the `foo`, // but that's a smaller problem than having the omitted newline char // appear constantly. - // - // I can't see a good way around this. - if !self.first_prompt { - self.screen.reset_abandoning_line(termsize_last().width()); - } + let trusted_width = (!self.first_prompt).then_some(termsize_last().width()); self.first_prompt = false; + self.screen.reset_abandoning_line(trusted_width); if !self.conf.event.is_empty() { event::fire_generic(self.parser, self.conf.event.to_owned(), vec![]); @@ -2836,7 +2833,8 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { Edit::new(0..self.command_line_len(), L!("").to_owned()), ); if c == rl::CancelCommandline { - self.screen.reset_abandoning_line(termsize_last().width()); + self.screen + .reset_abandoning_line(Some(termsize_last().width())); } // Post fish_cancel. @@ -3117,7 +3115,8 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { L!("fish_posterror").to_owned(), vec![self.command_line.text().to_owned()], ); - self.screen.reset_abandoning_line(termsize_last().width()); + self.screen + .reset_abandoning_line(Some(termsize_last().width())); } } rl::HistoryPrefixSearchBackward diff --git a/src/screen.rs b/src/screen.rs index b33bc971f..e2783e4de 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -672,7 +672,7 @@ pub fn offset_in_cmdline_given_cursor( /// abandoning the current line and going to the next line. /// If clear_to_eos is set, /// The screen width must be provided for the PROMPT_SP hack. - pub fn reset_abandoning_line(&mut self, screen_width: usize) { + pub fn reset_abandoning_line(&mut self, screen_width: Option) { self.actual.cursor.y = 0; self.actual.clear_lines(); self.actual_left_prompt = None; @@ -685,11 +685,16 @@ pub fn reset_abandoning_line(&mut self, screen_width: usize) { } } -fn abandon_line_string(screen_width: usize) -> Vec { +fn abandon_line_string(screen_width: Option) -> Vec { use std::iter::repeat_n; - // Do the PROMPT_SP hack. + + let Some(screen_width) = screen_width else { + return vec![b'\r']; + }; + let mut abandon_line_string = Vec::with_capacity(screen_width + 32); + // Do the PROMPT_SP hack. // Don't need to check for fish_wcwidth errors; this is done when setting up // omitted_newline_char in common.rs. let non_space_width = get_omitted_newline_width(); @@ -752,6 +757,13 @@ fn abandon_line_string(screen_width: usize) -> Vec { // pasting your terminal log becomes a pain. This commit clears that line, making it an // actual empty line. abandon_line_string.write_command(ClearToEndOfLine); + abandon_line_string.push(b'\r'); + // Clear entire line. Zsh doesn't do this. Fish added this with commit 4417a6ee: If you have + // a prompt preceded by a new line, you'll get a line full of spaces instead of an empty + // line above your prompt. This doesn't make a difference in normal usage, but copying and + // pasting your terminal log becomes a pain. This commit clears that line, making it an + // actual empty line. + abandon_line_string.write_command(ClearToEndOfLine); abandon_line_string } diff --git a/tests/checks/tmux-first-prompt.fish b/tests/checks/tmux-first-prompt.fish new file mode 100644 index 000000000..90b87f24a --- /dev/null +++ b/tests/checks/tmux-first-prompt.fish @@ -0,0 +1,15 @@ +#RUN: %fish %s +#REQUIRES: command -v tmux + +tmux_wait=false \ + isolated-tmux-start -C ' + function fish_prompt + command printf "%% " + end +' + +isolated-tmux send-keys 'echo hello' +tmux-sleep +tmux-sleep +isolated-tmux capture-pane -p +# CHECK: % echo hello diff --git a/tests/test_functions/isolated-tmux-start.fish b/tests/test_functions/isolated-tmux-start.fish index 1796ebac4..984ef321b 100644 --- a/tests/test_functions/isolated-tmux-start.fish +++ b/tests/test_functions/isolated-tmux-start.fish @@ -57,6 +57,10 @@ function isolated-tmux-start --wraps fish # Resize window so we can attach to tmux session without changing panel size. isolated-tmux resize-window $size + if test "$tmux_wait" = false + return + end + # Loop a bit, until we get an initial prompt. for i in (seq 50) if test -n "$(isolated-tmux capture-pane -p)"