Commits

Henning Schröder committed 23ce977

Initial import into bzr repository

Comments (0)

Files changed (14)

+*.pyc
+*~

codeaide/__init__.py

Empty file added.
+import re
+
+from PyQt4.QtCore import (
+    Qt, QString, SIGNAL, QVariant, QThread, QLine, QSize, QRect,
+    QAbstractListModel, QModelIndex)
+from PyQt4.QtGui import (
+    QApplication, QWidget, QHBoxLayout, QToolTip,
+    QTextBlockUserData, QPlainTextEdit, QCompleter, QTextCursor, 
+    QColor, QBrush, QPainter, QPen, QPalette)
+
+
+
+def command(func):
+    """
+    decorator to declare a method as an editor command
+    """
+    func.is_command = True
+    return func
+
+
+
+def class_members(cls):
+    names = dir(cls)
+    if hasattr(cls,'__bases__'):
+        for base in cls.__bases__:
+            names.extend(class_members(base))
+    return list(set(names))
+
+
+def members(obj):
+    names = dir(obj)
+    cls = getattr(obj, "__class__")
+    names.extend(class_members(cls))
+    return list(set(names))
+
+    
+
+class ModeBase(object):
+
+    keyboard_hooks = {
+        }
+    paint_hook = None
+    resize_hook = None
+    state = 0
+    name = None
+    requires = []
+
+
+    
+    @classmethod
+    def find(cls, mode):
+        for subcls in cls.__subclasses__():
+            if isinstance(mode, basestring):
+                if subcls.__name__ == mode:
+                    return subcls
+            else:
+                if subcls is mode:
+                    return subcls
+            found = subcls.find(mode)
+            if found:
+                return found
+
+
+    def __init__(self, textedit, *args, **kwargs):
+        for required_mode in self.requires:
+            textedit.install(required_mode)
+        self.textedit = textedit
+        self.init(*args, **kwargs)
+        self.install_hooks()
+
+
+    def init(self):
+        pass
+
+
+    def install_hooks(self):
+        self.install_keyboard_hooks()
+        self.install_paint_hook()
+        self.install_resize_hook()
+
+
+    def install_keyboard_hooks(self):
+        import functools
+        for key, callback in self.keyboard_hooks.items():
+            if isinstance(key, tuple):
+                key, modifiers = key
+            else:
+                modifiers = -1
+            h = self.textedit.keyboard_hooks.setdefault(self.state, {})
+            bound_method = functools.partial(callback, self)
+            h.setdefault((key, int(modifiers)), []).append(bound_method)
+
+
+    def install_paint_hook(self):
+        callback = self.paint_hook
+        if callback:
+            bound_method = callback
+            #bound_method = \
+            #    lambda *args, **kwargs: callback(self, *args, **kwargs)
+            h = self.textedit.paint_hooks
+            h.setdefault(self.state, []).append(bound_method)
+
+        
+    def install_resize_hook(self):
+        callback = self.resize_hook
+        if callback:
+            bound_method = callback
+            h = self.textedit.resize_hooks
+            h.setdefault(self.state, []).append(bound_method)

codeaide/modes/__init__.py

+import sys
+import os
+import re
+
+# XXX: importing from a zip package does not work
+
+modes = []
+
+def find():
+    global modes
+    modes = []
+    #path = os.path.dirname(os.path.abspath(__file__))
+    for path in __path__:
+        for name in os.listdir(path):
+            if re.match("^[A-Za-z][A-ZA-z0-9]*\.py$", name):
+                modes.append(os.path.splitext(name)[0])
+
+                
+def add_path(path):
+    __path__.insert(0, path)
+    
+        
+def load(name):
+    mod = __import__("codeaide.modes.%s" % name, {}, {}, [])
+    globals()[name] = mod
+    return mod
+
+
+def load_all(verbose=False):
+    if not modes:
+        find()
+    for name in modes:
+        if verbose:
+            print >> sys.stderr, "Loading mode %r" % name
+        load(name)

codeaide/modes/autotyping.py

+from codeaide.base import *
+
+class AutoTyping(ModeBase):
+
+
+    def count_paren(self, cursor, open_char, close_char):
+        # XXX: Only counts on the current line. Is this a good idea?
+        depth = 0
+        line = unicode(cursor.block().text())
+        col = cursor.columnNumber()
+        for i, char in enumerate(line):
+            if char == open_char:
+                depth += 1
+            elif char == close_char and \
+              ((depth > 0) or (col <= i)):
+                depth -= 1
+        return depth
+
+
+    def append_close_char(self, event, key, modifiers, text):
+        """
+        append close char and put cursor before close char
+        """
+        char = {
+            Qt.Key_ParenLeft: u")",
+            Qt.Key_BraceLeft: u"}",
+            Qt.Key_BracketLeft:u"]",
+            Qt.Key_Apostrophe: u"'",
+            Qt.Key_QuoteDbl: u'"'
+            }
+        close_char = char[key]
+        open_char = unicode(text)
+        cursor = self.textedit.textCursor()
+        if self.count_paren(cursor, text, close_char) <= -1:
+            return False
+        cursor.insertText(text)
+        cursor.insertText(char[key])
+        cursor.setPosition(cursor.position() - 1)
+        self.textedit.setTextCursor(cursor)
+        return True
+
+
+    def close_char_pressed(self, event, key, modifiers, text):
+        char = {
+            Qt.Key_ParenRight: u"(",
+            Qt.Key_BraceRight: u"{",
+            Qt.Key_BracketRight:u"[",
+            }
+        open_char = char[key]
+        close_char = unicode(text)
+        cursor = self.textedit.textCursor()
+        line = cursor.block().text()
+        cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor)
+        cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
+        seltext = unicode(cursor.selectedText())
+        if seltext == open_char + close_char:
+            if self.count_paren(cursor, open_char, close_char) < 1:
+                event.ignore()
+                return True
+        return False
+
+
+    def append_space(self, event, key, modifiers, text):
+        """
+        Normally you add a space character after commas, etc.
+        """
+        cursor = self.textedit.textCursor()
+        cursor.insertText(text)
+        cursor.insertText(" ")
+        self.textedit.setTextCursor(cursor)
+        return True
+
+
+    def expand_after_space(self, event, key, modifieres, text):
+        cursor = self.textedit.textCursor()
+        line = unicode(cursor.block().text())
+        col = cursor.columnNumber()
+        match = re.compile("^\s*from\s+[A-Za-z_0-9]+\s*$").match
+        head, tail = (line[:col], line[col:])
+        if match(head):
+            cursor.insertText(" import ")
+        self.textedit.setTextCursor(cursor)
+    
+
+
+    keyboard_hooks = {
+        Qt.Key_ParenLeft: append_close_char,
+        Qt.Key_BraceLeft: append_close_char,
+        Qt.Key_BracketLeft: append_close_char,
+        Qt.Key_Apostrophe: append_close_char,
+        Qt.Key_QuoteDbl: append_close_char,
+
+        Qt.Key_ParenRight: close_char_pressed,
+        Qt.Key_BraceRight: close_char_pressed,
+        Qt.Key_BracketRight: close_char_pressed,
+
+        Qt.Key_Comma: append_space,
+
+        Qt.Key_Space: expand_after_space,
+        }
+
+

codeaide/modes/completer.py

+from codeaide.base import *
+
+
+class CompleterModel(QAbstractListModel):
+
+
+    def __init__(self):
+        QAbstractListModel.__init__(self)
+        self.items = ["Foo", "Bar", "Bla", "Blub"]
+
+
+    def data(self, index, role):
+        if index.isValid() and role in (Qt.DisplayRole, Qt.EditRole):
+            return QVariant(self.items[index.row()])
+        else:
+            return QVariant()
+
+
+    def rowCount(self, parent=QModelIndex()):
+        return len(self.items)
+
+
+
+class CompleterBase(ModeBase):
+    """
+    Autocompletion based on "Custom Completer Example" in Qt docs 
+    and looking at the qbzr implementation.
+    """
+
+
+    def init(self, eow="~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=", 
+                 min_length=2, sensitivity=Qt.CaseSensitive):
+        self.widget = QCompleter(self.textedit)
+        self.eow = QString(eow)
+        self.min_length = min_length
+
+        self.widget.setWidget(self.textedit)
+        self.widget.setCaseSensitivity(sensitivity)
+        self.widget.connect(self.widget, SIGNAL("activated(QString)"), self.insertCompletion)
+        self.model = CompleterModel()
+        self.widget.setModel(self.model)
+        self.init_completer()
+
+
+    def init_completer(self):
+        pass
+
+
+    @command
+    def textUnderCursor(self):
+        tc = self.textedit.textCursor()
+        tc.select(QTextCursor.WordUnderCursor)
+        return tc.selectedText()
+
+
+    @command
+    def insertCompletion(self, completion):
+        tc = self.textedit.textCursor()
+        c = self.widget
+        extra = completion.length() - c.completionPrefix().length()
+        tc.movePosition(QTextCursor.Left)
+        tc.movePosition(QTextCursor.EndOfWord)
+        tc.insertText(completion.right(extra))
+        self.textedit.setTextCursor(tc)
+
+
+    def check_popup_visible(self, event, key, modifiers, text):
+        if self.widget.popup().isVisible():
+            # key will be handled by completer popup
+            event.ignore()
+            return True
+
+
+    def shortcut_pressed(self, event, key, modifiers, text):
+        ctrlOrShift = modifiers & (Qt.ControlModifier | Qt.ShiftModifier)
+        if ctrlOrShift and text.isEmpty():
+            print "transparency switched on"
+            self.widget.popup().setWindowOpacity(0.5)
+            return False
+
+        c = self.widget
+        hasModifier = (modifiers != Qt.NoModifier) and not ctrlOrShift
+        completionPrefix = self.textUnderCursor()
+        if  (hasModifier or text.isEmpty()
+             or self.eow.contains(text.right(1)) or 
+             completionPrefix.length() < self.min_length):
+            c.popup().hide()
+            return False
+
+        if completionPrefix != c.completionPrefix():
+            c.setCompletionPrefix(completionPrefix)
+            c.popup().setCurrentIndex(c.completionModel().index(0, 0))
+
+        cr = self.textedit.cursorRect()
+        cr.setWidth(c.popup().sizeHintForColumn(0) +
+                    c.popup().verticalScrollBar().sizeHint().width())
+        c.complete(cr)
+        return True
+
+
+    keyboard_hooks = {
+        Qt.Key_Enter: check_popup_visible,
+        Qt.Key_Return: check_popup_visible,
+        Qt.Key_Escape: check_popup_visible,
+        Qt.Key_Tab: check_popup_visible,
+        Qt.Key_Backtab: check_popup_visible,
+        (Qt.Key_Space, Qt.ControlModifier): shortcut_pressed,
+        }
+
+
+
+class CompleterThread(QThread):
+
+
+    def __init__(self, completer, interval=2):
+        QThread.__init__(self)
+        self.completer = completer
+        self.textedit = completer.textedit
+        self.interval = interval
+
+
+    def run(self):
+        completer = self.completer
+        #popup = completer.widget.popup()
+        while True:
+            if True: #not popup.isVisible():
+                words = self.find_completitions(
+                    unicode(self.textedit.toPlainText()))
+                completer.model.items = words
+            self.sleep(self.interval)
+
+
+
+
+split_words = lambda s: re.split(re.compile("[^\w_]+", re.UNICODE), s)
+
+
+class TextCompleterThread(CompleterThread):
+
+
+    def find_completitions(self, text):
+        words = set(split_words(text))
+        return sorted([w for w in words if len(w) > 2])
+
+
+    
+
+class TextCompleter(CompleterBase):
+
+
+    def init_completer(self):
+        self.thread = TextCompleterThread(self)
+        self.thread.start()
+
+

codeaide/modes/folding.py

+from codeaide.base import *
+from codeaide.modes.sidebar import SidebarModeBase
+
+
+class FoldingWidget(QWidget):
+
+    
+    def __init__(self, parent):
+        QWidget.__init__(self, parent)
+
+
+    def paintEvent(self, event):
+        painter = QPainter(self)
+        b = QBrush(QColor("white"))
+        painter.fillRect(event.rect(), b)
+        return QWidget.paintEvent(self, event)
+
+
+
+    def sizeHint(self):
+        return QSize(20, 0)
+
+
+
+class Folding(SidebarModeBase):
+
+
+    def init_sidebar(self):
+        self.widget = FoldingWidget(self.textedit)
+        self.sidebar_widget.add_widget(self.widget)
+        

codeaide/modes/indenter.py

+from codeaide.base import *
+
+
+class IndenterBase(ModeBase):
+
+
+    @command
+    def correct_indentation(self, line_number=-1):
+        if line_number == -1:
+            cursor = self.textedit.textCursor()
+            line_number = cursor.blockNumber()
+
+
+    @command
+    def entering_new_line(self, line_number=-1):
+        if line_number == -1:
+            cursor = self.textedit.textCursor()
+            line_number = cursor.blockNumber()
+
+
+
+    @command
+    def dedent(self):
+        cursor = self.textedit.textCursor()
+        text = unicode(cursor.block().text())
+        col = cursor.columnNumber()
+        if col > 0 and text[:col].strip() == "":
+            line_number = self.textCursor().blockNumber()
+            return True
+
+
+
+    @command
+    def indent(self):
+        self.indenter.correct_indentation()
+
+
+
+    keyboard_hooks = {
+        Qt.Key_Return: indent, 
+        Qt.Key_Enter:  indent,
+        Qt.Key_Tab: indent,
+        Qt.Key_Backspace: dedent,
+        Qt.Key_Backtab: dedent,
+        }

codeaide/modes/lines.py

+from codeaide.base import *
+
+
+class VerticalLine(ModeBase):
+
+
+    def init(self):
+        self.width = 78
+        self.x = self.textedit.fontMetrics().width("W"*78)
+        self.color = QColor("lightgrey")
+        self.pen = QPen(self.color)
+        self.brush = QBrush(QColor(self.color.lighter(115)))
+
+
+    def resize_hook(self, event):
+        cr = self.textedit.contentsRect()
+        y = cr.top()
+        x = cr.left() + self.x
+        h = cr.height()
+        w = cr.width()
+        self.line = QLine(x, y, x, h)
+        self.rect = QRect(x+1, y, w, h)
+
+    
+    def paint_hook(self, event, painter, viewport):
+        rect = event.rect()
+        if rect.right() < self.x:
+            return
+        painter.setPen(self.pen)
+        painter.drawLine(self.line)
+        painter.fillRect(self.rect, self.brush)
+
+
+class HighlightLine(ModeBase):
+
+
+    def init(self):
+        #color = self.textedit.palette().highlight().color().lighter(175)
+        color = QColor("blue").lighter(190)
+        self.brush = QBrush(QColor(color))
+        self.textedit.connect(self.textedit, 
+                              SIGNAL("cursorPositionChanged()"),
+                              self.update)
+        self.old_row = -1
+
+
+    def update(self):
+        cursor = self.textedit.textCursor()
+        row = cursor.blockNumber()
+        if row != self.old_row:
+            self.textedit.viewport().update()
+            self.old_row = row
+
+
+    def paint_hook(self, event, painter, viewport):
+        r = self.textedit.cursorRect()
+        r.setX(0)
+        r.setWidth(viewport.width())
+        painter.fillRect(r, self.brush)

codeaide/modes/sidebar.py

+from codeaide.base import *
+
+
+
+class SidebarWidget(QWidget):
+
+
+    def __init__(self, textedit):
+        QWidget.__init__(self, textedit)
+        self.setContentsMargins(0, 0, 0, 0)
+        self.textedit = textedit
+        palette = QPalette()
+        palette.setColor(self.backgroundRole(), QColor("lightgrey"))
+        self.setPalette(palette)
+        self.hblayout = QHBoxLayout(self)
+        self.hblayout.setContentsMargins(0, 0, 0, 0)
+        self.hblayout.setSpacing(0)
+        self.setLayout(self.hblayout)
+
+
+    def add_widget(self, widget):
+        widget.setContentsMargins(0, 0, 0, 0)
+        self.layout().addWidget(widget)        
+        width = self.sizeHint().width()
+        self.setFixedWidth(width)
+        self.textedit.setViewportMargins(width, 0, 0, 0)
+
+
+
+
+class Sidebar(ModeBase):
+
+    
+    def init(self, SidebarWidgetClass=SidebarWidget):
+        self.widget = SidebarWidgetClass(self.textedit)
+        self.fm = self.textedit.fontMetrics()
+        self.blocks_changed(0)
+        self.textedit.connect(
+            self.textedit, SIGNAL("blockCountChanged(int)"), 
+                self.blocks_changed)
+        self.textedit.connect(self.textedit.verticalScrollBar(),
+                   SIGNAL("valueChanged(int)"),
+                   self.blocks_changed)
+
+
+    def blocks_changed(self, num):
+        lines = []
+        te = self.textedit
+        block = te.firstVisibleBlock()
+        row = block.blockNumber() + 1
+        width = self.widget.width()
+        w  = width -2
+        h = self.fm.height()
+
+        bbgeom = te.blockBoundingGeometry(block)
+        top = bbgeom.translated(te.contentOffset()).top()
+        bottom = top + bbgeom.height()
+        ebottom_top = 0 #event.rect().top()
+        ebottom_bottom = self.widget.height() #event.rect().bottom()
+        while block.isValid() and top <= ebottom_bottom:
+            if block.isVisible() and bottom >= ebottom_top:
+                lines.append(
+                   ((row, block), (0, top, w, h))
+                   )
+            block = block.next()
+            row += 1
+            top = bottom
+            bottom = top + te.blockBoundingRect(block).height()
+        self.lines = lines
+        self.widget.emit(SIGNAL("updateSidebar()"))
+
+
+    def resize_hook(self, event):
+        cr = self.textedit.contentsRect()
+        self.widget.setGeometry(
+            cr.left(), cr.top(), self.widget.width(), cr.height())
+        self.widget.emit(SIGNAL("updateSidebar()"))
+
+
+
+class SidebarModeBase(ModeBase):
+
+    requires = [Sidebar]
+
+
+    def init(self):
+        self.sidebar = self.textedit.find_mode(Sidebar)
+        self.sidebar_widget = self.sidebar.widget
+        self.init_sidebar()
+
+
+    def init_sidebar(self):
+        pass
+
+
+
+
+
+class LineNumbersWidget(QWidget):
+
+    
+    def __init__(self, mode):
+        QWidget.__init__(self, mode.textedit)
+        self.sidebar = mode.sidebar
+        mode.sidebar_widget.connect(
+            mode.sidebar_widget, SIGNAL("updateSidebar()"), self.update)
+        self.textpen = QPen(QColor(0x80, 0x80, 0x80))
+
+
+    def sizeHint(self):
+        return QSize(self.parent().fontMetrics().width("W") * 4, 0)
+
+
+    def paintEvent(self, event):
+        painter = QPainter(self)
+        painter.setPen(self.textpen)
+        w = self.width()
+        for (row, block), (left, top, width, height) in self.sidebar.lines: 
+            painter.drawText(
+                    0, top, w, height, Qt.AlignRight, str(row))
+        return QWidget.paintEvent(self, event)
+
+
+
+
+class LineNumbers(SidebarModeBase):
+
+
+    def init_sidebar(self):
+        self.widget = LineNumbersWidget(self)    
+        self.sidebar_widget.add_widget(self.widget)
+
+
+
+
+class LineStatusWidget(QWidget):
+
+
+    def __init__(self, mode):
+        self.sidebar = mode.sidebar
+        self.sidebar_widget = mode.sidebar_widget
+        self.textedit = mode.textedit
+        QWidget.__init__(self, self.textedit)
+        self.modified_pen = QPen(QColor("red"))
+        self.textedit.connect(self.textedit, SIGNAL("textChanged()"), 
+            self.on_text_changed)
+        self.setMouseTracking(True)
+        self.sidebar_widget.connect(
+            self.sidebar_widget, SIGNAL("updateSidebar()"), self.update)
+
+
+    def mouseMoveEvent(self, event):
+        pos = event.pos()
+        y = pos.y()
+        for (row, block), (left, top, width, height) in self.sidebar.lines:
+            if y > top and y < top+height:
+                user_data = block.userData()
+                if not user_data:
+                    return
+                backup = user_data.get("backup")
+                if backup is None:
+                    return
+                QToolTip.showText(
+                    self.mapToGlobal(pos), backup, self)
+                
+
+
+    def on_text_changed(self):
+        # XXX: does not fully work with selecions
+        cursor = self.textedit.textCursor()
+        block = cursor.block()
+        previous = block.previous()
+        if previous and previous.isValid():
+            pud = self.textedit.user_data(previous)
+            if pud.get("backup") != previous.text():
+                pud["status"] = 1
+        user_data = self.textedit.user_data(block)
+        user_data["status"] = 1
+        self.update()   
+
+
+    def sizeHint(self):
+        return QSize(2, 0)
+
+
+    def paintEvent(self, event):
+        painter = QPainter(self)
+        painter.setPen(self.modified_pen)
+        for (row, block), (left, top, width, height) in self.sidebar.lines:
+            user_data = block.userData()
+            if not user_data or user_data["status"] == 1:
+                painter.drawLine(left, top, left, top+height)
+                painter.drawLine(left+1, top, left+1, top+height)
+        return QWidget.paintEvent(self, event)
+
+
+
+
+class LineStatus(SidebarModeBase):
+    
+    
+    def init_sidebar(self):
+        self.widget = LineStatusWidget(self)
+        self.sidebar.widget.add_widget(self.widget)
+

codeaide/modes/snippets.py

+import re
+
+
+from codeaide.base import *
+
+
+check_word = re.compile("^[^A-Za-z]*([A-Za-z]+)$").match
+
+snippet_definitions = {
+    "class": """class ${1:name}($2}:
+  def __init__(self, $3)
+"""
+}
+
+class Snippets(ModeBase):
+
+
+
+    def insert_snippet(self, event, key, modifiers, text):
+        cursor = self.textedit.textCursor()
+        block= cursor.block()
+        text = unicode(block.text())
+        col = block.position()
+        found = check_word(text[:col])
+        if found:
+            print found.groups()
+            self.textedit.setTextCursor(cursor)
+            return True
+
+
+    keyboard_hooks = {
+        Qt.Key_Tab: insert_snippet
+        }
+

codeaide/widget.py

+from codeaide.base import *
+
+
+class TextBlockUserData(QTextBlockUserData):
+    
+    def __init__(self):
+        QTextBlockUserData.__init__(self)
+        self.attrs = {}
+        
+        
+    def __getitem__(self, name):
+        return self.attrs[name]
+        
+        
+    def get(self, name, default=None):
+        return self.attrs.get(name, default)
+        
+        
+    def __setitem__(self, name, value):
+        self.attrs[name] = value
+
+
+
+
+class CodeAide(QPlainTextEdit):
+
+
+
+    def __init__(self, parent=None):
+        QPlainTextEdit.__init__(self, parent)
+        self.setFrameStyle(0)
+        #self.setBackgroundVisible(True)
+        self.set_font()
+        self.state = 0
+        self.keyboard_hooks = {}
+        self.paint_hooks = {}
+        self.resize_hooks = {}
+        self.installed_modes = []
+
+
+    def set_font(self, name="Monospace", size=10):
+        font = self.document().defaultFont()
+        font.setPointSize(10)
+        font.setFamily("Monospace")
+        font.setFixedPitch(True)
+        self.document().setDefaultFont(font)
+        self.setFont(font)
+
+
+    def find_mode(self, cls):
+        for mode in self.installed_modes:
+            if isinstance(mode, cls):
+                return mode
+
+
+
+    def is_installed(self, mode):
+        for installed in self.installed_modes:
+            if installed.__class__ is mode:
+                return True
+        return False
+
+
+    def install(self, *mode_args):
+        for mode in mode_args:
+            mode = ModeBase.find(mode)
+            if not self.is_installed(mode):
+                mode_instance = mode(self)
+                self.installed_modes.append(mode_instance)
+
+
+    @property
+    def commands(self):
+        c = {}
+        for mode in self.installed_modes:
+            for name in members(mode):
+                if not name.startswith("_"):
+                    obj = getattr(mode, name)
+                    if getattr(obj, "is_command", False):
+                        c[name] = obj
+        return c
+
+
+    def __call__(self, cmd, *args, **kwargs):
+        method = self.commands.get(cmd)
+        if not method:
+            raise TypeError, "Method %r not found" % cmd
+        return method(*args, **kwargs)
+
+
+    def paintEvent(self, event):
+        viewport = self.viewport()
+        painter = QPainter(viewport)
+        h = self.paint_hooks
+        for callback in \
+                h.get(self.state, []) + h.get(-1, []):
+            if callback(event, painter, viewport):
+                return
+        painter.end()
+        return QPlainTextEdit.paintEvent(self, event)
+
+
+    def keyPressEvent(self, event):
+        key = event.key()
+        text = event.text()
+        modifiers = event.modifiers()
+        h = self.keyboard_hooks.get(self.state, {})
+        for callback in \
+                h.get((key, int(modifiers)), []) + \
+                h.get((key, -1), []):
+            if callback(event, key, modifiers, text):
+                return
+        return QPlainTextEdit.keyPressEvent(self, event)
+
+
+    def resizeEvent(self, event):
+        h = self.resize_hooks
+        for callback in \
+                h.get(self.state, []) + h.get(-1, []):
+            if callback(event):
+                return
+        return QPlainTextEdit.resizeEvent(self, event)
+
+
+    def user_data(self, block=None):
+        if not block:
+            block = self.textCursor().block()
+        user_data = block.userData()
+        if not user_data:
+            user_data = TextBlockUserData()
+            block.setUserData(user_data)
+        return user_data
+
+    
+    def mark_unmodified(self):
+        block = self.document().begin()
+        while block and block.isValid():
+            user_data = self.user_data(block)
+            user_data["status"] = 0
+            user_data["backup"] = block.text()
+            block = block.next()
+
+
+    def setText(self, text):
+        self.setPlainText(text)
+        self.mark_unmodified()
+
+
+
+#!/bin/sh
+files=$(find -name "*.py")
+echo $files
+emacs $files
+
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from codeaide.base import *
+from codeaide.widget import *
+import codeaide.modes
+
+
+codeaide.modes.load_all(verbose=True)
+
+
+
+if __name__ == "__main__":
+    import sys
+    app = QApplication(sys.argv)
+    edit = CodeAide()
+    src = open(__file__).read()
+    edit.setText(src)
+    edit.install(
+        "AutoTyping", 
+        "VerticalLine",
+        "HighlightLine",
+        "LineNumbers",  # installs Sidebar as a dependency
+        "LineStatus",
+        "Folding",
+        "Snippets",
+        "TextCompleter",
+        )
+    edit.resize(800,600)
+    edit.show()
+    app.exec_()
+