Skip to content

Commit 0145391

Browse files
committed
pythongh-132267: fix unsynchronized cursor position and buffer mismatch after resize
1 parent 76cd0c4 commit 0145391

File tree

3 files changed

+87
-46
lines changed

3 files changed

+87
-46
lines changed

Lib/_pyrepl/reader.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,10 @@ def calc_screen(self) -> list[str]:
344344
pos -= line_len + 1
345345
prompt, prompt_len = self.process_prompt(prompt)
346346
chars, char_widths = disp_str(line)
347-
wrapcount = (sum(char_widths) + prompt_len) // self.console.width
347+
wrapcount = (sum(char_widths) + prompt_len) // (self.console.width - 1) # 1 for line continuations
348+
if (sum(char_widths) + prompt_len) % (self.console.width - 1) == 0:
349+
wrapcount -= 1
350+
348351
trace("wrapcount = {wrapcount}", wrapcount=wrapcount)
349352
if wrapcount == 0 or not char_widths:
350353
offset += line_len + 1 # Takes all of the line plus the newline

Lib/_pyrepl/unix_console.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from .fancy_termios import tcgetattr, tcsetattr
3838
from .trace import trace
3939
from .unix_eventqueue import EventQueue
40-
from .utils import wlen
40+
from .utils import wlen, ANSI_ESCAPE_SEQUENCE
4141

4242

4343
TYPE_CHECKING = False
@@ -233,7 +233,6 @@ def refresh(self, screen, c_xy):
233233
Parameters:
234234
- screen (list): List of strings representing the screen contents.
235235
- c_xy (tuple): Cursor position (x, y) on the screen.
236-
- repaint (bool): If True, overwrite the old screen and not reuse.
237236
"""
238237
cx, cy = c_xy
239238
if not self.__gone_tall:
@@ -338,47 +337,64 @@ def sync_screen(self):
338337
old_height, old_width = self.height, self.width
339338
new_height, new_width = self.getheightwidth()
340339

341-
groups = []
340+
vlines = []
342341
x, y = 0, 0
343342
new_line = True
344343
for i, line in enumerate(self.screen):
345344
l = wlen(line)
346345
if i == py:
347346
if new_line:
348-
y = sum(wlen(g) // new_width for g in groups) + len(groups) - 1
347+
y = sum(wlen(g) // new_width for g in vlines) + len(vlines)
349348
x = px
350349
else:
351-
y = sum(wlen(g) // new_width for g in groups[:-1]) + len(groups) - 1
352-
x = px + wlen(groups[-1])
350+
y = sum(wlen(g) // new_width for g in vlines[:-1]) + len(vlines) - 1
351+
x = px + wlen(vlines[-1])
353352
if x >= new_width:
354353
y += x // new_width
355354
x %= new_width
356355

357356
if new_line:
358-
groups.append(line)
357+
vlines.append(line)
359358
new_line = False
360359
else:
361-
groups[-1] += line
360+
vlines[-1] += line
362361
if l != old_width:
363362
new_line = True
364363

365364
new_screen = []
366-
for group in groups:
367-
l = 0
368-
line = ""
369-
for c in group:
370-
cw = wlen(c)
371-
if l + cw > new_width:
372-
new_screen.append(line)
373-
line = c
374-
l = cw
375-
else:
376-
line += c
377-
l += cw
378-
if line:
379-
new_screen.append(line)
365+
for vline in vlines:
366+
parts = []
367+
last_end = 0
368+
for match in ANSI_ESCAPE_SEQUENCE.finditer(vline):
369+
if match.start() > last_end:
370+
parts.append((vline[last_end:match.start()], False))
371+
parts.append((match.group(), True))
372+
last_end = match.end()
373+
374+
if last_end < len(vline):
375+
parts.append((vline[last_end:], False))
376+
377+
result = ""
378+
length = 0
379+
for part, is_escape in parts:
380+
if is_escape:
381+
result += part
382+
continue
383+
for char in part:
384+
lc = wlen(char)
385+
if lc + length > new_width:
386+
# save the current line
387+
new_screen.append(result)
388+
result = ""
389+
length = 0
390+
result += char
391+
length += lc
392+
393+
if result:
394+
new_screen.append(result)
380395

381396
self.posxy = x, y
397+
self.screen = new_screen
382398
self.height, self.width = new_height, new_width
383399

384400
def prepare(self):
@@ -745,13 +761,18 @@ def __write_changed_line(self, y, oldline, newline, px_coord):
745761
self.__write(newline[x_pos])
746762
self.posxy = character_width + 1, y
747763

764+
if newline[-1] != oldline[-1]:
765+
self.__move(self.width, y)
766+
self.__write(newline[-1])
767+
self.posxy = self.width - 1, y
768+
748769
else:
749770
self.__hide_cursor()
750771
self.__move(x_coord, y)
751772
if wlen(oldline) > wlen(newline):
752773
self.__write_code(self._el)
753774
self.__write(newline[x_pos:])
754-
self.posxy = wlen(newline), y
775+
self.posxy = min(wlen(newline), self.width - 1), y
755776

756777
if "\x1b" in newline:
757778
# ANSI escape characters are present, so we can't assume

Lib/_pyrepl/windows_console.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from ctypes import Structure, POINTER, Union
4141
from .console import Event, Console
4242
from .trace import trace
43-
from .utils import wlen
43+
from .utils import wlen, ANSI_ESCAPE_SEQUENCE
4444
from .windows_eventqueue import EventQueue
4545

4646
try:
@@ -277,7 +277,7 @@ def __write_changed_line(
277277
self._move_relative(0, y + 1)
278278
self.posxy = 0, y + 1
279279
else:
280-
self.posxy = wlen(newline), y
280+
self.posxy = max(wlen(newline), self.width - 1), y
281281

282282
if "\x1b" in newline or y != self.posxy[1] or '\x1a' in newline:
283283
# ANSI escape characters are present, so we can't assume
@@ -398,47 +398,64 @@ def sync_screen(self):
398398
old_height, old_width = self.height, self.width
399399
new_height, new_width = self.getheightwidth()
400400

401-
groups = []
401+
vlines = []
402402
x, y = 0, 0
403403
new_line = True
404404
for i, line in enumerate(self.screen):
405405
l = wlen(line)
406406
if i == py:
407407
if new_line:
408-
y = sum(wlen(g) // new_width for g in groups) + len(groups) - 1
408+
y = sum(wlen(g) // new_width for g in vlines) + len(vlines)
409409
x = px
410410
else:
411-
y = sum(wlen(g) // new_width for g in groups[:-1]) + len(groups) - 1
412-
x = px + wlen(groups[-1])
411+
y = sum(wlen(g) // new_width for g in vlines[:-1]) + len(vlines) - 1
412+
x = px + wlen(vlines[-1])
413413
if x >= new_width:
414414
y += x // new_width
415415
x %= new_width
416416

417417
if new_line:
418-
groups.append(line)
418+
vlines.append(line)
419419
new_line = False
420420
else:
421-
groups[-1] += line
421+
vlines[-1] += line
422422
if l != old_width:
423423
new_line = True
424424

425425
new_screen = []
426-
for group in groups:
427-
l = 0
428-
line = ""
429-
for c in group:
430-
cw = wlen(c)
431-
if l + cw > new_width:
432-
new_screen.append(line)
433-
line = c
434-
l = cw
435-
else:
436-
line += c
437-
l += cw
438-
if line:
439-
new_screen.append(line)
426+
for vline in vlines:
427+
parts = []
428+
last_end = 0
429+
for match in ANSI_ESCAPE_SEQUENCE.finditer(vline):
430+
if match.start() > last_end:
431+
parts.append((vline[last_end:match.start()], False))
432+
parts.append((match.group(), True))
433+
last_end = match.end()
434+
435+
if last_end < len(vline):
436+
parts.append((vline[last_end:], False))
437+
438+
result = ""
439+
length = 0
440+
for part, is_escape in parts:
441+
if is_escape:
442+
result += part
443+
continue
444+
for char in part:
445+
lc = wlen(char)
446+
if lc + length > new_width:
447+
# save the current line
448+
new_screen.append(result)
449+
result = ""
450+
length = 0
451+
result += char
452+
length += lc
453+
454+
if result:
455+
new_screen.append(result)
440456

441457
self.posxy = x, y
458+
self.screen = new_screen
442459
self.height, self.width = new_height, new_width
443460

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

0 commit comments

Comments
 (0)