Skip to content

Commit 5943350

Browse files
committed
feat: add clear_all function that clears screen AND scrollback
1 parent fc0305a commit 5943350

File tree

2 files changed

+38
-18
lines changed

2 files changed

+38
-18
lines changed

Lib/_pyrepl/unix_console.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,13 @@ def __init__(self):
125125

126126
def register(self, fd, flag):
127127
self.fd = fd
128+
128129
# note: The 'timeout' argument is received as *milliseconds*
129130
def poll(self, timeout: float | None = None) -> list[int]:
130131
if timeout is None:
131132
r, w, e = select.select([self.fd], [], [])
132133
else:
133-
r, w, e = select.select([self.fd], [], [], timeout/1000)
134+
r, w, e = select.select([self.fd], [], [], timeout / 1000)
134135
return r
135136

136137
poll = MinimalPoll # type: ignore[assignment]
@@ -213,7 +214,6 @@ def _sigcont_handler(self, signum, frame):
213214
def __read(self, n: int) -> bytes:
214215
return os.read(self.input_fd, n)
215216

216-
217217
def change_encoding(self, encoding: str) -> None:
218218
"""
219219
Change the encoding used for I/O operations.
@@ -339,7 +339,10 @@ def prepare(self):
339339
tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
340340

341341
# In macOS terminal we need to deactivate line wrap via ANSI escape code
342-
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
342+
if (
343+
platform.system() == "Darwin"
344+
and os.getenv("TERM_PROGRAM") == "Apple_Terminal"
345+
):
343346
os.write(self.output_fd, b"\033[?7l")
344347

345348
self.screen = []
@@ -370,7 +373,10 @@ def restore(self):
370373
self.flushoutput()
371374
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
372375

373-
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
376+
if (
377+
platform.system() == "Darwin"
378+
and os.getenv("TERM_PROGRAM") == "Apple_Terminal"
379+
):
374380
os.write(self.output_fd, b"\033[?7h")
375381

376382
if hasattr(self, "old_sigwinch"):
@@ -417,10 +423,7 @@ def wait(self, timeout: float | None = None) -> bool:
417423
"""
418424
Wait for events on the console.
419425
"""
420-
return (
421-
not self.event_queue.empty()
422-
or bool(self.pollob.poll(timeout))
423-
)
426+
return not self.event_queue.empty() or bool(self.pollob.poll(timeout))
424427

425428
def set_cursor_vis(self, visible):
426429
"""
@@ -561,6 +564,11 @@ def clear(self):
561564
self.posxy = 0, 0
562565
self.screen = []
563566

567+
def clear_all(self) -> None:
568+
"""Clear screen and scrollback buffer."""
569+
self.__write("\033[3J\033[2J\033[H")
570+
self.clear()
571+
564572
@property
565573
def input_hook(self):
566574
# avoid inline imports here so the repl doesn't get flooded

Lib/_pyrepl/windows_console.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
5959
self.err = err
6060
self.descr = descr
6161

62+
6263
# declare nt optional to allow None assignment on other platforms
6364
nt: types.ModuleType | None
6465
try:
@@ -126,12 +127,14 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
126127
class _error(Exception):
127128
pass
128129

130+
129131
def _supports_vt():
130132
try:
131133
return nt._supports_virtual_terminal()
132134
except AttributeError:
133135
return False
134136

137+
135138
class WindowsConsole(Console):
136139
def __init__(
137140
self,
@@ -145,12 +148,12 @@ def __init__(
145148
self.__vt_support = _supports_vt()
146149

147150
if self.__vt_support:
148-
trace('console supports virtual terminal')
151+
trace("console supports virtual terminal")
149152

150153
# Save original console modes so we can recover on cleanup.
151154
original_input_mode = DWORD()
152155
GetConsoleMode(InHandle, original_input_mode)
153-
trace(f'saved original input mode 0x{original_input_mode.value:x}')
156+
trace(f"saved original input mode 0x{original_input_mode.value:x}")
154157
self.__original_input_mode = original_input_mode.value
155158

156159
SetConsoleMode(
@@ -289,7 +292,7 @@ def __write_changed_line(
289292
else:
290293
self.posxy = wlen(newline), y
291294

292-
if "\x1b" in newline or y != self.posxy[1] or '\x1a' in newline:
295+
if "\x1b" in newline or y != self.posxy[1] or "\x1a" in newline:
293296
# ANSI escape characters are present, so we can't assume
294297
# anything about the position of the cursor. Moving the cursor
295298
# to the left margin should work to get to a known position.
@@ -334,7 +337,7 @@ def _disable_bracketed_paste(self) -> None:
334337

335338
def __write(self, text: str) -> None:
336339
if "\x1a" in text:
337-
text = ''.join(["^Z" if x == '\x1a' else x for x in text])
340+
text = "".join(["^Z" if x == "\x1a" else x for x in text])
338341

339342
if self.out is not None:
340343
self.out.write(text.encode(self.encoding, "replace"))
@@ -362,7 +365,9 @@ def prepare(self) -> None:
362365
self.__offset = 0
363366

364367
if self.__vt_support:
365-
SetConsoleMode(InHandle, self.__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT)
368+
SetConsoleMode(
369+
InHandle, self.__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT
370+
)
366371
self._enable_bracketed_paste()
367372

368373
def restore(self) -> None:
@@ -427,9 +432,7 @@ def _read_input(self) -> INPUT_RECORD | None:
427432

428433
return rec
429434

430-
def _read_input_bulk(
431-
self, n: int
432-
) -> tuple[ctypes.Array[INPUT_RECORD], int]:
435+
def _read_input_bulk(self, n: int) -> tuple[ctypes.Array[INPUT_RECORD], int]:
433436
rec = (n * INPUT_RECORD)()
434437
read = DWORD()
435438
if not ReadConsoleInput(InHandle, rec, n, read):
@@ -477,7 +480,9 @@ def get_event(self, block: bool = True) -> Event | None:
477480
elif key_event.dwControlKeyState & ALT_ACTIVE:
478481
# queue the key, return the meta command
479482
self.event_queue.insert(Event(evt="key", data=key))
480-
return Event(evt="key", data="\033") # keymap.py uses this for meta
483+
return Event(
484+
evt="key", data="\033"
485+
) # keymap.py uses this for meta
481486
return Event(evt="key", data=key)
482487
if block:
483488
continue
@@ -516,6 +521,14 @@ def clear(self) -> None:
516521
self.posxy = 0, 0
517522
self.screen = [""]
518523

524+
525+
def clear_all(self) -> None:
526+
"""Clear screen and scrollback buffer."""
527+
os.system("cls") # This might not clear scrollback
528+
# Fallback to ANSI escape sequences if terminal supports them
529+
self.write_code("\033[3J\033[2J\033[H")
530+
self.clear()
531+
519532
def finish(self) -> None:
520533
"""Move the cursor to the end of the display and otherwise get
521534
ready for end. XXX could be merged with restore? Hmm."""
@@ -697,7 +710,6 @@ class INPUT_RECORD(Structure):
697710
ReadConsoleInput.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, POINTER(DWORD)]
698711
ReadConsoleInput.restype = BOOL
699712

700-
701713
FlushConsoleInputBuffer = _KERNEL32.FlushConsoleInputBuffer
702714
FlushConsoleInputBuffer.argtypes = [HANDLE]
703715
FlushConsoleInputBuffer.restype = BOOL

0 commit comments

Comments
 (0)