#!/usr/bin/env python """ main.py - Emano MainWindow Class Author: Sean B. Palmer, inamidst.com Source: http://inamidst.com/emano/ """ import re, curses import events, window class MainWindow(window.Window): def __init__(self, editor): self.editor = editor self.typed = False self.mappings = { events.UP: self.moveUp, events.DOWN: self.moveDown, events.RIGHT: self.moveRight, events.LEFT: self.moveLeft, events.DELETE: self.delChar, events.PAGE_UP: self.pageUp, events.PAGE_DOWN: self.pageDown, events.HOME: self.startOfLine, events.END: self.endOfLine, # events.CTRL_A: self.editor.menu.selectAll, # events.CTRL_C: self.editor.menu.copy, events.CTRL_D: self.selectFrom, events.CTRL_F: self.editor.menu.chooseDocument, # events.CTRL_G: self.editor.menu.grepDialogue, # events.CTRL_H: self.editor.menu.help, # events.CTRL_I: self.editor.menu.indentLine, # events.CTRL_J: self.editor.menu.justify, events.CTRL_K: self.editor.menu.cutLine, # events.CTRL_L: self.editor.menu.undo, # @@ events.RETURN: self.newLine, # events.CTRL_N: self.editor.menu.commandMenu, events.CTRL_O: self.editor.menu.openDialogue, # events.CTRL_P: self.editor.menu.redo, # @@ events.CTRL_Q: self.editor.menu.exit, # @@! Dialogue, # events.CTRL_R: self.editor.menu.quickReference, events.CTRL_S: self.editor.menu.saveAsDialogue, # events.CTRL_T: self.editor.menu.findDialogue, # events.CTRL_U: self.editor.menu.unindentLine, events.CTRL_V: self.editor.menu.paste, events.CTRL_W: self.editor.menu.closeCurrentDocument, # events.CTRL_X: self.editor.menu.cut, events.BACKSPACE: self.deleteChar } def insertChar(self, c): y, x, q, p = self.position() line = self.editor.doc.line(q) # was: lines[q] if (x < self.editor.maxx) or (len(line) > self.editor.maxx): if c <= 0xFF: content = chr(c) else: content = unichr(c) self.editor.doc.insert(q, p, content) if x >= self.editor.maxx: self.editor.doc.setStartchar(q, add=(self.editor.maxx - 5)) self.drawline(y) # signif order again? can't be self.editor.moveCursor(y, 5) # @@ what if it only goes one over? else: self.drawline(y) self.editor.moveCursor(y, x + 1) # Otherwise, we're going off the edge of the screen and wrapping # This will probably include dragging a bit of a word down too else: self.newLine() # @@ if fill... if c <= 0xFF: text = chr(c) else: text = unichr(c) if (' ' in line) or ('\t' in line): space = max(line.rfind(' '), line.rfind('\t')) + 1 if space < len(line): # print >> debug, 'WRAP DELETE', ((q, space), (q, len(line))) self.editor.doc.delete((q, space), (q, len(line))) text = line[space:] + text self.editor.doc.insert(q + 1, 0, text) self.draw() # @@ draw two lines # self.drawline(y) self.editor.moveCursor(y + 1, len(text)) def drawline(self, y, raw=False, currentq=None): # if currentq is None: # currentq = self.position()[2] q = y + self.editor.doc.startline try: line = self.editor.doc.line(q) # was: lines[q] except IndexError: line = '' schar = self.editor.doc.getStartchar(q) if schar: line = '<-' + line[schar + 2:] maxx = self.editor.maxx if len(line) < maxx: spaces = ' ' * ((maxx - len(line)) + 1) elif len(line) > maxx: spaces, line = '', line[:maxx - 2] + '->' else: spaces = '' # Replace hi-bytes with question mark mojibake! if self.editor.doc.encoding == 'bytes': r_hibyte = re.compile(r'([\x80-\xff])') line = r_hibyte.sub('?', line) else: # Replace unicode with asciicode (ASCII codepoint substitutions) r_unicode = re.compile(ur'([\u0080-\uFFFF])') from asciicode import asciicode def replaceUnicode(m): char = m.group(1) if asciicode.has_key(char): return asciicode[char] return '?' line = r_unicode.sub(replaceUnicode, line) # Also replace \r with ?, because question marks are awesome line = line.replace('\r', '?') if self.editor.doc.isSelectionLine(q): (topy, topx), (boty, botx) = self.editor.doc.getTopAndBot() if (topy == q) or (boty == q): # this is the line in which the selection fores self.editor.screen.addstr(y, 0, line) if topy == q: # @@ def safe(num, upper=None) if schar: startpos = max(0, topx - schar) else: startpos = topx else: startpos = 0 if boty == q: if schar: endpos = botx - schar else: endpos = botx else: endpos = len(line) self.editor.screen.addstr(y, 0, line + spaces) args = (y, startpos, line[startpos:endpos], curses.A_REVERSE) # try: self.editor.screen.addstr(*args) # finally: print args, startpos, endpos # @@ only draw in mainwin focus mode? else: self.editor.screen.addstr(y, 0, line + spaces) self.editor.screen.addstr(y, 0, line, curses.A_REVERSE) else: self.editor.screen.addstr(y, 0, line + spaces) if not raw: self.editor.screen.refresh() def draw(self): # pos = self.editor.screen.getyx() self.editor.setDocumentPos() # currentq, currentp = self.editor.readDocumentPos() currentq, currentp = tuple(self.editor.doc.pos) for y in xrange(self.editor.maxy + 1): self.drawline(y, raw=True, currentq=currentq) self.editor.screen.refresh() # self.editor.moveCursor(*...) self.editor.getDocumentPos() def position(self): if hasattr(self.editor, 'doc'): y, x = self.editor.screen.getyx() q = y + self.editor.doc.startline p = x + self.editor.doc.getStartchar(q) return y, x, q, p # you have to love the ordering else: return 0, 0, 0, 0 # @@! # def safeMoveCursor(self, q, p): # @@ updateCursor? # pass def moveTo(self, q, p): doclen = self.editor.doc.length() - 1 # was: len(lines) if (q < 0) or (q > doclen): raise ValueError("q is not valid") if (p < 0) or (p > len(self.editor.doc.line(q))): # was: lines[q] raise ValueError("p is not valid") currentq = self.editor.mainwin.position()[2] def visibleOnScreen(q, p, currentq=currentq): screentop = self.editor.doc.startline screenbottom = min(screentop + self.editor.maxy, doclen) mapping = {} # @@ should be able to just compile for the line we're inspecting for linepos in xrange(screentop, screenbottom): schar = self.editor.doc.getStartchar(linepos) linelen = len(self.editor.doc.line(linepos)) # was: lines[linepos] epos = min(linelen - schar, self.editor.maxx + linelen) mapping[linepos] = (schar, epos) if mapping.has_key(q): spos, epos = mapping[q] if (p <= spos) and (p >= epos): return True return False if visibleOnScreen(q, p): y = q - self.editor.doc.startline x = p - self.editor.doc.getStartchar(q) self.editor.moveCursor(y, x) self.draw() return schar = self.editor.doc.getStartchar(q) if schar: p -= schar if ((q > self.editor.doc.startline) and (q < self.editor.doc.startline + self.editor.maxy - 2)): self.draw() self.editor.moveCursor(q - self.editor.doc.startline, p) elif q >= self.editor.maxy: self.editor.doc.startline = q self.editor.moveCursor(0, p) self.draw() # @@ order significant else: self.editor.doc.startline = 0 self.editor.moveCursor(q, p) self.draw() # @@ order significant def pageUp(self): for i in xrange(24): # @@ for now self.moveUp() def pageDown(self): for i in xrange(24): # @@ for now self.moveDown() def moveUp(self): # @@ doc this y, x, q, p = self.position() if q: # not the very first line of the doc if self.editor.doc.getStartchar(q): self.editor.doc.setStartchar(q, 0) # @@! self.drawline(y) if (self.editor.doc.startline < 1) or y: linelen = len(self.editor.doc.line(q - 1)) # was: lines[q - 1] nx = min(p, linelen, self.editor.maxx) self.editor.moveCursor(y - 1, nx) else: self.editor.doc.startline -= 1 self.draw() linelen = len(self.editor.doc.line(q - 1)) # was: lines[q - 1] nx = min(p, linelen, self.editor.maxx) self.editor.moveCursor(y, nx) def moveDown(self): # @@ doc this y, x, q, p = self.position() if q < (self.editor.doc.length() - 1): # was: len(...) if self.editor.doc.getStartchar(q): # 1) only if we can move down; b) to some sane value self.editor.doc.setStartchar(q, 0) # @@! self.drawline(y) if q < (self.editor.doc.length() - 1) and y < self.editor.maxy: # was... linelen = len(self.editor.doc.line(q + 1)) # was: lines[q + 1] nx = min(p, linelen, self.editor.maxx) self.editor.moveCursor(y + 1, nx) elif q < (self.editor.doc.length() - 1) and y == self.editor.maxy: # ... self.editor.doc.startline += 1 self.draw() # @@ why? moving off bottom of screen? linelen = len(self.editor.doc.line(q + 1)) # was: lines[q + 1] nx = min(p, linelen, self.editor.maxx) self.editor.moveCursor(y, nx) def moveLeft(self): # @@ doc this y, x, q, p = self.position() schar = self.editor.doc.getStartchar(q) if x: self.editor.moveCursor(y, x - 1) elif schar: start = max(0, schar - self.editor.maxx - 5) self.editor.doc.setStartchar(q, start) self.drawline(y) self.editor.moveCursor(y, min(p - 1, self.editor.maxx)) # elif y: # linelength = len(self.editor.doc.lines[q - 1]) # self.editor.moveCursor(y - 1, min(self.editor.maxx, linelength)) # elif self.editor.doc.startline: # # if at screen(0, 0) and there's a line above... elif (y or self.editor.doc.startline): self.moveUp() self.endOfLine() def moveRight(self): # @@ doc this y, x, q, p = self.position() schar = self.editor.doc.getStartchar(q) # Moving off the right hand side of the non-last line, onto the next line if (y < self.editor.maxy and p >= len(self.editor.doc.line(q)) and # was: lines[q] q < (self.editor.doc.length() - 1)): if schar: self.editor.doc.setStartchar(q, 0) # @@! self.drawline(y) self.editor.moveCursor(y + 1, 0) # Moving into a continuation zone elif (x >= self.editor.maxx and len(self.editor.doc.line(q)) > self.editor.maxx): # was: lines[q] self.editor.doc.setStartchar(q, add=(self.editor.maxx - 5)) self.drawline(y) self.editor.moveCursor(y, 5) # @@ what if it only goes one over? return True # Moving off the right hand side of the last line elif (q >= (self.editor.doc.length() - 1) and # was len(...) p >= len(self.editor.doc.line(q))): # was: lines[q] return False # order significant! # Moving off the right hand side of the last screen line elif (y >= self.editor.maxy and p >= len(self.editor.doc.line(q)) and # was: lines[q] q < (self.editor.doc.length() - 1)): # was: len(...) if schar: self.editor.doc.setStartchar(q, 0) # @@! self.drawline(y) self.moveDown() self.startOfLine() # Everything else else: self.editor.moveCursor(y, x + 1) return True def startOfLine(self): y, x, q, p = self.position() if self.editor.doc.getStartchar(q): # @@ make setStartchar del if the equals arg is 0 self.editor.doc.setStartchar(q, 0) self.drawline(y) self.editor.moveCursor(y, 0) def endOfLine(self): y, x, q, p = self.position() linelen = len(self.editor.doc.line(q)) # was: lines[q] if linelen > self.editor.maxx: self.editor.doc.setStartchar(q, linelen - 15) nx = 15 self.drawline(y) # significant order! # self.editor.moveCursor(y, nx) else: nx = linelen # self.editor.moveCursor(y, nx) self.editor.moveCursor(y, nx) def newLine(self): # @@ betterise this so that it only redraws # as much as it needs to y, x, q, p = self.position() if self.editor.doc.getStartchar(q): self.editor.doc.setStartchar(q, 0) # @@! self.drawline(y) self.editor.doc.insert(q, p, '\n') # @@ self.editor.doc.linesep? if y < self.editor.maxy: self.draw() self.editor.moveCursor(y + 1, 0) else: self.editor.doc.startline += 1 self.draw() self.editor.moveCursor(y, 0) def delChar(self): moved = self.moveRight() if moved: self.deleteChar() def deleteChar(self): y, x, q, p = self.position() # Delete in the middle of a line that we're showing more of # @@ Will be obsolete when disallowing a move into '<-' schar = self.editor.doc.getStartchar(q) if schar and (x == 0): # line = self.editor.doc.line(q) # was: lines[q] # self.editor.doc.lines[q] = line[:(p - 1)] + line[p:] self.editor.tav.delete((q, p - 1), (q, p)) start = max(0, schar - self.editor.maxx - 5) self.editor.doc.setStartchar(q, start) self.drawline(y) self.editor.moveCursor(y, min(p - 1, self.editor.maxx)) # Delete across the beginning of a line elif (x == 0) and (y > 0): # line = self.editor.doc.lines[q] # prev = self.editor.doc.lines[q - 1] # self.editor.doc.lines[q - 1] += line # del self.editor.doc.lines[q] # # @@ The above is probably more efficient prevLength = len(self.editor.doc.line(q - 1)) # was: lines[q - 1] self.editor.doc.delete((q - 1, prevLength), (q, p)) if prevLength > self.editor.maxx: self.editor.doc.setStartchar(q - 1, prevLength - 5) nx = 5 else: nx = prevLength self.draw() self.editor.moveCursor(y - 1, nx) # Deleting at the top left pos of the screen elif (x == 0) and (y == 0) and self.editor.doc.startline: # @@ note the minimal differences to the clause above # line = self.editor.doc.lines[q] # prev = self.editor.doc.lines[q - 1] # self.editor.doc.lines[q - 1] += line # del self.editor.doc.lines[q] prevLength = len(self.editor.doc.line(q - 1)) # was: lines[q - 1] self.editor.doc.delete((q - 1, prevLength), (q, p)) if prevLength > self.editor.maxx: self.editor.doc.setStartchar(q - 1, len(prev) - 5) nx = 5 else: nx = len(prev) self.editor.doc.startline -= 1 self.draw() self.editor.moveCursor(y, nx) # Delete everywhere else but the top left of the *screen* @@! elif y or x: # line = self.editor.doc.lines[q] # self.editor.doc.lines[q] = line[:(p - 1)] + line[p:] self.editor.doc.delete((q, p - 1), (q, p)) # self.drawline(y) # @@ grumble: @@ why is this? because of deleting across newlines? # @@ hehe, mini-conversation with self. like Morbus self.draw() self.editor.moveCursor(y, x - 1) def selectFrom(self): # Ctrl+D to start selection of a region if not self.editor.doc.selection: y, x, q, p = self.position() self.editor.doc.selection = [[q, p], [q, p]] else: self.selectTo(warn=True) def selectTo(self, warn=False): self.editor.doc.selection = [] self.draw() if warn: self.editor.statwin.notice('Cancelled selection...') def updateSelection(self): y, x, q, p = self.position() if y < (self.editor.maxy + 1): self.editor.doc.selection[1] = [q, p] self.draw() # @@ only draw in mainwin focus mode? def getEvent(self, e): if self.mappings.has_key(e): self.mappings[e]() result = True else: result = False if self.editor.doc.selection: self.updateSelection() if (self.editor.focus is self.editor.mainwin): self.editor.statwin.default() return result def event(self, e): self.typed = True self.event = self.getEvent return self.event(e) if __name__=="__main__": main()