mirror of
https://github.com/python/cpython.git
synced 2025-10-02 21:25:24 +00:00
gh-123024: Correctly prepare/restore around help and show-history commands (#124485)
Co-authored-by: Emily Morehouse <emily@cuttlesoft.com> Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
This commit is contained in:
parent
d147e5e52c
commit
5a9afe2362
7 changed files with 77 additions and 57 deletions
|
@ -459,9 +459,15 @@ class show_history(Command):
|
||||||
from site import gethistoryfile # type: ignore[attr-defined]
|
from site import gethistoryfile # type: ignore[attr-defined]
|
||||||
|
|
||||||
history = os.linesep.join(self.reader.history[:])
|
history = os.linesep.join(self.reader.history[:])
|
||||||
with self.reader.suspend():
|
self.reader.console.restore()
|
||||||
pager = get_pager()
|
pager = get_pager()
|
||||||
pager(history, gethistoryfile())
|
pager(history, gethistoryfile())
|
||||||
|
self.reader.console.prepare()
|
||||||
|
|
||||||
|
# We need to copy over the state so that it's consistent between
|
||||||
|
# console and reader, and console does not overwrite/append stuff
|
||||||
|
self.reader.console.screen = self.reader.screen.copy()
|
||||||
|
self.reader.console.posxy = self.reader.cxy
|
||||||
|
|
||||||
|
|
||||||
class paste_mode(Command):
|
class paste_mode(Command):
|
||||||
|
|
|
@ -45,6 +45,7 @@ class Event:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Console(ABC):
|
class Console(ABC):
|
||||||
|
posxy: tuple[int, int]
|
||||||
screen: list[str] = field(default_factory=list)
|
screen: list[str] = field(default_factory=list)
|
||||||
height: int = 25
|
height: int = 25
|
||||||
width: int = 80
|
width: int = 80
|
||||||
|
|
|
@ -290,13 +290,17 @@ class HistoricalReader(Reader):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def suspend(self) -> SimpleContextManager:
|
def suspend(self) -> SimpleContextManager:
|
||||||
with super().suspend():
|
with super().suspend(), self.suspend_history():
|
||||||
try:
|
yield
|
||||||
old_history = self.history[:]
|
|
||||||
del self.history[:]
|
@contextmanager
|
||||||
yield
|
def suspend_history(self) -> SimpleContextManager:
|
||||||
finally:
|
try:
|
||||||
self.history[:] = old_history
|
old_history = self.history[:]
|
||||||
|
del self.history[:]
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.history[:] = old_history
|
||||||
|
|
||||||
def prepare(self) -> None:
|
def prepare(self) -> None:
|
||||||
super().prepare()
|
super().prepare()
|
||||||
|
|
|
@ -77,7 +77,7 @@ REPL_COMMANDS = {
|
||||||
"exit": _sitebuiltins.Quitter('exit', ''),
|
"exit": _sitebuiltins.Quitter('exit', ''),
|
||||||
"quit": _sitebuiltins.Quitter('quit' ,''),
|
"quit": _sitebuiltins.Quitter('quit' ,''),
|
||||||
"copyright": _sitebuiltins._Printer('copyright', sys.copyright),
|
"copyright": _sitebuiltins._Printer('copyright', sys.copyright),
|
||||||
"help": "help",
|
"help": _sitebuiltins._Helper(),
|
||||||
"clear": _clear_screen,
|
"clear": _clear_screen,
|
||||||
"\x1a": _sitebuiltins.Quitter('\x1a', ''),
|
"\x1a": _sitebuiltins.Quitter('\x1a', ''),
|
||||||
}
|
}
|
||||||
|
@ -124,18 +124,10 @@ def run_multiline_interactive_console(
|
||||||
reader.history.pop() # skip internal commands in history
|
reader.history.pop() # skip internal commands in history
|
||||||
command = REPL_COMMANDS[statement]
|
command = REPL_COMMANDS[statement]
|
||||||
if callable(command):
|
if callable(command):
|
||||||
command()
|
# Make sure that history does not change because of commands
|
||||||
|
with reader.suspend_history():
|
||||||
|
command()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if isinstance(command, str):
|
|
||||||
# Internal readline commands require a prepared reader like
|
|
||||||
# inside multiline_input.
|
|
||||||
reader.prepare()
|
|
||||||
reader.refresh()
|
|
||||||
reader.do_cmd((command, [statement]))
|
|
||||||
reader.restore()
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -240,7 +240,7 @@ class UnixConsole(Console):
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
self.__move(0, len(self.screen) - 1)
|
self.__move(0, len(self.screen) - 1)
|
||||||
self.__write("\n")
|
self.__write("\n")
|
||||||
self.__posxy = 0, len(self.screen)
|
self.posxy = 0, len(self.screen)
|
||||||
self.screen.append("")
|
self.screen.append("")
|
||||||
else:
|
else:
|
||||||
while len(self.screen) < len(screen):
|
while len(self.screen) < len(screen):
|
||||||
|
@ -250,7 +250,7 @@ class UnixConsole(Console):
|
||||||
self.__gone_tall = 1
|
self.__gone_tall = 1
|
||||||
self.__move = self.__move_tall
|
self.__move = self.__move_tall
|
||||||
|
|
||||||
px, py = self.__posxy
|
px, py = self.posxy
|
||||||
old_offset = offset = self.__offset
|
old_offset = offset = self.__offset
|
||||||
height = self.height
|
height = self.height
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ class UnixConsole(Console):
|
||||||
if old_offset > offset and self._ri:
|
if old_offset > offset and self._ri:
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
self.__write_code(self._cup, 0, 0)
|
self.__write_code(self._cup, 0, 0)
|
||||||
self.__posxy = 0, old_offset
|
self.posxy = 0, old_offset
|
||||||
for i in range(old_offset - offset):
|
for i in range(old_offset - offset):
|
||||||
self.__write_code(self._ri)
|
self.__write_code(self._ri)
|
||||||
oldscr.pop(-1)
|
oldscr.pop(-1)
|
||||||
|
@ -279,7 +279,7 @@ class UnixConsole(Console):
|
||||||
elif old_offset < offset and self._ind:
|
elif old_offset < offset and self._ind:
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
self.__write_code(self._cup, self.height - 1, 0)
|
self.__write_code(self._cup, self.height - 1, 0)
|
||||||
self.__posxy = 0, old_offset + self.height - 1
|
self.posxy = 0, old_offset + self.height - 1
|
||||||
for i in range(offset - old_offset):
|
for i in range(offset - old_offset):
|
||||||
self.__write_code(self._ind)
|
self.__write_code(self._ind)
|
||||||
oldscr.pop(0)
|
oldscr.pop(0)
|
||||||
|
@ -299,7 +299,7 @@ class UnixConsole(Console):
|
||||||
while y < len(oldscr):
|
while y < len(oldscr):
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
self.__move(0, y)
|
self.__move(0, y)
|
||||||
self.__posxy = 0, y
|
self.posxy = 0, y
|
||||||
self.__write_code(self._el)
|
self.__write_code(self._el)
|
||||||
y += 1
|
y += 1
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ class UnixConsole(Console):
|
||||||
self.event_queue.insert(Event("scroll", None))
|
self.event_queue.insert(Event("scroll", None))
|
||||||
else:
|
else:
|
||||||
self.__move(x, y)
|
self.__move(x, y)
|
||||||
self.__posxy = x, y
|
self.posxy = x, y
|
||||||
self.flushoutput()
|
self.flushoutput()
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
|
@ -350,7 +350,7 @@ class UnixConsole(Console):
|
||||||
|
|
||||||
self.__buffer = []
|
self.__buffer = []
|
||||||
|
|
||||||
self.__posxy = 0, 0
|
self.posxy = 0, 0
|
||||||
self.__gone_tall = 0
|
self.__gone_tall = 0
|
||||||
self.__move = self.__move_short
|
self.__move = self.__move_short
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
|
@ -559,7 +559,7 @@ class UnixConsole(Console):
|
||||||
self.__write_code(self._clear)
|
self.__write_code(self._clear)
|
||||||
self.__gone_tall = 1
|
self.__gone_tall = 1
|
||||||
self.__move = self.__move_tall
|
self.__move = self.__move_tall
|
||||||
self.__posxy = 0, 0
|
self.posxy = 0, 0
|
||||||
self.screen = []
|
self.screen = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -644,8 +644,8 @@ class UnixConsole(Console):
|
||||||
# if we need to insert a single character right after the first detected change
|
# if we need to insert a single character right after the first detected change
|
||||||
if oldline[x_pos:] == newline[x_pos + 1 :] and self.ich1:
|
if oldline[x_pos:] == newline[x_pos + 1 :] and self.ich1:
|
||||||
if (
|
if (
|
||||||
y == self.__posxy[1]
|
y == self.posxy[1]
|
||||||
and x_coord > self.__posxy[0]
|
and x_coord > self.posxy[0]
|
||||||
and oldline[px_pos:x_pos] == newline[px_pos + 1 : x_pos + 1]
|
and oldline[px_pos:x_pos] == newline[px_pos + 1 : x_pos + 1]
|
||||||
):
|
):
|
||||||
x_pos = px_pos
|
x_pos = px_pos
|
||||||
|
@ -654,7 +654,7 @@ class UnixConsole(Console):
|
||||||
self.__move(x_coord, y)
|
self.__move(x_coord, y)
|
||||||
self.__write_code(self.ich1)
|
self.__write_code(self.ich1)
|
||||||
self.__write(newline[x_pos])
|
self.__write(newline[x_pos])
|
||||||
self.__posxy = x_coord + character_width, y
|
self.posxy = x_coord + character_width, y
|
||||||
|
|
||||||
# if it's a single character change in the middle of the line
|
# if it's a single character change in the middle of the line
|
||||||
elif (
|
elif (
|
||||||
|
@ -665,7 +665,7 @@ class UnixConsole(Console):
|
||||||
character_width = wlen(newline[x_pos])
|
character_width = wlen(newline[x_pos])
|
||||||
self.__move(x_coord, y)
|
self.__move(x_coord, y)
|
||||||
self.__write(newline[x_pos])
|
self.__write(newline[x_pos])
|
||||||
self.__posxy = x_coord + character_width, y
|
self.posxy = x_coord + character_width, y
|
||||||
|
|
||||||
# if this is the last character to fit in the line and we edit in the middle of the line
|
# if this is the last character to fit in the line and we edit in the middle of the line
|
||||||
elif (
|
elif (
|
||||||
|
@ -677,14 +677,14 @@ class UnixConsole(Console):
|
||||||
):
|
):
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
self.__move(self.width - 2, y)
|
self.__move(self.width - 2, y)
|
||||||
self.__posxy = self.width - 2, y
|
self.posxy = self.width - 2, y
|
||||||
self.__write_code(self.dch1)
|
self.__write_code(self.dch1)
|
||||||
|
|
||||||
character_width = wlen(newline[x_pos])
|
character_width = wlen(newline[x_pos])
|
||||||
self.__move(x_coord, y)
|
self.__move(x_coord, y)
|
||||||
self.__write_code(self.ich1)
|
self.__write_code(self.ich1)
|
||||||
self.__write(newline[x_pos])
|
self.__write(newline[x_pos])
|
||||||
self.__posxy = character_width + 1, y
|
self.posxy = character_width + 1, y
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.__hide_cursor()
|
self.__hide_cursor()
|
||||||
|
@ -692,7 +692,7 @@ class UnixConsole(Console):
|
||||||
if wlen(oldline) > wlen(newline):
|
if wlen(oldline) > wlen(newline):
|
||||||
self.__write_code(self._el)
|
self.__write_code(self._el)
|
||||||
self.__write(newline[x_pos:])
|
self.__write(newline[x_pos:])
|
||||||
self.__posxy = wlen(newline), y
|
self.posxy = wlen(newline), y
|
||||||
|
|
||||||
if "\x1b" in newline:
|
if "\x1b" in newline:
|
||||||
# ANSI escape characters are present, so we can't assume
|
# ANSI escape characters are present, so we can't assume
|
||||||
|
@ -711,32 +711,36 @@ class UnixConsole(Console):
|
||||||
self.__write_code(fmt, *args)
|
self.__write_code(fmt, *args)
|
||||||
|
|
||||||
def __move_y_cuu1_cud1(self, y):
|
def __move_y_cuu1_cud1(self, y):
|
||||||
dy = y - self.__posxy[1]
|
assert self._cud1 is not None
|
||||||
|
assert self._cuu1 is not None
|
||||||
|
dy = y - self.posxy[1]
|
||||||
if dy > 0:
|
if dy > 0:
|
||||||
self.__write_code(dy * self._cud1)
|
self.__write_code(dy * self._cud1)
|
||||||
elif dy < 0:
|
elif dy < 0:
|
||||||
self.__write_code((-dy) * self._cuu1)
|
self.__write_code((-dy) * self._cuu1)
|
||||||
|
|
||||||
def __move_y_cuu_cud(self, y):
|
def __move_y_cuu_cud(self, y):
|
||||||
dy = y - self.__posxy[1]
|
dy = y - self.posxy[1]
|
||||||
if dy > 0:
|
if dy > 0:
|
||||||
self.__write_code(self._cud, dy)
|
self.__write_code(self._cud, dy)
|
||||||
elif dy < 0:
|
elif dy < 0:
|
||||||
self.__write_code(self._cuu, -dy)
|
self.__write_code(self._cuu, -dy)
|
||||||
|
|
||||||
def __move_x_hpa(self, x: int) -> None:
|
def __move_x_hpa(self, x: int) -> None:
|
||||||
if x != self.__posxy[0]:
|
if x != self.posxy[0]:
|
||||||
self.__write_code(self._hpa, x)
|
self.__write_code(self._hpa, x)
|
||||||
|
|
||||||
def __move_x_cub1_cuf1(self, x: int) -> None:
|
def __move_x_cub1_cuf1(self, x: int) -> None:
|
||||||
dx = x - self.__posxy[0]
|
assert self._cuf1 is not None
|
||||||
|
assert self._cub1 is not None
|
||||||
|
dx = x - self.posxy[0]
|
||||||
if dx > 0:
|
if dx > 0:
|
||||||
self.__write_code(self._cuf1 * dx)
|
self.__write_code(self._cuf1 * dx)
|
||||||
elif dx < 0:
|
elif dx < 0:
|
||||||
self.__write_code(self._cub1 * (-dx))
|
self.__write_code(self._cub1 * (-dx))
|
||||||
|
|
||||||
def __move_x_cub_cuf(self, x: int) -> None:
|
def __move_x_cub_cuf(self, x: int) -> None:
|
||||||
dx = x - self.__posxy[0]
|
dx = x - self.posxy[0]
|
||||||
if dx > 0:
|
if dx > 0:
|
||||||
self.__write_code(self._cuf, dx)
|
self.__write_code(self._cuf, dx)
|
||||||
elif dx < 0:
|
elif dx < 0:
|
||||||
|
@ -766,12 +770,12 @@ class UnixConsole(Console):
|
||||||
|
|
||||||
def repaint(self):
|
def repaint(self):
|
||||||
if not self.__gone_tall:
|
if not self.__gone_tall:
|
||||||
self.__posxy = 0, self.__posxy[1]
|
self.posxy = 0, self.posxy[1]
|
||||||
self.__write("\r")
|
self.__write("\r")
|
||||||
ns = len(self.screen) * ["\000" * self.width]
|
ns = len(self.screen) * ["\000" * self.width]
|
||||||
self.screen = ns
|
self.screen = ns
|
||||||
else:
|
else:
|
||||||
self.__posxy = 0, self.__offset
|
self.posxy = 0, self.__offset
|
||||||
self.__move(0, self.__offset)
|
self.__move(0, self.__offset)
|
||||||
ns = self.height * ["\000" * self.width]
|
ns = self.height * ["\000" * self.width]
|
||||||
self.screen = ns
|
self.screen = ns
|
||||||
|
|
|
@ -152,10 +152,10 @@ class WindowsConsole(Console):
|
||||||
self._hide_cursor()
|
self._hide_cursor()
|
||||||
self._move_relative(0, len(self.screen) - 1)
|
self._move_relative(0, len(self.screen) - 1)
|
||||||
self.__write("\n")
|
self.__write("\n")
|
||||||
self.__posxy = 0, len(self.screen)
|
self.posxy = 0, len(self.screen)
|
||||||
self.screen.append("")
|
self.screen.append("")
|
||||||
|
|
||||||
px, py = self.__posxy
|
px, py = self.posxy
|
||||||
old_offset = offset = self.__offset
|
old_offset = offset = self.__offset
|
||||||
height = self.height
|
height = self.height
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ class WindowsConsole(Console):
|
||||||
# portion of the window. We need to scroll the visible portion and the
|
# portion of the window. We need to scroll the visible portion and the
|
||||||
# entire history
|
# entire history
|
||||||
self._scroll(scroll_lines, self._getscrollbacksize())
|
self._scroll(scroll_lines, self._getscrollbacksize())
|
||||||
self.__posxy = self.__posxy[0], self.__posxy[1] + scroll_lines
|
self.posxy = self.posxy[0], self.posxy[1] + scroll_lines
|
||||||
self.__offset += scroll_lines
|
self.__offset += scroll_lines
|
||||||
|
|
||||||
for i in range(scroll_lines):
|
for i in range(scroll_lines):
|
||||||
|
@ -197,7 +197,7 @@ class WindowsConsole(Console):
|
||||||
y = len(newscr)
|
y = len(newscr)
|
||||||
while y < len(oldscr):
|
while y < len(oldscr):
|
||||||
self._move_relative(0, y)
|
self._move_relative(0, y)
|
||||||
self.__posxy = 0, y
|
self.posxy = 0, y
|
||||||
self._erase_to_end()
|
self._erase_to_end()
|
||||||
y += 1
|
y += 1
|
||||||
|
|
||||||
|
@ -254,11 +254,11 @@ class WindowsConsole(Console):
|
||||||
if wlen(newline) == self.width:
|
if wlen(newline) == self.width:
|
||||||
# If we wrapped we want to start at the next line
|
# If we wrapped we want to start at the next line
|
||||||
self._move_relative(0, y + 1)
|
self._move_relative(0, y + 1)
|
||||||
self.__posxy = 0, y + 1
|
self.posxy = 0, y + 1
|
||||||
else:
|
else:
|
||||||
self.__posxy = wlen(newline), y
|
self.posxy = wlen(newline), y
|
||||||
|
|
||||||
if "\x1b" in newline or y != self.__posxy[1] or '\x1a' in newline:
|
if "\x1b" in newline or y != self.posxy[1] or '\x1a' in newline:
|
||||||
# ANSI escape characters are present, so we can't assume
|
# ANSI escape characters are present, so we can't assume
|
||||||
# anything about the position of the cursor. Moving the cursor
|
# anything about the position of the cursor. Moving the cursor
|
||||||
# to the left margin should work to get to a known position.
|
# to the left margin should work to get to a known position.
|
||||||
|
@ -320,7 +320,7 @@ class WindowsConsole(Console):
|
||||||
self.screen = []
|
self.screen = []
|
||||||
self.height, self.width = self.getheightwidth()
|
self.height, self.width = self.getheightwidth()
|
||||||
|
|
||||||
self.__posxy = 0, 0
|
self.posxy = 0, 0
|
||||||
self.__gone_tall = 0
|
self.__gone_tall = 0
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
|
|
||||||
|
@ -328,9 +328,9 @@ class WindowsConsole(Console):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _move_relative(self, x: int, y: int) -> None:
|
def _move_relative(self, x: int, y: int) -> None:
|
||||||
"""Moves relative to the current __posxy"""
|
"""Moves relative to the current posxy"""
|
||||||
dx = x - self.__posxy[0]
|
dx = x - self.posxy[0]
|
||||||
dy = y - self.__posxy[1]
|
dy = y - self.posxy[1]
|
||||||
if dx < 0:
|
if dx < 0:
|
||||||
self.__write(MOVE_LEFT.format(-dx))
|
self.__write(MOVE_LEFT.format(-dx))
|
||||||
elif dx > 0:
|
elif dx > 0:
|
||||||
|
@ -349,7 +349,7 @@ class WindowsConsole(Console):
|
||||||
self.event_queue.insert(0, Event("scroll", ""))
|
self.event_queue.insert(0, Event("scroll", ""))
|
||||||
else:
|
else:
|
||||||
self._move_relative(x, y)
|
self._move_relative(x, y)
|
||||||
self.__posxy = x, y
|
self.posxy = x, y
|
||||||
|
|
||||||
def set_cursor_vis(self, visible: bool) -> None:
|
def set_cursor_vis(self, visible: bool) -> None:
|
||||||
if visible:
|
if visible:
|
||||||
|
@ -455,7 +455,7 @@ class WindowsConsole(Console):
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""Wipe the screen"""
|
"""Wipe the screen"""
|
||||||
self.__write(CLEAR)
|
self.__write(CLEAR)
|
||||||
self.__posxy = 0, 0
|
self.posxy = 0, 0
|
||||||
self.screen = [""]
|
self.screen = [""]
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
|
|
|
@ -1343,3 +1343,16 @@ class TestMain(ReplTestCase):
|
||||||
def test_keyboard_interrupt_after_isearch(self):
|
def test_keyboard_interrupt_after_isearch(self):
|
||||||
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
|
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
|
||||||
self.assertEqual(exit_code, 0)
|
self.assertEqual(exit_code, 0)
|
||||||
|
|
||||||
|
def test_prompt_after_help(self):
|
||||||
|
output, exit_code = self.run_repl(["help", "q", "exit"])
|
||||||
|
|
||||||
|
# Regex pattern to remove ANSI escape sequences
|
||||||
|
ansi_escape = re.compile(r"(\x1B(=|>|(\[)[0-?]*[ -\/]*[@-~]))")
|
||||||
|
cleaned_output = ansi_escape.sub("", output)
|
||||||
|
self.assertEqual(exit_code, 0)
|
||||||
|
|
||||||
|
# Ensure that we don't see multiple prompts after exiting `help`
|
||||||
|
# Extra stuff (newline and `exit` rewrites) are necessary
|
||||||
|
# because of how run_repl works.
|
||||||
|
self.assertNotIn(">>> \n>>> >>>", cleaned_output)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue