#!/usr/bin/env python """A Python curses test.""" import sys, curses, curses.textpad import curses.ascii as ascii class Editor: """Editing widget using the interior of a window object.""" def __init__(self, win, fn=None): self.win = win maxy, maxx = win.getmaxyx() self.maxy = maxy - 3 self.maxx = maxx - 1 self.win.keypad(1) self.startline = 0 if fn: lines = open(fn).read().splitlines() else: lines = [''] self.lines = lines self.draw() prompt = "$[" + (" " * (self.maxx - 3)) + "]" self.win.addstr(self.maxy + 2, 0, prompt, curses.A_BOLD) msg = "Type stuff. Edit stuff. Do something like Ctrl+S to save." self.setStatus(msg) # self.win.refresh() self.win.move(0, 0) def setStatus(self, s): if len(s) < self.maxx: s += ' ' * ((self.maxx - len(s)) + 1) else: s = s[:self.maxx] self.win.addstr(self.maxy + 1, 0, s, curses.A_REVERSE) self.win.refresh() def draw(self): for y in xrange(self.maxy + 1): Y = y + self.startline try: line = self.lines[Y] except IndexError: line = '' if len(line) < self.maxx: line += ' ' * ((self.maxx - len(line)) + 1) else: line = line[:self.maxx] self.win.addstr(y, 0, line) self.win.refresh() def insert(self, y, x, s): Y = y + self.startline line = self.lines[Y] line = (line[:x] + s + line[x:])[:self.maxx] self.lines[Y] = line self.win.addstr(y, 0, line) self.win.refresh() self.win.move(y, x + len(s)) def delete(self, y, x, i): if i < 1: return if (y == 0) and (x == 0): return Y = y + self.startline if (x == 0) and (y > 0): line = self.lines[Y] prev = self.lines[Y-1] if i != 1: self.lines[Y-1] = prev[:-(i-1)] + line else: self.lines[Y-1] += line del self.lines[Y] self.draw() self.win.move(y - 1, (len(prev) - i) + 1) else: line = self.lines[Y] line = (line[:(x - i)] + line[x:])[:self.maxx] self.lines[Y] = line if len(line) < self.maxx: line += ' ' * ((self.maxx - len(line)) + 1) self.win.addstr(y, 0, line) self.win.refresh() self.win.move(y, x - i) def do_command(self, ch): "Process a single editing command." y, x = self.win.getyx() Y = y + self.startline if ascii.isprint(ch): if (y < self.maxy) or (x < self.maxx): # The try-catch ignores the error we trigger from some curses # versions by trying to write into the lowest-rightmost spot # in the window. try: self.insert(y, x, chr(ch)) except curses.error: pass elif ch in (ascii.DLE, curses.KEY_UP): if y > 0: n = len(self.lines[Y-1]) self.win.move(y-1, x) if x > n: self.win.move(y-1, n) elif self.startline > 0: self.startline -= 1 self.draw() self.win.move(y, x) elif ch in (ascii.SO, curses.KEY_DOWN): if Y < (len(self.lines) - 1) and y < self.maxy: n = len(self.lines[Y+1]) self.win.move(y+1, x) if x > n: self.win.move(y+1, n) elif Y < (len(self.lines) - 1) and y == self.maxy: self.startline += 1 self.draw() self.win.move(y, x) elif ch == 10: if y < self.maxy: line = self.lines[Y] self.lines[Y] = line[:x] rest = line[x:] self.lines.insert(Y + 1, rest) self.draw() self.win.move(y+1, 0) elif ch in (ascii.STX, curses.KEY_LEFT, ascii.BS, curses.KEY_BACKSPACE, 127): if x > 0: self.win.move(y, x-1) elif y == 0: pass else: self.win.move(y-1, len(self.lines[Y-1])) if ch in (ascii.BS, curses.KEY_BACKSPACE, 127): self.delete(y, x, 1) # @@! elif ch in (ascii.ACK, curses.KEY_RIGHT): if x < len(self.lines[Y]): self.win.move(y, x+1) elif y == self.maxy or Y == (len(self.lines) - 1): pass elif x >= len(self.lines[Y]): self.win.move(y+1, 0) elif ch == 330: # Delete; delete the bit after the cursor if x < len(self.lines[Y]): self.win.move(y, x+1) elif y == self.maxy or Y == (len(self.lines) - 1): pass elif x >= len(self.lines[Y]): self.win.move(y+1, 0) y, x = self.win.getyx() self.delete(y, x, 1) elif ch == 262: # Home; beginning of line self.win.move(y, 0) elif ch == 360: # End; end of line self.win.move(y, len(self.lines[Y])) elif ch == 17: # Ctrl+Q; Quit return 0 else: if y < self.maxy or x < self.maxx: try: self.insert(y, x, '[%r]' % ch) except curses.error: pass return 1 def run(stdscr): if sys.argv[1:]: fn = sys.argv[1] else: fn = None editor = Editor(stdscr, fn=fn) while 1: c = stdscr.getch() n = editor.do_command(c) if not n: break def main(): """Run run() in a curses wrapper.""" curses.wrapper(run) if __name__=="__main__": main()