Skip to content

Commit 4fc6961

Browse files
committed
fix: synchronize cursor position by fetch hardware info
1 parent a383dc6 commit 4fc6961

File tree

5 files changed

+44
-141
lines changed

5 files changed

+44
-141
lines changed

Lib/_pyrepl/console.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def restore(self) -> None: ...
8383
def move_cursor(self, x: int, y: int) -> None: ...
8484

8585
@abstractmethod
86-
def sync_screen(self) -> None: ...
86+
def sync_cursor(self) -> None: ...
87+
88+
@abstractmethod
89+
def sync_screen_size(self) -> None: ...
8790

8891
@abstractmethod
8992
def set_cursor_vis(self, visible: bool) -> None: ...

Lib/_pyrepl/reader.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,9 @@ def handle1(self, block: bool = True) -> bool:
716716
elif event.evt == "scroll":
717717
self.refresh()
718718
elif event.evt == "resize":
719+
self.console.sync_screen_size()
720+
self.console.sync_cursor()
721+
self.console.clear()
719722
self.refresh()
720723
else:
721724
translate = False

Lib/_pyrepl/unix_console.py

Lines changed: 21 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,9 @@ def refresh(self, screen, c_xy):
271271
self.posxy = 0, old_offset
272272
for i in range(old_offset - offset):
273273
self.__write_code(self._ri)
274-
oldscr.pop(-1)
275274
oldscr.insert(0, "")
275+
if len(oldscr) > height:
276+
oldscr.pop(-1)
276277
elif old_offset < offset and self._ind:
277278
self.__hide_cursor()
278279
self.__write_code(self._cup, self.height - 1, 0)
@@ -321,78 +322,28 @@ def move_cursor(self, x, y):
321322
self.posxy = x, y
322323
self.flushoutput()
323324

324-
def sync_screen(self):
325+
def sync_cursor(self):
325326
"""
326-
Synchronize self.posxy, self.screen, self.width and self.height.
327-
Assuming that the content of the screen doesn't change, only the width changes.
327+
Synchronize posxy after resizing.
328328
"""
329-
if not self.screen:
330-
self.posxy = 0, 0
331-
return
329+
os.write(self.output_fd, b"\x1b[6n")
330+
response = b""
331+
while True:
332+
ch = os.read(self.input_fd, 1)
333+
response += ch
334+
if ch == b'R':
335+
break
336+
m = re.match(rb"\x1b\[(\d+);(\d+)R", response)
337+
if m:
338+
row, col = map(int, m.groups())
339+
cur_x, cur_y = col - 1, row - 1
340+
self.posxy = cur_x, cur_y + self.__offset
332341

333-
px, py = self.posxy
334-
old_height, old_width = self.height, self.width
335-
new_height, new_width = self.getheightwidth()
336-
337-
vlines = []
338-
x, y = 0, 0
339-
new_line = True
340-
for i, line in enumerate(self.screen):
341-
l = wlen(line)
342-
if i == py:
343-
if new_line:
344-
y = sum(wlen(g) // new_width for g in vlines) + len(vlines)
345-
x = px
346-
else:
347-
y = sum(wlen(g) // new_width for g in vlines[:-1]) + len(vlines) - 1
348-
x = px + wlen(vlines[-1])
349-
if x >= new_width:
350-
y += x // new_width
351-
x %= new_width
352-
353-
if new_line:
354-
vlines.append(line)
355-
new_line = False
356-
else:
357-
vlines[-1] += line
358-
if l != old_width:
359-
new_line = True
360-
361-
new_screen = []
362-
for vline in vlines:
363-
parts = []
364-
last_end = 0
365-
for match in ANSI_ESCAPE_SEQUENCE.finditer(vline):
366-
if match.start() > last_end:
367-
parts.append((vline[last_end:match.start()], False))
368-
parts.append((match.group(), True))
369-
last_end = match.end()
370-
371-
if last_end < len(vline):
372-
parts.append((vline[last_end:], False))
373-
374-
result = ""
375-
length = 0
376-
for part, is_escape in parts:
377-
if is_escape:
378-
result += part
379-
continue
380-
for char in part:
381-
lc = wlen(char)
382-
if lc + length > new_width:
383-
# save the current line
384-
new_screen.append(result)
385-
result = ""
386-
length = 0
387-
result += char
388-
length += lc
389-
390-
if result:
391-
new_screen.append(result)
392-
393-
self.posxy = x, y
394-
self.screen = new_screen
395-
self.height, self.width = new_height, new_width
342+
def sync_screen_size(self):
343+
"""
344+
Synchronize screen size after resizing.
345+
"""
346+
self.height, self.width = self.getheightwidth()
396347

397348
def prepare(self):
398349
"""

Lib/_pyrepl/windows_console.py

Lines changed: 12 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -395,78 +395,21 @@ def move_cursor(self, x: int, y: int) -> None:
395395
self._move_relative(x, y)
396396
self.posxy = x, y
397397

398-
def sync_screen(self):
398+
def sync_cursor(self):
399399
"""
400-
Synchronize self.posxy, self.screen, self.width and self.height.
401-
Assuming that the content of the screen doesn't change, only the width changes.
400+
Synchronize posxy after resizing.
402401
"""
403-
if not self.screen:
404-
self.posxy = 0, 0
405-
return
402+
info = CONSOLE_SCREEN_BUFFER_INFO()
403+
if not GetConsoleScreenBufferInfo(OutHandle, info):
404+
raise WinError(GetLastError())
405+
cur_x, cur_y = info.dwCursorPosition.X, info.dwCursorPosition.Y
406+
self.posxy = cur_x, cur_y + self.__offset
406407

407-
px, py = self.posxy
408-
old_height, old_width = self.height, self.width
409-
new_height, new_width = self.getheightwidth()
410-
411-
vlines = []
412-
x, y = 0, 0
413-
new_line = True
414-
for i, line in enumerate(self.screen):
415-
l = wlen(line)
416-
if i == py:
417-
if new_line:
418-
y = sum(wlen(g) // new_width for g in vlines) + len(vlines)
419-
x = px
420-
else:
421-
y = sum(wlen(g) // new_width for g in vlines[:-1]) + len(vlines) - 1
422-
x = px + wlen(vlines[-1])
423-
if x >= new_width:
424-
y += x // new_width
425-
x %= new_width
426-
427-
if new_line:
428-
vlines.append(line)
429-
new_line = False
430-
else:
431-
vlines[-1] += line
432-
if l != old_width:
433-
new_line = True
434-
435-
new_screen = []
436-
for vline in vlines:
437-
parts = []
438-
last_end = 0
439-
for match in ANSI_ESCAPE_SEQUENCE.finditer(vline):
440-
if match.start() > last_end:
441-
parts.append((vline[last_end:match.start()], False))
442-
parts.append((match.group(), True))
443-
last_end = match.end()
444-
445-
if last_end < len(vline):
446-
parts.append((vline[last_end:], False))
447-
448-
result = ""
449-
length = 0
450-
for part, is_escape in parts:
451-
if is_escape:
452-
result += part
453-
continue
454-
for char in part:
455-
lc = wlen(char)
456-
if lc + length > new_width:
457-
# save the current line
458-
new_screen.append(result)
459-
result = ""
460-
length = 0
461-
result += char
462-
length += lc
463-
464-
if result:
465-
new_screen.append(result)
466-
467-
self.posxy = x, y
468-
self.screen = new_screen
469-
self.height, self.width = new_height, new_width
408+
def sync_screen_size(self):
409+
"""
410+
Synchronize screen size after resizing.
411+
"""
412+
self.height, self.width = self.getheightwidth()
470413

471414
def set_cursor_vis(self, visible: bool) -> None:
472415
if visible:

Lib/test/test_pyrepl/support.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ def restore(self) -> None:
143143
def move_cursor(self, x: int, y: int) -> None:
144144
pass
145145

146-
def sync_screen(self) -> None:
146+
def sync_cursor(self) -> None:
147+
pass
148+
149+
def sync_screen_size(self) -> None:
147150
pass
148151

149152
def set_cursor_vis(self, visible: bool) -> None:

0 commit comments

Comments
 (0)