Skip to content

Commit

Permalink
use escape sequence for clearing screen and setting cursor position
Browse files Browse the repository at this point in the history
  • Loading branch information
tonybaloney committed May 24, 2024
1 parent 564e6e1 commit 3ae4316
Showing 1 changed file with 32 additions and 50 deletions.
82 changes: 32 additions & 50 deletions Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import os
import sys

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from _pyrepl.console import Event, Console
from .trace import trace
import ctypes
Expand Down Expand Up @@ -103,12 +101,14 @@ class CHAR_INFO(Structure):
SetConsoleMode.argtypes = [HANDLE, DWORD]
SetConsoleMode.restype = BOOL


class Char(Union):
_fields_ = [
("UnicodeChar",WCHAR),
("Char", CHAR),
]


class KeyEvent(ctypes.Structure):
_fields_ = [
("bKeyDown", BOOL),
Expand All @@ -119,6 +119,7 @@ class KeyEvent(ctypes.Structure):
("dwControlKeyState", DWORD),
]


class WindowsBufferSizeEvent(ctypes.Structure):
_fields_ = [
('dwSize', _COORD)
Expand Down Expand Up @@ -152,8 +153,6 @@ class INPUT_RECORD(Structure):
ReadConsoleInput.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, POINTER(DWORD)]
ReadConsoleInput.restype = BOOL



OutHandle = GetStdHandle(STD_OUTPUT_HANDLE)
InHandle = GetStdHandle(STD_INPUT_HANDLE)

Expand Down Expand Up @@ -191,9 +190,11 @@ class INPUT_RECORD(Structure):
ENABLE_WRAP_AT_EOL_OUTPUT = 0x02
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x04


class _error(Exception):
pass


class WindowsConsole(Console):
def __init__(
self,
Expand All @@ -216,7 +217,6 @@ def __init__(
else:
self.output_fd = f_out.fileno()


def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
"""
Refresh the console screen.
Expand Down Expand Up @@ -255,17 +255,17 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
info = CONSOLE_SCREEN_BUFFER_INFO()
if not GetConsoleScreenBufferInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())

bottom = info.srWindow.Bottom

trace("Scrolling {} {} {} {} {}", scroll_lines, info.srWindow.Bottom, self.height, len(screen) - self.height, self.__posxy)
self.scroll(scroll_lines, bottom)
self.__posxy = self.__posxy[0], self.__posxy[1] + scroll_lines
self.__offset += scroll_lines

for i in range(scroll_lines):
self.screen.append("")

elif offset > 0 and len(screen) < offset + height:
trace("Adding extra line")
offset = max(len(screen) - height, 0)
Expand Down Expand Up @@ -315,22 +315,16 @@ def scroll(self, top: int, bottom: int, left: int | None = None, right: int | No
raise ctypes.WinError(ctypes.GetLastError())

def __hide_cursor(self):
info = CONSOLE_CURSOR_INFO()
if not GetConsoleCursorInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())

info.bVisible = False
if not SetConsoleCursorInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())
self.__write("\x1b[?25l")

def __show_cursor(self):
info = CONSOLE_CURSOR_INFO()
if not GetConsoleCursorInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())
self.__write("\x1b[?25h")

info.bVisible = True
if not SetConsoleCursorInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())
def __enable_blinking(self):
self.__write("\x1b[?12h")

def __disable_blinking(self):
self.__write("\x1b[?12l")

def __write(self, text: str):
os.write(self.output_fd, text.encode(self.encoding, "replace"))
Expand Down Expand Up @@ -484,12 +478,7 @@ def __move_absolute(self, x, y):
trace(f"Negative offset: {self.__posxy} {self.screen_xy}")
if x < 0:
trace("Negative move {}", self.getheightwidth())
# return
cord = _COORD()
cord.X = x
cord.Y = y
if not SetConsoleCursorPosition(OutHandle, cord):
raise ctypes.WinError(ctypes.GetLastError())
self.__write("\x1b[{};{}H".format(y + 1, x + 1))

def move_cursor(self, x: int, y: int) -> None:
trace(f'move_cursor {x} {y}')
Expand Down Expand Up @@ -520,10 +509,10 @@ def __read_input(self) -> INPUT_RECORD | None:
read = DWORD()
if not ReadConsoleInput(InHandle, rec, 1, read):
raise ctypes.WinError(ctypes.GetLastError())

if read.value == 0:
return None

return rec

def get_event(self, block: bool = True) -> Event | None:
Expand All @@ -536,14 +525,14 @@ def get_event(self, block: bool = True) -> Event | None:
if block:
continue
return None

if rec.EventType == WINDOW_BUFFER_SIZE_EVENT:
old_height, old_width = self.height, self.width
self.height, self.width = self.getheightwidth()
delta = self.width - old_width
# Windows will fix up the wrapping for us, but we
# need to sync __posxy with those changes.

new_x, new_y = self.__posxy
y = self.__posxy[1]
trace("Cur screen {}", self.screen)
Expand Down Expand Up @@ -581,9 +570,9 @@ def get_event(self, block: bool = True) -> Event | None:
if block:
continue
return None

key = rec.Event.KeyEvent.uChar.UnicodeChar

if rec.Event.KeyEvent.uChar.UnicodeChar == '\r':
# Make enter make unix-like
return Event(evt="key", data="\n", raw="\n")
Expand All @@ -597,7 +586,7 @@ def get_event(self, block: bool = True) -> Event | None:
# Handle special keys like arrow keys and translate them into the appropriate command
code = VK_MAP.get(rec.Event.KeyEvent.wVirtualKeyCode)
if code:
return Event(evt="key", data=code, raw=rec.Event.KeyEvent.uChar.UnicodeChar)
return Event(evt="key", data=code, raw=rec.Event.KeyEvent.uChar.UnicodeChar)
if block:
continue

Expand All @@ -609,26 +598,19 @@ def push_char(self, char: int | bytes) -> None:
"""
Push a character to the console event queue.
"""
trace(f'put_char {char}')
...

def beep(self) -> None: ...

def clear(self) -> None:
def clear(self, clear_scrollback=False) -> None:
"""Wipe the screen"""
info = CONSOLE_SCREEN_BUFFER_INFO()
if not GetConsoleScreenBufferInfo(OutHandle, info):
raise ctypes.WinError(ctypes.GetLastError())
size = info.dwSize.X * info.dwSize.Y
if not FillConsoleOutputCharacter(OutHandle, b' ', size, _COORD(), DWORD()):
raise ctypes.WinError(ctypes.GetLastError())
if not FillConsoleOutputAttribute(OutHandle, 0, size, _COORD(), DWORD()):
raise ctypes.WinError(ctypes.GetLastError())
y = info.srWindow.Bottom - info.srWindow.Top + 1
self.__move_absolute(0, 0)
self.__write("\x1b[2J")
if clear_scrollback:
self.__write("\x1b[3J")
self.__write("\x1b[H")
self.__posxy = 0, 0
self.screen = [""]


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."""
Expand All @@ -641,10 +623,10 @@ def finish(self) -> None:
def flushoutput(self) -> None:
"""Flush all output to the screen (assuming there's some
buffering going on somewhere).
All output on Windows is unbuffered so this is a nop"""
pass
...

def forgetinput(self) -> None:
"""Forget all pending, but not yet processed input."""
...
Expand Down

0 comments on commit 3ae4316

Please sign in to comment.