diff -r d85efd73b0e1 Lib/curses/textpad.py --- a/Lib/curses/textpad.py Mon Dec 19 16:41:11 2011 -0500 +++ b/Lib/curses/textpad.py Tue Dec 20 11:14:51 2011 -0600 @@ -43,20 +43,22 @@ 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.stripspaces = 1 self.lastcmd = None win.keypad(1) + def _getmaxyx(self): + (maxy, maxx) = self.win.getmaxyx() + return maxy-1, 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.""" - last = self.maxx + (maxy, maxx) = self._getmaxyx() + last = maxx while True: if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP: - last = min(self.maxx, last+1) + last = min(maxx, last+1) break elif last == 0: break @@ -65,9 +67,12 @@ def _insert_printable_char(self, ch): (y, x) = self.win.getyx() - if y < self.maxy or x < self.maxx: - if self.insert_mode: - oldch = self.win.inch() + (maxy, maxx) = self._getmaxyx() + (backy, backx) = None, None + + while y < maxy or x < maxx: + oldch = self.win.inch() + # The try-catch ignores the error we trigger from some curses # versions by trying to write into the lowest-rightmost spot # in the window. @@ -75,18 +80,27 @@ self.win.addch(ch) except curses.error: pass - if self.insert_mode: + + if not self.insert_mode or not curses.ascii.isprint(oldch): + break + + # Remember where to put the cursor back since we are in insert_mode + if backy is None: (backy, backx) = self.win.getyx() - if curses.ascii.isprint(oldch): - self._insert_printable_char(oldch) - self.win.move(backy, backx) + + ch = oldch + (y, x) = self.win.getyx() + + if self.insert_mode and backy: + self.win.move(backy, backx) def do_command(self, ch): "Process a single editing command." + (maxy, maxx) = self._getmaxyx() (y, x) = self.win.getyx() self.lastcmd = ch if curses.ascii.isprint(ch): - if y < self.maxy or x < self.maxx: + if y < maxy or x < maxx: self._insert_printable_char(ch) elif ch == curses.ascii.SOH: # ^a self.win.move(y, 0) @@ -98,7 +112,7 @@ elif self.stripspaces: self.win.move(y-1, self._end_of_line(y-1)) else: - self.win.move(y-1, self.maxx) + self.win.move(y-1, maxx) if ch in (curses.ascii.BS, curses.KEY_BACKSPACE): self.win.delch() elif ch == curses.ascii.EOT: # ^d @@ -107,20 +121,20 @@ if self.stripspaces: self.win.move(y, self._end_of_line(y)) else: - self.win.move(y, self.maxx) + self.win.move(y, maxx) elif ch in (curses.ascii.ACK, curses.KEY_RIGHT): # ^f - if x < self.maxx: + if x < maxx: self.win.move(y, x+1) - elif y == self.maxy: + elif y == maxy: pass else: self.win.move(y+1, 0) elif ch == curses.ascii.BEL: # ^g return 0 elif ch == curses.ascii.NL: # ^j - if self.maxy == 0: + if maxy == 0: return 0 - elif y < self.maxy: + elif y < maxy: self.win.move(y+1, 0) elif ch == curses.ascii.VT: # ^k if x == 0 and self._end_of_line(y) == 0: @@ -132,7 +146,7 @@ elif ch == curses.ascii.FF: # ^l self.win.refresh() elif ch in (curses.ascii.SO, curses.KEY_DOWN): # ^n - if y < self.maxy: + if y < maxy: self.win.move(y+1, x) if x > self._end_of_line(y+1): self.win.move(y+1, self._end_of_line(y+1)) @@ -148,16 +162,17 @@ def gather(self): "Collect and return the contents of the window." result = "" - for y in range(self.maxy+1): + (maxy, maxx) = self._getmaxyx() + for y in range(maxy+1): self.win.move(y, 0) stop = self._end_of_line(y) if stop == 0 and self.stripspaces: continue - for x in range(self.maxx+1): + for x in range(maxx+1): if self.stripspaces and x > stop: break result = result + chr(curses.ascii.ascii(self.win.inch(y, x))) - if self.maxy > 0: + if maxy > 0: result = result + "\n" return result diff -r d85efd73b0e1 Lib/test/test_curses.py --- a/Lib/test/test_curses.py Mon Dec 19 16:41:11 2011 -0500 +++ b/Lib/test/test_curses.py Tue Dec 20 11:14:51 2011 -0600 @@ -22,7 +22,7 @@ # If either of these don't exist, skip the tests. curses = import_module('curses') curses.panel = import_module('curses.panel') - +curses.textpad = import_module('curses.textpad') # XXX: if newterm was supported we could use it instead of initscr and not exit term = os.environ.get('TERM') @@ -269,9 +269,23 @@ assert type(b) is bytes curses.putp(b) +def test_issue13051(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') + + # clean up for other tests + stdscr.clear() + def main(stdscr): curses.savetty() try: + # this test doesn't play nicely if there's random garbage on the + # screen, so we run it first + test_issue13051(stdscr) module_funcs(stdscr) window_funcs(stdscr) test_userptr_without_set(stdscr) diff -r d85efd73b0e1 Misc/ACKS --- a/Misc/ACKS Mon Dec 19 16:41:11 2011 -0500 +++ b/Misc/ACKS Tue Dec 20 11:14:51 2011 -0600 @@ -26,6 +26,7 @@ Mark Anacker Shashwat Anand Anders Andersen +Tycho Andersen John Anderson Erik Andersén Oliver Andrich