From: Serhiy Storchaka Date: Wed, 28 Dec 2016 08:16:06 +0000 (+0200) Subject: Issue #13051: Fixed recursion errors in large or resized curses.textpad.Textbox. X-Git-Tag: v2.7.14rc1~313 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4d42af136bb62f138d2d119c244caeaac78cc9ad;p=python Issue #13051: Fixed recursion errors in large or resized curses.textpad.Textbox. Based on patch by Tycho Andersen. --- diff --git a/Lib/curses/textpad.py b/Lib/curses/textpad.py index c45361c7d2..b50c03a22d 100644 --- a/Lib/curses/textpad.py +++ b/Lib/curses/textpad.py @@ -43,16 +43,20 @@ class Textbox: def __init__(self, win, insert_mode=False): self.win = win self.insert_mode = insert_mode - (self.maxy, self.maxx) = win.getmaxyx() - self.maxy = self.maxy - 1 - self.maxx = self.maxx - 1 + self._update_max_yx() self.stripspaces = 1 self.lastcmd = None win.keypad(1) + def _update_max_yx(self): + maxy, maxx = self.win.getmaxyx() + self.maxy = maxy - 1 + self.maxx = maxx - 1 + def _end_of_line(self, y): """Go to the location of the first blank on the given line, returning the index of the last non-blank character.""" + self._update_max_yx() last = self.maxx while True: if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP: @@ -64,8 +68,10 @@ class Textbox: return last def _insert_printable_char(self, ch): + self._update_max_yx() (y, x) = self.win.getyx() - if y < self.maxy or x < self.maxx: + backyx = None + while y < self.maxy or x < self.maxx: if self.insert_mode: oldch = self.win.inch() # The try-catch ignores the error we trigger from some curses @@ -75,14 +81,20 @@ class Textbox: self.win.addch(ch) except curses.error: pass - if self.insert_mode: - (backy, backx) = self.win.getyx() - if curses.ascii.isprint(oldch): - self._insert_printable_char(oldch) - self.win.move(backy, backx) + if not self.insert_mode or not curses.ascii.isprint(oldch): + break + ch = oldch + (y, x) = self.win.getyx() + # Remember where to put the cursor back since we are in insert_mode + if backyx is None: + backyx = y, x + + if backyx is not None: + self.win.move(*backyx) def do_command(self, ch): "Process a single editing command." + self._update_max_yx() (y, x) = self.win.getyx() self.lastcmd = ch if curses.ascii.isprint(ch): @@ -148,6 +160,7 @@ class Textbox: def gather(self): "Collect and return the contents of the window." result = "" + self._update_max_yx() for y in range(self.maxy+1): self.win.move(y, 0) stop = self._end_of_line(y) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index be0837fa47..7d8ddcdce0 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -26,6 +26,7 @@ requires('curses') curses = import_module('curses') import_module('curses.panel') import_module('curses.ascii') +import_module('curses.textpad') def requires_curses_func(name): return unittest.skipUnless(hasattr(curses, name), @@ -327,6 +328,14 @@ class TestCurses(unittest.TestCase): b = curses.tparm(curses.tigetstr("cup"), 5, 3) self.assertIs(type(b), bytes) + def test_issue13051(self): + stdscr = self.stdscr + box = curses.textpad.Textbox(stdscr, insert_mode=True) + lines, cols = stdscr.getmaxyx() + stdscr.resize(lines-2, cols-2) + # this may cause infinite recursion, leading to a RuntimeError + box._insert_printable_char('a') + class TestAscii(unittest.TestCase): diff --git a/Misc/ACKS b/Misc/ACKS index ee73c542a2..223b3a8be5 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -37,6 +37,7 @@ A. Amoroso Mark Anacker Shashwat Anand Anders Andersen +Tycho Andersen John Anderson Pehr Anderson Erik Andersén diff --git a/Misc/NEWS b/Misc/NEWS index 839e910ca5..dc33582c3c 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,6 +15,9 @@ Core and Builtins Library ------- +- Issue #13051: Fixed recursion errors in large or resized + curses.textpad.Textbox. Based on patch by Tycho Andersen. + - Issue #9770: curses.ascii predicates now work correctly with negative integers.