diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94412f6ec..2c2588f71 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,36 @@ +fish 4.1.0 (released ???) +========================= + +Notable improvements and fixes +------------------------------ + +Deprecations and removed features +--------------------------------- + +Scripting improvements +---------------------- + +Interactive improvements +------------------------ + +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. + +Completions +^^^^^^^^^^^ + +Improved terminal support +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Other improvements +------------------ + +For distributors +---------------- + +-------------- + fish 4.0b1 (released December 17, 2024) ======================================= diff --git a/build_tools/pexpect_helper.py b/build_tools/pexpect_helper.py index 347c699f5..e8bb143e4 100644 --- a/build_tools/pexpect_helper.py +++ b/build_tools/pexpect_helper.py @@ -129,6 +129,10 @@ class Message(object): """Return a output message with the given text.""" return Message(Message.DIR_OUTPUT, text, when) +# Sequences for moving the cursor below the commandline. This happens before executing. +MOVE_TO_END: str = r"(?:\r\n|\x1b\[2 q)" +TO_END: str = MOVE_TO_END + r"[^\n]*" +TO_END_SUFFIX: str = r"[^\n]*" + MOVE_TO_END class SpawnedProc(object): """A process, talking to our ptty. This wraps pexpect.spawn. diff --git a/src/reader.rs b/src/reader.rs index 3bbb491cc..f9550b829 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1972,11 +1972,8 @@ impl<'a> Reader<'a> { zelf.finish_highlighting_before_exec(); } - // Emit a newline so that the output is on the line after the command. - // But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826. - if !zelf.screen.cursor_is_wrapped_to_own_line() { - let _ = write_to_fd(b"\n", STDOUT_FILENO); - } + // Move the cursor so that output is on the line after the command. + zelf.screen.move_to_end(); // HACK: If stdin isn't the same terminal as stdout, we just moved the cursor. // For now, just reset it to the beginning of the line. @@ -3582,7 +3579,6 @@ impl<'a> Reader<'a> { self.add_to_history(); self.rls_mut().finished = true; - self.update_buff_pos(elt, Some(self.command_line_len())); true } diff --git a/src/screen.rs b/src/screen.rs index 479622198..29ea5731c 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -473,6 +473,10 @@ impl Screen { self.save_status(); } + pub fn move_to_end(&mut self) { + self.r#move(0, self.actual.line_count()); + } + /// Resets the screen buffer's internal knowledge about the contents of the screen, /// abandoning the current line and going to the next line. /// If clear_to_eos is set, diff --git a/tests/pexpects/bind.py b/tests/pexpects/bind.py index 75ef600cc..c42e0e7e0 100644 --- a/tests/pexpects/bind.py +++ b/tests/pexpects/bind.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END import os import platform import sys @@ -42,7 +42,7 @@ expect_prompt("") # Start by testing with no delay. This should transpose the words. send("echo abc def") send("\033t\r") -expect_prompt("\r\n.*def abc\r\n") # emacs transpose words, default timeout: no delay +expect_prompt(TO_END + "def abc\r\n") # emacs transpose words, default timeout: no delay # Now test with a delay > 0 and < the escape timeout. This should transpose # the words. @@ -51,7 +51,7 @@ send("\033") sleep(0.010) send("t\r") # emacs transpose words, default timeout: short delay -expect_prompt("\r\n.*jkl ghi\r\n") +expect_prompt(TO_END + "jkl ghi\r\n") # Now test with a delay > the escape timeout. The transposition should not # occur and the "t" should become part of the text that is echoed. @@ -60,11 +60,11 @@ send("\033") sleep(0.250) send("t\r") # emacs transpose words, default timeout: long delay -expect_prompt("\r\n.*mno pqrt\r\n") +expect_prompt(TO_END + "mno pqrt\r\n") # Now test that exactly the expected bind modes are defined sendline("bind --list-modes") -expect_prompt("\r\n.*default", unmatched="Unexpected bind modes") +expect_prompt(TO_END + "default", unmatched="Unexpected bind modes") # Test vi key bindings. # This should leave vi mode in the insert state. @@ -74,7 +74,7 @@ expect_prompt() # Go through a prompt cycle to let fish catch up, it may be slow due to ASAN sendline("echo success: default escape timeout") expect_prompt( - "\r\n.*success: default escape timeout", unmatched="prime vi mode, default timeout" + TO_END + "success: default escape timeout", unmatched="prime vi mode, default timeout" ) send("echo fail: default escape timeout") @@ -88,7 +88,7 @@ sleep(0.250) send("ddi") sendline("echo success: default escape timeout") expect_prompt( - "\r\n.*success: default escape timeout\r\n", + TO_END + "success: default escape timeout\r\n", unmatched="vi replace line, default timeout: long delay", ) @@ -103,7 +103,7 @@ send("\033") sleep(0.400) send("hhrAi\r") expect_prompt( - "\r\n.*TAXT\r\n", unmatched="vi mode replace char, default timeout: long delay" + TO_END + "TAXT\r\n", unmatched="vi mode replace char, default timeout: long delay" ) # Test deleting characters with 'x'. @@ -115,7 +115,7 @@ send("xxxxx\r") # vi mode delete char, default timeout: long delay expect_prompt( - "\r\n.*MORE\r\n", unmatched="vi mode delete char, default timeout: long delay" + TO_END + "MORE\r\n", unmatched="vi mode delete char, default timeout: long delay" ) # Test jumping forward til before a character with t @@ -127,7 +127,7 @@ send("0tTD\r") # vi mode forward-jump-till character, default timeout: long delay expect_prompt( - "\r\n.*MORE\r\n", + TO_END + "MORE\r\n", unmatched="vi mode forward-jump-till character, default timeout: long delay", ) @@ -140,7 +140,7 @@ expect_prompt( # send("TSD\r") # # vi mode backward-jump-till character, default timeout: long delay # expect_prompt( -# "\r\n.*MORE-TEXT-IS\r\n", +# TO_END + "MORE-TEXT-IS\r\n", # unmatched="vi mode backward-jump-till character, default timeout: long delay", # ) @@ -152,7 +152,7 @@ sleep(0.250) send("F-;D\r") # vi mode backward-jump-to character and repeat, default timeout: long delay expect_prompt( - "\r\n.*MORE-TEXT\r\n", + TO_END + "MORE-TEXT\r\n", unmatched="vi mode backward-jump-to character and repeat, default timeout: long delay", ) @@ -164,7 +164,7 @@ sleep(0.250) send("F-F-,D\r") # vi mode backward-jump-to character, and reverse, default timeout: long delay expect_prompt( - "\r\n.*MORE-TEXT-IS\r\n", + TO_END + "MORE-TEXT-IS\r\n", unmatched="vi mode backward-jump-to character, and reverse, default timeout: long delay", ) @@ -179,7 +179,7 @@ send("ddi") sleep(0.25) send("echo success: lengthened escape timeout\r") expect_prompt( - "\r\n.*success: lengthened escape timeout\r\n", + TO_END + "success: lengthened escape timeout\r\n", unmatched="vi replace line, 100ms timeout: long delay", ) @@ -191,7 +191,7 @@ sleep(0.010) send("ddi") send("inserted\r") expect_prompt( - "\r\n.*fail: no normal modediinserted\r\n", + TO_END + "fail: no normal modediinserted\r\n", unmatched="vi replace line, 100ms timeout: short delay", ) @@ -208,7 +208,7 @@ expect_str("echo TEXT") send("\033") sleep(0.200) send("hhtTrN\r") -expect_prompt("\r\n.*TENT\r\n", unmatched="Couldn't find expected output 'TENT'") +expect_prompt(TO_END + "TENT\r\n", unmatched="Couldn't find expected output 'TENT'") # Test sequence key delay send("set -g fish_sequence_key_delay_ms 200\r") @@ -239,7 +239,7 @@ expect_prompt("foo") # send("echo some TExT\033") # sleep(0.300) # send("hh~~bbve~\r") -# expect_prompt("\r\n.*SOME TeXT\r\n", unmatched="Couldn't find expected output 'SOME TeXT") +# expect_prompt(TO_END + "SOME TeXT\r\n", unmatched="Couldn't find expected output 'SOME TeXT") # Now test that exactly the expected bind modes are defined sendline("bind --list-modes") @@ -255,7 +255,7 @@ expect_prompt() # Verify the custom escape timeout set earlier is still in effect. sendline("echo fish_escape_delay_ms=$fish_escape_delay_ms") expect_prompt( - "\r\n.*fish_escape_delay_ms=50\r\n", + TO_END + "fish_escape_delay_ms=50\r\n", unmatched="default-mode custom timeout not set correctly", ) @@ -270,7 +270,7 @@ send("echo abc def") send("\033") send("t\r") expect_prompt( - "\r\n.*def abc\r\n", unmatched="emacs transpose words fail, 200ms timeout: no delay" + TO_END + "def abc\r\n", unmatched="emacs transpose words fail, 200ms timeout: no delay" ) # Verify special characters, such as \cV, are not intercepted by the kernel @@ -301,7 +301,7 @@ expect_prompt() send("foo ") expect_str("echo foonanana") send(" banana\r") -expect_str(" banana\r") +expect_str(" banana") expect_prompt("foonanana banana") # Ensure that nul can be bound properly (#3189). @@ -329,7 +329,7 @@ expect_prompt() send("a b c d\x01") # ctrl-a, move back to the beginning of the line send("\x07") # ctrl-g, kill bigword sendline("echo") -expect_prompt("\n.*b c d") +expect_prompt(TO_END + "b c d") # Test that overriding the escape binding works # and does not inhibit other escape sequences (up-arrow in this case). @@ -345,7 +345,7 @@ expect_prompt() send(" a b c d\x01") # ctrl-a, move back to the beginning of the line send("\x07") # ctrl-g, kill bigword sendline("echo") -expect_prompt("\n.*b c d") +expect_prompt(TO_END + "b c d") # Check that ctrl-z can be bound sendline('bind ctrl-z "echo bound ctrl-z"') diff --git a/tests/pexpects/bind_mode_events.py b/tests/pexpects/bind_mode_events.py index 4ee9c37c2..11db8e078 100644 --- a/tests/pexpects/bind_mode_events.py +++ b/tests/pexpects/bind_mode_events.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END import os import sys import signal @@ -16,7 +16,7 @@ send("set -g fish_key_bindings fish_vi_key_bindings\r") expect_prompt() send("echo ready to go\r") -expect_prompt(f"\r\n.*ready to go\r\n") +expect_prompt(TO_END + f"ready to go\r\n") send( "function add_change --on-variable fish_bind_mode ; set -g MODE_CHANGES $MODE_CHANGES $fish_bind_mode ; end\r" ) @@ -42,7 +42,7 @@ send("i") sleep(10 if "CI" in os.environ else 1) send("echo mode changes: $MODE_CHANGES\r") -expect_prompt("\r\n.*mode changes: default insert default insert\r\n") +expect_prompt(TO_END + "mode changes: default insert default insert\r\n") # Regression test for #8125. # Control-C should return us to insert mode. @@ -70,4 +70,4 @@ sleep(timeout) # We should be back in insert mode now. send("echo mode changes: $MODE_CHANGES\r") -expect_prompt("\r\n.*mode changes: default insert\r\n") +expect_prompt(TO_END + "mode changes: default insert\r\n") diff --git a/tests/pexpects/complete.py b/tests/pexpects/complete.py index 843d4d192..2f4f898a4 100644 --- a/tests/pexpects/complete.py +++ b/tests/pexpects/complete.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END sp = SpawnedProc() send, sendline, sleep, expect_prompt, expect_re, expect_str = ( @@ -75,6 +75,6 @@ send("echo fo\t") expect_re("foooo") send("\x07") sendline("echo bar") -expect_re("\n.*bar") +expect_re(TO_END + "bar") sendline("echo fo\t") expect_re("foooo") diff --git a/tests/pexpects/history.py b/tests/pexpects/history.py index 0e931b1f0..d8612cfb8 100644 --- a/tests/pexpects/history.py +++ b/tests/pexpects/history.py @@ -11,7 +11,7 @@ # The history function might pipe output through the user's pager. We don't # want something like `less` to complicate matters so force the use of `cat`. -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END, TO_END_SUFFIX import os os.environ["PAGER"] = "cat" @@ -98,7 +98,7 @@ expect_prompt("echo start1; builtin history; echo end1\r\n") # ========== # Delete a single command we recently ran. sendline("history delete -e -C 'echo hello'") -expect_prompt("history delete -e -C 'echo hello'\r\n") +expect_prompt("history delete -e -C 'echo hello'" + TO_END_SUFFIX) sendline("echo count hello (history search -e -C 'echo hello' | wc -l | string trim)") expect_prompt("count hello 0\r\n") @@ -107,12 +107,13 @@ expect_prompt("count hello 0\r\n") # delete the first entry matched by the prefix search (the most recent command # sent above that matches). sendline("history delete -p 'echo hello'") -expect_re("history delete -p 'echo hello'\r\n") -expect_re("\[1\] echo hello AGAIN\r\n") -expect_re("\[2\] echo hello again\r\n\r\n") -expect_re( - "Enter nothing to cancel the delete, or\r\nEnter one or more of the entry IDs or ranges like '5..12', separated by a space.\r\nFor example '7 10..15 35 788..812'.\r\nEnter 'all' to delete all the matching entries.\r\n" -) +expect_re("history delete -p 'echo hello'" + TO_END_SUFFIX) +expect_re("\[1\] echo hello AGAIN" + TO_END_SUFFIX) +expect_re("\[2\] echo hello again" + TO_END_SUFFIX) +expect_re("Enter nothing to cancel the delete, or\r\n") +expect_re("Enter one or more of the entry IDs or ranges like '5..12', separated by a space.\r\n") +expect_re("For example '7 10..15 35 788..812'.\r\n") +expect_re("Enter 'all' to delete all the matching entries.\r\n") expect_re("Delete which entries\? ") sendline("1") expect_prompt('Deleting history entry 1: "echo hello AGAIN"\r\n') @@ -177,7 +178,7 @@ expect_prompt() sendline("history clear-session") expect_prompt() sendline("history search --exact 'echo after' | cat") -expect_prompt("\r\n") +expect_prompt() # Check history filtering # We store anything that starts with "echo ephemeral". diff --git a/tests/pexpects/read.py b/tests/pexpects/read.py index 4eabb531e..a831918b3 100644 --- a/tests/pexpects/read.py +++ b/tests/pexpects/read.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END sp = SpawnedProc() send, sendline, sleep, expect_prompt, expect_re, expect_str = ( @@ -17,7 +17,7 @@ def expect_read_prompt(): def expect_marker(text): - expect_prompt("\r\n.*@MARKER:" + str(text) + "@\\r\\n") + expect_prompt(TO_END + "@MARKER:" + str(text) + "@\\r\\n") def print_var_contents(varname, expected): diff --git a/tests/pexpects/status.py b/tests/pexpects/status.py index 01633395a..b805d7a6d 100644 --- a/tests/pexpects/status.py +++ b/tests/pexpects/status.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pexpect_helper import SpawnedProc +from pexpect_helper import SpawnedProc, TO_END sp = SpawnedProc() send, sendline, sleep, expect_prompt, expect_re, expect_str = ( @@ -22,11 +22,11 @@ expect_prompt("") # Validate standalone behavior sendline("status current-commandline") -expect_prompt("\r\n.*status current-commandline\r\n") +expect_prompt(TO_END + "status current-commandline\r\n") # Validate behavior as part of a command chain sendline("true 7 && status current-commandline") -expect_prompt("\r\n.*true 7 && status current-commandline\r\n") +expect_prompt(TO_END + "true 7 && status current-commandline\r\n") # Validate behavior when used in a function sendline("function report; set -g last_cmdline (status current-commandline); end") @@ -34,7 +34,7 @@ expect_prompt("") sendline("report 27") expect_prompt("") sendline("echo $last_cmdline") -expect_prompt("\r\n.*report 27\r\n") +expect_prompt(TO_END + "report 27\r\n") # Exit send("\x04") #