8000 [3.13] gh-123024: Correctly prepare/restore around help and show-hist… · python/cpython@0ddcb61 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0ddcb61

Browse files
miss-islingtonlysnikolaouemilyemorehousepablogsal
authored
[3.13] gh-123024: Correctly prepare/restore around help and show-history commands (GH-124485) (#129155)
gh-123024: Correctly prepare/restore around help and show-history commands (GH-124485) (cherry picked from commit 5a9afe2) Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com> Co-authored-by: Emily Morehouse <emily@cuttlesoft.com> Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
1 parent 3048dcd commit 0ddcb61

File tree

7 files changed

+77
-57
lines changed

7 files changed

+77
-57
lines changed

Lib/_pyrepl/commands.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,15 @@ def do(self) -> None:
459459
from site import gethistoryfile # type: ignore[attr-defined]
460460

461461
history = os.linesep.join(self.reader.history[:])
462-
with self.reader.suspend():
463-
pager = get_pager()
464-
pager(history, gethistoryfile())
462+
self.reader.console.restore()
463+
pager = get_pager()
464+
pager(history, gethistoryfile())
465+
self.reader.console.prepare()
466+
467+
# We need to copy over the state so that it's consistent between
468+
# console and reader, and console does not overwrite/append stuff
469+
self.reader.console.screen = self.reader.screen.copy()
470+
self.reader.console.posxy = self.reader.cxy
465471

466472

467473
class paste_mode(Command):

Lib/_pyrepl/console.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Event:
4545

4646
@dataclass
4747
class Console(ABC):
48+
posxy: tuple[int, int]
4849
screen: list[str] = field(default_factory=list)
4950
height: int = 25
5051
width: int = 80

Lib/_pyrepl/historical_reader.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,17 @@ def get_item(self, i: int) -> str:
290290

291291
@contextmanager
292292
def suspend(self) -> SimpleContextManager:
293-
with super().suspend():
294-
try:
295-
old_history = self.history[:]
296-
del self.history[:]
297-
yield
298-
finally:
299-
self.history[:] = old_history
293+
with super().suspend(), self.suspend_history():
294+
yield
295+
296+
@contextmanager
297+
def suspend_history(self) -> SimpleContextManager:
298+
try:
299+
old_history = self.history[:]
300+
del self.history[:]
301+
yield
302+
finally:
303+
self.history[:] = old_history
300304

301305
def prepare(self) -> None:
302306
super().prepare()

Lib/_pyrepl/simple_interact.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def _clear_screen():
7777
"exit": _sitebuiltins.Quitter('exit', ''),
7878
"quit": _sitebuiltins.Quitter('quit' ,''),
7979
"copyright": _sitebuiltins._Printer('copyright', sys.copyright),
80-
"help": "help",
80+
"help": _sitebuiltins._Helper(),
8181
"clear": _clear_screen,
8282
"\x1a": _sitebuiltins.Quitter('\x1a', ''),
8383
}
@@ -124,18 +124,10 @@ def maybe_run_command(statement: str) -> bool:
124124
reader.history.pop() # skip internal commands in history
125125
command = REPL_COMMANDS[statement]
126126
if callable(command):
127-
command()
127+
# Make sure that history does not change because of commands
128+
with reader.suspend_history():
129+
command()
128130
return True
129-
130-
if isinstance(command, str):
131-
# Internal readline commands require a prepared reader like
132-
# inside multiline_input.
133-
reader.prepare()
134-
reader.refresh()
135-
reader.do_cmd((command, [statement]))
136-
reader.restore()
137-
return True
138-
139131
return False
140132

141133
while 1:

Lib/_pyrepl/unix_console.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def refresh(self, screen, c_xy):
240240
self.__hide_cursor()
241241
self.__move(0, len(self.screen) - 1)
242242
self.__write("\n")
243-
self.__posxy = 0, len(self.screen)
243+
self.posxy = 0, len(self.screen)
244244
self.screen.append("")
245245
else:
246246
while len(self.screen) < len(screen):
@@ -250,7 +250,7 @@ def refresh(self, screen, c_xy):
250250
self.__gone_tall = 1
251251
self.__move = self.__move_tall
252252

253-
px, py = self.__posxy
253+
px, py = self.posxy
254254
old_offset = offset = self.__offset
255255
height = self.height
256256

@@ -271,15 +271,15 @@ def refresh(self, screen, c_xy):
271271
if old_offset > offset and self._ri:
272272
self.__hide_cursor()
273273
self.__write_code(self._cup, 0, 0)
274-
self.__posxy = 0, old_offset
274+
self.posxy = 0, old_offset
275275
for i in range(old_offset - offset):
276276
self.__write_code(self._ri)
277277
oldscr.pop(-1)
278278
oldscr.insert(0, "")
279279
elif old_offset < offset and self._ind:
280280
self.__hide_cursor()
281281
self.__write_code(self._cup, self.height - 1, 0)
282-
self.__posxy = 0, old_offset + self.height - 1
282+
self.posxy = 0, old_offset + self.height - 1
283283
for i in range(offset - old_offset):
284284
self.__write_code(self._ind)
285285
oldscr.pop(0)
@@ -299,7 +299,7 @@ def refresh(self, screen, c_xy):
299299
while y < len(oldscr):
300300
self.__hide_cursor()
301301
self.__move(0, y)
302-
self.__posxy = 0, y
302+
self.posxy = 0, y
303303
self.__write_code(self._el)
304304
y += 1
305305

@@ -321,7 +321,7 @@ def move_cursor(self, x, y):
321321
self.event_queue.insert(Event("scroll", None))
322322
else:
323323
self.__move(x, y)
324-
self.__posxy = x, y
324+
self.posxy = x, y
325325
self.flushoutput()
326326

327327
def prepare(self):
@@ -350,7 +350,7 @@ def prepare(self):
350350

351351
self.__buffer = []
352352

353-
self.__posxy = 0, 0
353+
self.posxy = 0, 0
354354
self.__gone_tall = 0
355355
self.__move = self.__move_short
356356
self.__offset = 0
@@ -559,7 +559,7 @@ def clear(self):
559559
self.__write_code(self._clear)
560560
self.__gone_tall = 1
561561
self.__move = self.__move_tall
562-
self.__posxy = 0, 0
562+
self.posxy = 0, 0
563563
self.screen = []
564564

565565
@property
@@ -644,8 +644,8 @@ def __write_changed_line(self, y, oldline, newline, px_coord):
644644
# if we need to insert a single character right after the first detected change
645645
if oldline[x_pos:] == newline[x_pos + 1 :] and self.ich1:
646646
if (
647-
y == self.__posxy[1]
648-
and x_coord > self.__posxy[0]
647+
y == self.posxy[1]
648+
and x_coord > self.posxy[0]
649649
and oldline[px_pos:x_pos] == newline[px_pos + 1 : x_pos + 1]
650650
):
651651
x_pos = px_pos
@@ -654,7 +654,7 @@ def __write_changed_line(self, y, oldline, newline, px_coord):
654654
self.__move(x_coord, y)
655655
self.__write_code(self.ich1)
656656
self.__write(newline[x_pos])
657-
self.__posxy = x_coord + character_width, y
657+
self.posxy = x_coord + character_width, y
658658

659659
# if it's a single character change in the middle of the line
660660
elif (
@@ -665,7 +665,7 @@ def __write_changed_line(self, y, oldline, newline, px_coord):
665665
character_width = wlen(newline[x_pos])
666666
self.__move(x_coord, y)
667667
self.__write(newline[x_pos])
668-
self.__posxy = x_coord + character_width, y
668+
self.posxy = x_coord + character_width, y
669669

670670
# if this is the last character to fit in the line and we edit in the middle of the line
671671
elif (
@@ -677,22 +677,22 @@ def __write_changed_line(self, y, oldline, newline, px_coord):
677677
):
678678
self.__hide_cursor()
679679
self.__move(self.width - 2, y)
680-
self.__posxy = self.width - 2, y
680+
self.posxy = self.width - 2, y
681681
self.__write_code(self.dch1)
682682

683683
character_width = wlen(newline[x_pos])
684684
self.__move(x_coord, y)
685685
self.__write_code(self.ich1)
686686
self.__write(newline[x_pos])
687-
self.__posxy = character_width + 1, y
687+
self.posxy = character_width + 1, y
688688

689689
else:
690690
self.__hide_cursor()
691691
self.__move(x_coord, y)
692692
if wlen(oldline) > wlen(newline):
693693
self.__write_code(self._el)
694694
self.__write(newline[x_pos:])
695-
self.__posxy = wlen(newline), y
695+
self.posxy = wlen(newline), y
696696

697697
if "\x1b" in newline:
698698
# ANSI escape characters are present, so we can't assume
@@ -711,32 +711,36 @@ def __maybe_write_code(self, fmt, *args):
711711
self.__write_code(fmt, *args)
712712

713713
def __move_y_cuu1_cud1(self, y):
714-
dy = y - self.__posxy[1]
714+
assert self._cud1 is not None
715+
assert self._cuu1 is not None
716+
dy = y - self.posxy[1]
715717
if dy > 0:
716718
self.__write_code(dy * self._cud1)
717719
elif dy < 0:
718720
self.__write_code((-dy) * self._cuu1)
719721

720722
def __move_y_cuu_cud(self, y):
721-
dy = y - self.__posxy[1]
723+
dy = y - self.posxy[1]
722724
if dy > 0:
723725
self.__write_code(self._cud, dy)
724726
elif dy < 0:
725727
self.__write_code(self._cuu, -dy)
726728

727729
def __move_x_hpa(self, x: int) -> None:
728-
if x != self.__posxy[0]:
730+
if x != self.posxy[0]:
729731
self.__write_code(self._hpa, x)
730732

731733
def __move_x_cub1_cuf1(self, x: int) -> None:
732-
dx = x - self.__posxy[0]
734+
assert self._cuf1 is not None
735+
assert self._cub1 is not None
736+
dx = x - self.posxy[0]
733737
if dx > 0:
734738
self.__write_code(self._cuf1 * dx)
735739
elif dx < 0:
736740
self.__write_code(self._cub1 * (-dx))
737741

738742
def __move_x_cub_cuf(self, x: int) -> None:
739-
dx = x - self.__posxy[0]
743+
dx = x - self.posxy[0]
740744
if dx > 0:
741745
self.__write_code(self._cuf, dx)
742746
elif dx < 0:
@@ -766,12 +770,12 @@ def __show_cursor(self):
766770

767771
def repaint(self):
768772
if not self.__gone_tall:
769-
self.__posxy = 0, self.__posxy[1]
773+
self.posxy = 0, self.posxy[1]
770774
self.__write("\r")
771775
ns = len(self.screen) * ["\000" * self.width]
772776
self.screen = ns
773777
else:
774-
self.__posxy = 0, self.__offset
778+
self.posxy = 0, self.__offset
775779
self.__move(0, self.__offset)
776780
ns = self.height * ["\000" * self.width]
777781
self.screen = ns

Lib/_pyrepl/windows_console.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,10 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
148148
self._hide_cursor()
149149
self._move_relative(0, len(self.screen) - 1)
150150
self.__write("\n")
151-
self.__posxy = 0, len(self.screen)
151+
self.posxy = 0, len(self.screen)
152152
self.screen.append("")
153153

154-
px, py = self.__posxy
154+
px, py = self.posxy
155155
old_offset = offset = self.__offset
156156
height = self.height
157157

@@ -167,7 +167,7 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
167167
# portion of the window. We need to scroll the visible portion and the
168168
# entire history
169169
self._scroll(scroll_lines, self._getscrollbacksize())
170-
self.__posxy = self.__posxy[0], self.__posxy[1] + scroll_lines
170+
self.posxy = self.posxy[0], self.posxy[1] + scroll_lines
171171
self.__offset += scroll_lines
172172

173173
for i in range(scroll_lines):
@@ -193,7 +193,7 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
193193
y = len(newscr)
194194
while y < len(oldscr):
195195
self._move_relative(0, y)
196-
self.__posxy = 0, y
196+
self.posxy = 0, y
197197
self._erase_to_end()
198198
y += 1
199199

@@ -250,11 +250,11 @@ def __write_changed_line(
250250
if wlen(newline) == self.width:
251251
# If we wrapped we want to start at the next line
252252
self._move_relative(0, y + 1)
253-
self.__posxy = 0, y + 1
253+
self.posxy = 0, y + 1
254254
else:
255-
self.__posxy = wlen(newline), y
255+
self.posxy = wlen(newline), y
256256

257-
if "\x1b" in newline or y != se 10000 lf.__posxy[1] or '\x1a' in newline:
257+
if "\x1b" in newline or y != self.posxy[1] or '\x1a' in newline:
258258
# ANSI escape characters are present, so we can't assume
259259
# anything about the position of the cursor. Moving the cursor
260260
# to the left margin should work to get to a known position.
@@ -316,17 +316,17 @@ def prepare(self) -> None:
316316
self.screen = []
317317
self.height, self.width = self.getheightwidth()
318318

319-
self.__posxy = 0, 0
319+
self.posxy = 0, 0
320320
self.__gone_tall = 0
321321
self.__offset = 0
322322

323323
def restore(self) -> None:
324324
pass
325325

326326
def _move_relative(self, x: int, y: int) -> None:
327-
"""Moves relative to the current __posxy"""
328-
dx = x - self.__posxy[0]
329-
dy = y - self.__posxy[1]
327+
"""Moves relative to the current posxy"""
328+
dx = x - self.posxy[0]
329+
dy = y - self.posxy[1]
330330
if dx < 0:
331331
self.__write(MOVE_LEFT.format(-dx))
332332
elif dx > 0:
@@ -345,7 +345,7 @@ def move_cursor(self, x: int, y: int) -> None:
345345
self.event_queue.insert(0, Event("scroll", ""))
346346
else:
347347
self._move_relative(x, y)
348-
self.__posxy = x, y
348+
self.posxy = x, y
349349

350350
def set_cursor_vis(self, visible: bool) -> None:
351351
if visible:
@@ -445,7 +445,7 @@ def beep(self) -> None:
445445
def clear(self) -> None:
446446
"""Wipe the screen"""
447447
self.__write(CLEAR)
448-
self.__posxy = 0, 0
448+
self.posxy = 0, 0
449449
self.screen = [""]
450450

451451
def finish(self) -> None:

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,3 +1344,16 @@ def test_readline_history_file(self):
13441344
def test_keyboard_interrupt_after_isearch(self):
13451345
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
13461346
self.assertEqual(exit_code, 0)
1347+
1348+
def test_prompt_after_help(self):
1349+
output, exit_code = self.run_repl(["help", "q", "exit"])
1350+
1351+
# Regex pattern to remove ANSI escape sequences
1352+
ansi_escape = re.compile(r"(\x1B(=|>|(\[)[0-?]*[ -\/]*[@-~]))")
1353+
cleaned_output = ansi_escape.sub("", output)
1354+
self.assertEqual(exit_code, 0)
1355+
1356+
# Ensure that we don't see multiple prompts after exiting `help`
1357+
# Extra stuff (newline and `exit` rewrites) are necessary
1358+
# because of how run_repl works.
1359+
self.assertNotIn(">>> \n>>> >>>", cleaned_output)

0 commit comments

Comments
 (0)
0