Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
1061d22
Windows repl support
DinoV May 17, 2024
fa0f538
Arrow key support
DinoV May 18, 2024
6f35100
Make backspace clear char
DinoV May 18, 2024
7772787
Fix missing newline after input
DinoV May 18, 2024
aced5ae
Make insert work
DinoV May 18, 2024
fa3815f
Fix delete in middle
DinoV May 18, 2024
b30b105
Fix crash on invalid command key
DinoV May 18, 2024
df262c4
More fixes
DinoV May 20, 2024
6e57316
Colorize
DinoV May 20, 2024
18ecc2e
Use constants
DinoV May 20, 2024
60525eb
Use UnicodeChar
DinoV May 21, 2024
af79a17
Simplify
DinoV May 21, 2024
5f99256
Fix scrolling on input which is longer than screen/scrollback
DinoV May 21, 2024
bdff535
fix pager typo and refactor some unused branches (#41)
tonybaloney May 20, 2024
88d64f5
Implement screen clear for Windows (#42)
tonybaloney May 20, 2024
8a74306
Fix word wrap not being enabled in Windows Terminal
DinoV May 21, 2024
38e9c58
Fix culmitive errors in wrapping as lines proceed
DinoV May 21, 2024
9cddace
Fix issues with inputs longer than a single line
DinoV May 22, 2024
fc4efee
Resize WIP
DinoV May 23, 2024
564e6e1
set compat based on Windows build version
tonybaloney May 24, 2024
3ae4316
use escape sequence for clearing screen and setting cursor position
tonybaloney May 24, 2024
5bb02a2
Remove unused imports
tonybaloney May 24, 2024
500761e
Windows repl support
DinoV May 17, 2024
98f16b9
Arrow key support
DinoV May 18, 2024
911a76a
Make backspace clear char
DinoV May 18, 2024
77f1c42
Fix missing newline after input
DinoV May 18, 2024
243817d
Make insert work
DinoV May 18, 2024
25f51b4
Fix delete in middle
DinoV May 18, 2024
5242239
Fix crash on invalid command key
DinoV May 18, 2024
febe424
More fixes
DinoV May 20, 2024
756ac47
Colorize
DinoV May 20, 2024
50fd4c9
Use constants
DinoV May 20, 2024
ff46d66
Use UnicodeChar
DinoV May 21, 2024
fbb9f84
Simplify
DinoV May 21, 2024
683f5f6
Fix scrolling on input which is longer than screen/scrollback
DinoV May 21, 2024
2c524b8
fix pager typo and refactor some unused branches (#41)
tonybaloney May 20, 2024
44ce57b
Implement screen clear for Windows (#42)
tonybaloney May 20, 2024
5ecc8cd
Fix word wrap not being enabled in Windows Terminal
DinoV May 21, 2024
5bb90c7
Fix issues with inputs longer than a single line
DinoV May 22, 2024
35557dd
Resize WIP
DinoV May 23, 2024
e04699f
Use vt100 scrolling to avoid race conditions on resize
DinoV May 24, 2024
6230400
More win api cleanup
DinoV May 24, 2024
70a9a31
Code cleanup
DinoV May 24, 2024
4adfe77
Reformat
DinoV May 24, 2024
9c00bd1
Annotations
DinoV May 24, 2024
dc7ec86
Update news
DinoV May 24, 2024
79a3fb9
Update history
DinoV May 24, 2024
3e4fe55
Initial test cases
DinoV May 25, 2024
38800e7
📜🤖 Added by blurb_it.
blurb-it[bot] May 25, 2024
b3f7092
Fix mypy and formatting issues
DinoV May 25, 2024
a589321
More mypy fixes
DinoV May 25, 2024
9a34d8a
More MyPy
DinoV May 25, 2024
f8a9176
More MyPy fixes
DinoV May 25, 2024
ce77202
More mypy fixes, name changes in WindowsConsole
DinoV May 26, 2024
6d8e7f5
Fix up ignores
DinoV May 26, 2024
bbbb296
Avoid kernel32 on non-Windows platforms
DinoV May 26, 2024
d064124
Formatting
DinoV May 26, 2024
f244a0e
Merge remote-tracking branch 'dinov/winrepl' into winrepl_control
tonybaloney May 26, 2024
3d526ba
roll back other merge
tonybaloney May 26, 2024
b1f5688
Merge pull request #1 from tonybaloney/winrepl_control
DinoV May 29, 2024
1146dbb
Catch more specific comment, avoid typing, fix comment
DinoV May 29, 2024
972e7ec
Ignore type error on return of Any
DinoV May 29, 2024
1d300e4
Merge branch 'main' of https://github.com/python/cpython into winrepl
DinoV May 29, 2024
d783dae
Don't import traceback on exception
DinoV May 29, 2024
d1289af
Remove one more TYPE_CHECKING
DinoV May 29, 2024
db191a2
Update Lib/test/test_pyrepl/test_windows_console.py
ambv May 30, 2024
fba84aa
Use _WindowsConsoleIO for output
DinoV May 31, 2024
42e3e8e
Merge branch 'winrepl' of github.com:DinoV/cpython into winrepl
DinoV May 31, 2024
63d854e
Remove get_abs_positon
DinoV May 31, 2024
513a148
Fix tests on windows
DinoV May 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Resize WIP
  • Loading branch information
DinoV committed May 23, 2024
commit fc4efeea2e673e79b8b1b0e195da5393240ea528
114 changes: 86 additions & 28 deletions Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ctypes import Structure, POINTER, Union
from ctypes import windll
from typing import TYPE_CHECKING
from .utils import wlen

if TYPE_CHECKING:
from typing import IO
Expand Down Expand Up @@ -118,11 +119,17 @@ class KeyEvent(ctypes.Structure):
("dwControlKeyState", DWORD),
]

class WindowsBufferSizeEvent(ctypes.Structure):
_fields_ = [
('dwSize', _COORD)
]


class ConsoleEvent(ctypes.Union):
_fields_ = [
("KeyEvent", KeyEvent),
# ("MouseEvent", ),
# ("WindowsBufferSizeEvent", ),
("WindowsBufferSizeEvent", WindowsBufferSizeEvent),
# ("MenuEvent", )
# ("FocusEvent", )
]
Expand Down Expand Up @@ -187,9 +194,6 @@ class INPUT_RECORD(Structure):
class _error(Exception):
pass

def wlen(s: str) -> int:
return len(s)

class WindowsConsole(Console):
def __init__(
self,
Expand Down Expand Up @@ -223,7 +227,7 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
"""
cx, cy = c_xy

trace('!!Refresh {}', screen)
trace('!!Refresh {} {} {}', c_xy, self.__offset, screen)
while len(self.screen) < min(len(screen), self.height):
self.__hide_cursor()
self.__move_relative(0, len(self.screen) - 1)
Expand Down Expand Up @@ -275,6 +279,7 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
trace('new offset {} {}', offset, px)
self.__offset = offset

self.__hide_cursor()
for (
y,
oldline,
Expand All @@ -285,7 +290,6 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:

y = len(newscr)
while y < len(oldscr):
self.__hide_cursor()
self.__move_relative(0, y)
self.__posxy = 0, y
self.erase_to_end()
Expand Down Expand Up @@ -389,20 +393,34 @@ def __write_changed_line(self, y: int, oldline: str, newline: str, px_coord):
raise ctypes.WinError(ctypes.GetLastError())

self.__move_relative(x_coord, y)
pos = self.__posxy
coord = self.screen_xy

self.__write(newline[x_pos])
if x_coord + character_width == self.width:
self.move_next_line(y)

# If we wrapped we need to get back to a known good position,
# and the starting position was known good.
self.__move_absolute(*coord)
self.__posxy = pos
else:
self.__posxy = x_coord + character_width, y
else:
trace("Rewrite all {!r} {} {} y={} {} posxy={}", newline, x_coord, len(oldline), y, wlen(newline), self.__posxy)
self.__hide_cursor()
self.__move_relative(x_coord, y)
if wlen(oldline) > wlen(newline):
self.erase_to_end()
pos = self.__posxy
coord = self.screen_xy
#if wlen(oldline) > wlen(newline):
self.erase_to_end()
trace(f"Writing {newline[x_pos:]}")
self.__write(newline[x_pos:])
if len(newline[x_pos:]) == self.width:
self.move_next_line(y)

if wlen(newline[x_pos:]) == self.width:
# If we wrapped we need to get back to a known good position,
# and the starting position was known good.
self.__move_absolute(*coord)
self.__posxy = pos
else:
self.__posxy = wlen(newline), y

Expand All @@ -411,16 +429,11 @@ def __write_changed_line(self, y: int, oldline: str, newline: str, px_coord):
# ANSI escape characters are present, so we can't assume
# anything about the position of the cursor. Moving the cursor
# to the left margin should work to get to a known position.
_, cur_y = self.get_abs_position(0, y)
self.__move_absolute(0, cur_y)
#_, cur_y = self.get_abs_position(0, y)
#self.__move_absolute(0, cur_y)
self.__move_absolute(0, self.screen_xy[1])
self.__posxy = 0, y

def move_next_line(self, y: int):
self.__posxy = 0, y
_, cur_y = self.get_abs_position(0, y)
self.__move_absolute(0, cur_y)
self.__posxy = 0, y

def erase_to_end(self):
info = CONSOLE_SCREEN_BUFFER_INFO()
if not GetConsoleScreenBufferInfo(OutHandle, info):
Expand Down Expand Up @@ -457,7 +470,6 @@ def __move_relative(self, x, y):
"""Moves relative to the current __posxy"""
trace('move relative {} {} {} {}', x, y, self.__posxy, self.screen_xy)
cur_x, cur_y = self.get_abs_position(x, y)
trace('move is {} {}', cur_x, cur_y)
if cur_y < 0:
# We're scrolling above the current buffer, we need to refresh
self.__posxy = self.__posxy[0], self.__posxy[1] + cur_y
Expand All @@ -467,6 +479,7 @@ def __move_relative(self, x, y):

def __move_absolute(self, x, y):
"""Moves to an absolute location in the screen buffer"""
trace(f"move absolute {x} {y}")
if y < 0:
trace(f"Negative offset: {self.__posxy} {self.screen_xy}")
if x < 0:
Expand All @@ -487,7 +500,6 @@ def move_cursor(self, x: int, y: int) -> None:
self.__move_relative(x, y)
self.__posxy = x, y


def set_cursor_vis(self, visible: bool) -> None:
if visible:
self.__show_cursor()
Expand All @@ -503,21 +515,67 @@ def getheightwidth(self) -> tuple[int, int]:
return (info.srWindow.Bottom - info.srWindow.Top + 1,
info.srWindow.Right - info.srWindow.Left + 1)

def __read_input(self) -> INPUT_RECORD | None:
rec = INPUT_RECORD()
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:
"""Return an Event instance. Returns None if |block| is false
and there is no event pending, otherwise waits for the
completion of an event."""
while True:
rec = INPUT_RECORD()
read = DWORD()
if not ReadConsoleInput(InHandle, rec, 1, read):
raise ctypes.WinError(ctypes.GetLastError())

if read.value == 0:
rec = self.__read_input()
if rec is 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)
#last_len = -1
new_lines = 0
while y >= 0:
line = self.screen[y]
line_len = wlen(line)
trace(f"XX {wlen(line)} {self.width} {old_width} {wlen(line) <= self.width} {old_width > wlen(line)} {line}")
if (line_len >= self.width and line_len < old_width) and line[-1] != "\\":
# This line is turning into 2 lines
trace("Lines wrap")
new_y += 1
new_lines += 1
#elif line_len >= old_width and line_len < self.width and line[-1] == "\\" and last_len == 1:
# # This line is turning into 1 line
# trace("Lines join")
# #new_y -= 1
#last_len = line_len
y -= 1

trace(f"RESIZE {self.screen_xy} {self.__posxy} ({new_x}, {new_y}) ({self.width}, {self.height})")
# Force redraw of current input, the wrapping can corrupt our state with \
self.screen = [' ' * self.width] * (len(self.screen) + new_lines)

# We could have "unwrapped" things which weren't really wrapped, shifting our x position,
# get back to something neutral.
self.__move_absolute(0, self.screen_xy[1])
self.__posxy = 0, new_y

return Event("resize", "")

if rec.EventType != KEY_EVENT or not rec.Event.KeyEvent.bKeyDown:
# Only process keys and keydown events
if block:
Expand Down Expand Up @@ -566,7 +624,7 @@ def clear(self) -> None:
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, y - info.dwSize.Y)
self.__move_absolute(0, 0)
self.__posxy = 0, 0
self.screen = [""]

Expand Down