#!/usr/bin/env python """ emano.py - Emano Editor Class Author: Sean B. Palmer, inamidst.com Source: http://inamidst.com/emano/ """ import curses.ascii import document, events class Editor(object): def __init__(self, screen, filenames=None): self.screen = screen self.documents = [] self.clipboard = '' self.setupConfig() self.setupWindows() if hasattr(self.menu, 'openFilenames'): self.menu.openFilenames(filenames) else: self.statwin.notice("Couldn't open args") self.new() self.statwin.default() def setupConfig(self): class Config(object): def __getattr__(self, attr): if hasattr(self, attr): return object.__getattr__(self, attr) return None self.config = Config() # Internally set configuration options self.config.DefaultInputEncoding = 'utf-8' self.config.CommentString = '#' # @@ what if it contains \r\n? self.config.IndentString = ' ' # # Options set from a config file # configFile = os.path.join(dotemano, 'config') # if os.path.isfile(configFile): # f = open(configFile) # for line in f: # line = line.rstrip('\r\n') # if ': ' in line: # name, value = line.split(': ', 1) # setattr(self.config, name, value) # f.close() def setupWindows(self): # Hmm, Maxy and Maxx sound like strippers maxy, maxx = self.screen.getmaxyx() self.maxy = maxy - 2 # @@ why 2? self.maxx = maxx - 1 # @@ why 1? import menu self.menu = menu.Menu(self) import main, status, item self.mainwin = main.MainWindow(self) self.statwin = status.StatusWindow(self) self.mainwin.mappings[events.CTRL_R] = self.statwin.default self.itemwin = item.ItemWindow(self) self.setFocus(self.mainwin) def new(self): doc = document.Document(editor=self) if doc.error: self.statwin.notice('Error: ' + doc.error) return self.documents.append(doc) self.edit(doc) self.moveCursor(0, 0) def open(self, uri=None, lines=None): # @@ Allow opening into the current window? desc = uri or ('%s lines' % len(lines)) self.statwin.notice('Opening %s...' % desc) doc = document.Document(editor=self, uri=uri, lines=lines) if doc.error: if doc.error == 'Tried to open a directory': if self.menu.browseDirectory: self.menu.browseDirectory(uri) else: self.statwin.notice("Error: can't open directories") else: self.statwin.notice(doc.error) return self.documents.append(doc) self.edit(doc) self.moveCursor(0, 0) self.statwin.notice('Editing %s' % desc) def save(self, uri=None): self.doc.save(uri=uri) def edit(self, doc): if doc not in self.documents: self.statwin.notice('Error: not a current document') return self.doc = doc self.getDocumentPos() self.mainwin.draw() def setDocumentPos(self): y, x, q, p = self.mainwin.position() self.doc.pos = [q, p] def getDocumentPos(self): q, p = tuple(self.doc.pos) y = q - self.doc.startline x = p - self.doc.getStartchar(q) self.moveCursor(y, x) def setFocus(self, window): # @@ If focus is already on the window, send a warning # when focussing away from mainwin, store pos # when focussing to mainwin, read pos if hasattr(self, 'focus'): if (self.focus is self.mainwin) and (window is not self.mainwin): self.setDocumentPos() elif (self.focus is not self.mainwin) and (window is self.mainwin): self.getDocumentPos() if (self.focus is self.itemwin) and (window is not self.itemwin): y, x, q, p = self.mainwin.position() self.itemwin.pos = q # @@ add p too? elif (self.focus is not self.itemwin) and (window is self.itemwin): y = self.itemwin.pos - self.itemwin.startline # @@ uh, hmm? y = min(self.maxy, y) self.moveCursor(y, 0) self.focus = window def moveCursor(self, y, x): if ((y < 0) or (y > self.maxy + 1) or (x < 0) or (x > self.maxx) or ((y > self.maxy) and (self.focus is self.mainwin))): msg = 'Error: tried to move to (%s, %s). KABOOM! Save & restart!' raise ValueError(msg % (y, x)) self.screen.move(y, x) def dialogue(self, *args, **kargs): # This is used often enough to warrant this alias self.statwin.dialogue(*args, **kargs) def error(self, err): # Last line of defence. If this errors out, bye-bye work! if isinstance(err, SystemExit): raise err try: import sys, traceback lines = ["Emano Error!", "", "Emano is experimental, and, as such, breaks occasionally. This ", "is one of those occasions. The error has been caught, so your ", "work should be safe, but think about saving and restarting.", "", "Here's the error: ", ""] for line in traceback.format_exception(*sys.exc_info()): lines.extend(line.rstrip('\r\n').splitlines()) lines.extend(["", "(Ctrl+W to close this document.)", ""]) self.open(lines=lines) self.setFocus(self.mainwin) # @@ put this in self.open? except Exception, e: msg = 'Error: Got %s when reporting an error! Save & restart!' self.statwin.notice(msg % e, 2) def getEvent(self): """Get a character from the screen or return None.""" try: ch = self.screen.getch() except KeyboardInterrupt: return None # If it's not an escape sequence, return the character event if ch != 27: # ASCII 27 is \E (ESCAPE) return ch escapes = { '[': { '1': { '~': events.HOME }, # e.g. cygwin, linux '3': { '*': events.DELETE }, # @@ ? '4': { '*': events.END }, # @@ ? '5': { '*': events.PAGE_UP }, # e.g. xterm, rxvt, cygwin, linux '6': { '*': events.PAGE_DOWN }, '7': { '*': events.HOME }, # e.g. rxvt '8': { '*': events.END }, '9': events.DELETE, 'A': events.UP, # e.g. xterm, rxvt, cygwin, linux # \E[B is also cud1 in cygwin, as well as kcud1 'B': events.DOWN, # e.g. rxvt, cygwin, linux; but *not* xterm 'C': events.RIGHT, 'D': events.LEFT, 'F': events.END, 'G': events.PAGE_DOWN, # FreeBSD 'H': events.HOME, 'I': events.PAGE_UP, # FreeBSD 'V': events.PAGE_UP, # Hurd 'U': events.PAGE_DOWN, # Hurd 'Y': events.END }, 'O': { 'A': events.UP, # \EOB is kcud1 in xterm, but Ctrl+DOWN (not DOWN) rxvt-cygwin! 'B': events.DOWN, 'C': events.RIGHT, 'D': events.LEFT, 'F': events.END, 'H': events.HOME # e.g. xterm } } while True: ch = self.screen.getch() c = chr(ch) if escapes.has_key(c): value = escapes[c] if isinstance(value, dict): escapes = value continue else: return value elif escapes.has_key('*'): return escapes['*'] else: break # @@ Otherwise, try to work it out from terminfo? return ch def insertChar(self, c): self.focus.insertChar(c) if (self.focus is self.mainwin): self.statwin.default() def event(self, e): DEBUG = False if DEBUG and (self.mode == 'editor'): for char in '{%s}' % curses.unctrl(e): self.insertChar(ord(char)) if isinstance(e, int) and curses.ascii.isprint(e): try: self.insertChar(e) except Exception, err: self.error(err) return True if hasattr(self.focus, 'event'): try: result = self.focus.event(e) except Exception, err: try: self.error(err) except SystemExit, e: raise e except Exception, e: pass result = True if not result: # @@ This is debug information if e == -1: raise ValueError("Got a wacky -1") # @@ self.statwin.refresh(augment=(' [%r?]' % e)) return True def run(self): # listen for events while True: e = self.getEvent() n = self.event(e) if not n: break def run(screen, filenames): e = Editor(screen, filenames) e.run() if __name__=="__main__": main()