Source

codeaide / codeaide / modes / python / console.py

# -*- coding: utf-8 -*-
import sys
import traceback

from codeaide.modes.base.console import ConsoleBase


# XXX: redirect raw_input, input and getpass.getpass
# (to make things like help() working)


class PythonInterpreter(object):


    def __init__(self, name="<pyqtshell>", locals=None):
        self.name = name
        self.locals = locals or {}
        self.locals["__name__"] = self.name
        self.lines = []


    def run(self, source, locals=None):
        if locals == None:
            locals = self.locals
        code = compile(source, self.name, "exec")
        try:
            exec code in locals
        except:
            self.showtraceback()


    def push(self, line):
        if self.lines:
            if line:
                self.lines.append(line)
                return True # want more!
            else:
                line = "\n".join(self.lines) + "\n"
        else:
            if not line:
                return False
        try:
            code = compile(line, self.name, "single")
            self.lines = []
        except SyntaxError, why:
            if why[0] == "unexpected EOF while parsing":
                self.lines.append(line)
                return True # want more!
            else:
                self.showtraceback()
        except:
            self.showtraceback()
        else:
            try:
                exec code in self.locals
            except:
                self.showtraceback()
        return False



    def showtraceback(self):
        self.lines = []
        if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2:
            print "  File \"%s\", line %d" % (self.name, sys.exc_value[1][1])
            print " " * (sys.exc_value[1][2] + 2) + "^"
            print sys.exc_type.__name__ + ":", sys.exc_value[0]
        else:
            traceback.print_tb(sys.exc_traceback, None)
            print sys.exc_type.__name__ + ":", sys.exc_value



class PythonIntrospectionCompleter(object):


    def __init__(self, namespace):
        self.namespace = namespace
        self.matches = []


    def complete(self, text, state):
        if state == 0:
            if "." in text:
                self.matches = self.attr_matches(text)
            else:
                self.matches = self.global_matches(text)
        try:
            return self.matches[state]
        except IndexError:
            return None


    def global_matches(self, text):
        import keyword, __builtin__
        matches = []
        n = len(text)
        for list in [keyword.kwlist,
                     __builtin__.__dict__,
                     self.namespace]:
            for word in list:
                if word[:n] == text and word != "__builtins__":
                    matches.append(word)
        return matches


    def attr_matches(self, text):
        def get_class_members(cls):
            ret = dir(cls)
            if hasattr(cls,'__bases__'):
                for base in cls.__bases__:
                    ret = ret + get_class_members(base)
            return ret
        import re
        m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
        if not m:
            return
        expr, attr = m.group(1, 3)
        object = eval(expr, self.namespace)
        words = dir(object)
        if hasattr(object,'__class__'):
            words.append('__class__')
            words = words + get_class_members(object.__class__)
        matches = []
        n = len(attr)
        for word in words:
            if word[:n] == attr and word != "__builtins__":
                matches.append("%s.%s" % (expr, word))
        return matches




class PythonConsoleBase(ConsoleBase):

    ps1 = ">>> "
    ps2 = "... "
    history_filename = "history.py"
    history_pattern = "*.py"

    require_modules = [
        "codeaide.modes.python.core"
        ]
    requires = ["PythonHighlighting"]
    instance = None

    def init_console(self):
        if self.__class__.instance:
            raise TypeError("Only one instance of Python console allowed")
        self.__class__.instance = self
        sys.stdout = sys.stderr = self
        self.namespace = {}
        self.inter = PythonInterpreter("<PyQt console>", self.namespace)
        self.completer = PythonIntrospectionCompleter(self.namespace)
        self.push("pass")


    def __del__(self):
        PythonConsoleBase.instance = None

        
    def clear_console(self):
        ConsoleBase.clear_console(self)
        self.inter.lines = []


    def push(self, line):
        result = self.inter.push(line)
        return result


    def save_session(self):
        key = self.__class__.__name__
        return {"%s_history" % key: self.history}


    def restore_session(self, data):
        key = self.__class__.__name__
        self.history = self.history or data.get("%s_history"  % key)