diff -r ede3ac49b4ac Lib/curses/textpad.py --- a/Lib/curses/textpad.py Fri Dec 16 23:17:52 2016 -0500 +++ b/Lib/curses/textpad.py Sat Dec 17 21:19:36 2016 +0200 @@ -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 -r ede3ac49b4ac Lib/test/test_curses.py --- a/Lib/test/test_curses.py Fri Dec 16 23:17:52 2016 -0500 +++ b/Lib/test/test_curses.py Sat Dec 17 21:19:36 2016 +0200 @@ -27,6 +27,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), @@ -392,6 +393,14 @@ class TestCurses(unittest.TestCase): human_readable_signature = stdscr.addch.__doc__.split("\n")[0] self.assertIn("[y, x,]", human_readable_signature) + 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 MiscTests(unittest.TestCase): diff -r ede3ac49b4ac Misc/ACKS --- a/Misc/ACKS Fri Dec 16 23:17:52 2016 -0500 +++ b/Misc/ACKS Sat Dec 17 21:19:36 2016 +0200 @@ -41,6 +41,7 @@ A. Amoroso Mark Anacker Shashwat Anand Anders Andersen +Tycho Andersen John Anderson Pehr Anderson Erik Andersén