diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 50c824995d85b8..2d92fb3402765e 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -135,6 +135,13 @@ def do(self) -> None: r.dirty = True +class clear_display(Command): + def do(self) -> None: + r = self.reader + r.console.clear_all() + r.dirty = True + + class refresh(Command): def do(self) -> None: self.reader.dirty = True diff --git a/Lib/_pyrepl/console.py b/Lib/_pyrepl/console.py index e0535d50396316..97625310a15b86 100644 --- a/Lib/_pyrepl/console.py +++ b/Lib/_pyrepl/console.py @@ -114,6 +114,11 @@ def clear(self) -> None: """Wipe the screen""" ... + @abstractmethod + def clear_all(self) -> None: + """Clear screen and scrollback buffer.""" + ... + @abstractmethod def finish(self) -> None: """Move the cursor to the end of the display and otherwise get diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 0ebd9162eca4bb..41f96af712b2b2 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -75,6 +75,7 @@ def make_default_commands() -> dict[CommandName, type[Command]]: (r"\", "accept"), (r"\C-k", "kill-line"), (r"\C-l", "clear-screen"), + (r"\C-\M-l", "clear-display"), (r"\C-m", "accept"), (r"\C-t", "transpose-characters"), (r"\C-u", "unix-line-discard"), diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index a7e49923191c07..ba7eed064488d3 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -561,6 +561,11 @@ def clear(self): self.posxy = 0, 0 self.screen = [] + def clear_all(self) -> None: + """Clear screen and scrollback buffer.""" + self.__write("\x1b[3J\x1b[2J\x1b[H") + self.clear() + @property def input_hook(self): # avoid inline imports here so the repl doesn't get flooded diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index c56dcd6d7dd434..6ced21098a924d 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -111,6 +111,7 @@ def __init__(self, err: int | None, descr: str | None = None) -> None: MOVE_UP = "\x1b[{}A" MOVE_DOWN = "\x1b[{}B" CLEAR = "\x1b[H\x1b[J" +CLEAR_ALL= "\x1b[3J\x1b[2J\x1b[H" # State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str ALT_ACTIVE = 0x01 | 0x02 @@ -516,6 +517,12 @@ def clear(self) -> None: self.posxy = 0, 0 self.screen = [""] + + def clear_all(self) -> None: + """Clear screen and scrollback buffer.""" + self.__write(CLEAR_ALL) + self.clear() + def finish(self) -> None: """Move the cursor to the end of the display and otherwise get ready for end. XXX could be merged with restore? Hmm.""" diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py index 4f7f9d77933336..2df2d97fbbe0cb 100644 --- a/Lib/test/test_pyrepl/support.py +++ b/Lib/test/test_pyrepl/support.py @@ -155,6 +155,9 @@ def beep(self) -> None: def clear(self) -> None: pass + def clear_all(self) -> None: + pass + def finish(self) -> None: pass diff --git a/Misc/NEWS.d/next/Library/2025-09-05-15-02-20.gh-issue-134746.9kiF6K.rst b/Misc/NEWS.d/next/Library/2025-09-05-15-02-20.gh-issue-134746.9kiF6K.rst new file mode 100644 index 00000000000000..dc1cbfe913fa4f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-05-15-02-20.gh-issue-134746.9kiF6K.rst @@ -0,0 +1,5 @@ +Added a new ``clear_display`` command in the REPL. ``Ctrl+Alt+L`` now clears +the screen and, if possible, the terminal’s scrollback buffer, then redraws +the current line at the top of the screen. + +Manually tested on Unix; Windows behavior not yet verified.