Source

codeaide / codeaide / modes / python / main.py

Full commit
# -*- coding: utf-8 -*-
import re

from codeaide.base import *
from codeaide.modes.language.completer import BackgroundCompleterBase
from codeaide.modes.language.indenter import IndenterBase
from codeaide.modes.language.checker import CheckerBase, ExternalCheckerBase
from codeaide.modes.language.folding import IndentionFoldingBase
from codeaide.modes.language.outline import OutlineBase


try:
    from rope_interface import CodeAssist, Indenter, Outline, SourceChecker, CallTip
except ImportError, e:
    print "Warning: rope package not available: %s\n Using dummy instead" % e
    from dummy_interface import CodeAssist, Indenter, Outline, SourceChecker, CallTip



class PythonCompleter(BackgroundCompleterBase):


    def init_completer(self):
        self.complete = CodeAssist()




class PythonOutline(OutlineBase):


    def init_outline(self):
        self.do_outline = Outline()


    def update_structure(self):
        src = unicode(self.textedit.toPlainText())
        nodes = self.do_outline(src)
        return nodes



class PythonIndenting(IndenterBase):
    
    
    def init_indenter(self):
        self.indenter = Indenter(self.textedit)


    def indent_line(self, num):
        self.indenter.indent(num)
        return True


    def dedent_line(self, num):
        self.indenter.dedent(num)
        return True


    def on_tab(self, *args):
        self.indenter.correct_indentation(self.current_line_number())
        return True


    def on_newline(self, *args):
        self.textedit.insertPlainText("\n")
        self.indenter.entering_new_line(self.current_line_number())
        return True



class PythonFolding(IndentionFoldingBase):

    is_foldable = re.compile("\s*(def|class|with|if|for|while).*:").match



import compiler


class PythonChecker(CheckerBase):


    def check(self):
        te = self.textedit
        src = unicode(te.toPlainText())
        try:
            ast = compiler.parse(src)
        except SyntaxError, e:
            row, col = (e.lineno-1, e.offset-1)
            length = len(e.text)
            msg = e.msg
            return [ ((row, col, length), msg, True) ]
        try:
            from pyflakes.checker import Checker
        except ImportError:
            return []
        l = []
        try:
            filename = te.document().filename
        except AttributeError, _err:
            filename = "__unknown__.py"
        c = Checker(ast, filename)
        c.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
        for msg in c.messages:
            args = msg.message_args
            l.append( ((msg.lineno-1, 0, len(args[0])), msg.message % args, False) )
        return l


    
class PylintChecker(CheckerBase):

    
    def check(self):
        temp = self.create_tempfile()
        import os
        os.system("pylint %r" % temp.name)
        from pylint import epylint
        (std_out, std_err) = epylint.py_run("-fparseable -rno %r" % temp.name, True)
        import time
        output = std_out.read()
        for found in self.parse_regex("(?P<filename>[^:]*):(?P<row>[^:]):(?P<message>.*)", output):
            print found
        return []



indention_match = re.compile("[\t ]*").match
        
def indention(s):
    return indention_match(s).end()

def block_indention(block):
    return indention(unicode(block.text()))

     

class PythonCallTip(ModeBase):

    def install_special_hooks(self):
        self.install_hook(
           self.textedit.keyboard_hooks, self.show_calltip, (Qt.Key_ParenLeft,-1), state=0, priority=True)


    def format_call(self, call):
        call, sep, args = call.partition("(")
        args, sep, rest = args.rpartition(")")
        alist = []
        for a in args.split(","):
            k, sep, v = a.partition("=")
            s = "<b>%s</b>" % k.strip()
            if v:
                s += "=<i>%s</i>" % v.replace(" ", "&nbsp").replace("<", "&lt;").replace(">", "&gt;")
            alist.append(s)
        if alist and alist[0] == "<b>self</b>":
            alist[0] = '<font color="#909090">self</font>'
        args = ",&nbsp;".join(alist)
        tt = "%s(%s)%s" % (call, args, rest)
        return tt
    
    
    def show_tooltip(self, pos, text, parent=None):
        QToolTip.showText(pos, text, parent)


    def show_calltip(self, *args):
        c = CallTip()
        r = self.textedit.cursorRect()
        r.setX(r.x() + self.textedit.viewport().x())
        src = unicode(self.textedit.toPlainText())
        pos = self.textedit.textCursor().position()
        try:
            tt = c(src, pos-1)
        except Exception, e:
            tt = None
        if tt:
            tt = self.format_call(tt)
            self.show_tooltip(self.textedit.mapToGlobal(r.topLeft()), tt, self.textedit)



class PythonEditing(ModeBase):

    
    def keyboard_hooks(self):
        return {
            Qt.Key_Space: self.space_after_import
            }
    
    
    def _start_of_block(self, cursor=None):
        # XXX: bad implementation, better use the one from rope
        cursor = cursor or self.textedit.textCursor()
        block = cursor.block()
        count = block_indention(block)
        while block and block.isValid():
            if block_indention(block) < count:
                break
            block = block.previous()
        if block and block.isValid():
            return block.position() + block_indention(block)
    

    def _end_of_block(self, cursor=None):
        # XXX: bad implementation, better use the one from rope
        cursor = cursor or self.textedit.textCursor()
        block = cursor.block()
        count = block_indention(block)
        while block and block.isValid():
            if block_indention(block) < count:
                break
            block = block.next()
        return block and block.isValid() and block.position()


    @command("Goto definition", context_menu=True)
    def goto_definition(self):
        pass

    

    @command("Select block", shortcut="Ctrl+U")
    def select_block(self):
        cursor  = TextCursor(self.textedit.textCursor())
        start_pos = self._start_of_block(cursor)
        end_pos = self._end_of_block(cursor)
        if start_pos != None and end_pos != None:
            cursor.select_region(start_pos, end_pos)
            self.textedit.setTextCursor(cursor)
            

    @command("Jump to start of block", shortcut="Ctrl+[")
    def jump_start(self):
        cursor  = TextCursor(self.textedit.textCursor())
        start_pos = self._start_of_block(cursor)
        if start_pos != None:
            self.textedit.set_position(start_pos)

            
    @command("Jump to end of block", shortcut="Ctrl+]")
    def jump_end(self):
        cursor  = TextCursor(self.textedit.textCursor())
        end_pos = self._end_of_block(cursor)
        if end_pos != None:
            self.textedit.set_position(end_pos-1)



    @command("Comment", shortcut="Ctrl+#", context_menu=True)
    def comment(self):
        for block in TextCursor(self.textedit.textCursor()).iter_blocks():
            cursor = TextCursor(block)
            #cursor.joinPreviousEditBlock()
            cursor.insertText("## ")


            
    @command("Uncomment", shortcut="Ctrl+AltX+#", context_menu=True)
    def uncomment(self):
        for block in TextCursor(self.textedit.textCursor()).iter_blocks():
            text = unicode(block.text())
            cursor = TextCursor(block)
            while text.startswith("#"):
                cursor.deleteChar()
                text = unicode(block.text())
                
                
    dotted_name_re = "([A-Za-z_][A-Za-z_0-9\.]*)" # XXX wrong
    match_insert = [
            (re.compile("^\s*from\s+%s\s*$" % dotted_name_re).match, " import "),
            (re.compile("^\s*import\s+%s+\s*$" % dotted_name_re).match, " as "),
            ]

    def space_after_import(self, event, key, modifieres, text):
        cursor = self.textedit.textCursor()
        line = unicode(cursor.block().text())
        col = cursor.columnNumber()
        for match, insert in self.match_insert:
            head, tail = (line[:col], line[col:])
            if match(head) and not tail.strip():
                cursor.insertText(insert)
                self.textedit.setTextCursor(cursor)
                return True
    


class PythonInteraction(ModeBase):
    
        
    #@command("Run source in Python interpreter", shortcut="Ctrl+R",
    #         context_menu=True)
    #def run(self):
    #    print "run called"


    TODO = """
C-j                py-newline-and-indent
RET                py-newline-and-indent
C-x                Prefix Command
ESC                Prefix Command
:                py-electric-colon
DEL                py-electric-backspace

C-x n            Prefix Command

C-M-a            py-beginning-of-def-or-class
C-M-e            py-end-of-def-or-class
C-M-h            py-mark-def-or-class
C-M-x            py-execute-def-or-class

C-c C-b            py-submit-bug-report
C-c C-c            py-execute-buffer
C-c C-d            py-pdbtrack-toggle-stack-tracking
C-c C-h            py-help-at-point
C-c TAB            py-indent-region
C-c C-k            py-mark-block
C-c C-l            py-shift-region-left
C-c RET            py-execute-import-or-reload
C-c C-n            py-next-statement
C-c C-p            py-previous-statement
C-c C-r            py-shift-region-right
C-c C-s            py-execute-string
C-c C-t            py-toggle-shells
C-c C-u            py-goto-block-up
C-c C-v            py-version
C-c C-w            py-pychecker-run
C-c !            py-shell
C-c #            py-comment-region
C-c -            py-up-exception
C-c :            py-guess-indent-offset
C-c <            py-shift-region-left
C-c =            py-down-exception
C-c >            py-shift-region-right
C-c ?            py-describe-mode
C-c |            py-execute-region

C-x n d            py-narrow-to-defun
"""