Ejemplo n.º 1
0
class MainConsole(QWidget):
    """
    Interpreter with interactive console.
    All console output will be redirected to this command line.
    It provides normal REPL functionality and additionally access to editor components
    such as the session object and nodes (by right-click on them).
    The input field below can also expand to a text edit to take whole code blocks.
    """

    instance = None

    def __init__(
            self,
            window_theme,
            history: int = 100,  # max lines in history buffer
            blockcount: int = 5000,  # max lines in output buffer
    ):

        super(MainConsole, self).__init__()

        self.session = None  # set by MainWindow
        self.window_theme = window_theme

        self.init_ui(history, blockcount)

    def init_ui(self, history, blockcount):
        self.content_layout = QGridLayout(self)
        self.content_layout.setContentsMargins(0, 0, 0, 0)
        # self.content_layout.setSpacing(0)

        self.input_layout = QGridLayout()
        self.input_layout.setContentsMargins(0, 0, 0, 0)
        self.input_layout.setSpacing(0)

        # display for output
        self.out_display = ConsoleDisplay(blockcount)
        self.content_layout.addWidget(self.out_display, 1, 0, 1, 2)

        # colors to differentiate input, output and stderr
        self.inpfmt = self.out_display.currentCharFormat()
        self.inpfmt.setForeground(
            QBrush(QColor(self.window_theme.colors['primaryColor'])))
        self.outfmt = QTextCharFormat(self.inpfmt)
        self.outfmt.setForeground(
            QBrush(QColor(self.window_theme.colors['secondaryTextColor'])))
        self.errfmt = QTextCharFormat(self.inpfmt)
        self.errfmt.setForeground(
            QBrush(QColor(self.window_theme.colors['danger'])))

        # display input prompt left besides input edit
        self.prompt_label = QLabel('> ', self)
        self.prompt_label.setFixedWidth(15)
        self.input_layout.addWidget(self.prompt_label, 0, 0, 1, 1)
        self.prompt_label.hide()

        # command "text edit" for large code input
        self.inptextedit = ConsoleInputTextEdit(self.window_theme)
        self.input_layout.addWidget(self.inptextedit, 1, 0, 2, 2)
        self.inptextedit.hide()

        # command line
        self.inpedit = ConsoleInputLineEdit(self.inptextedit,
                                            max_history=history)
        self.inpedit.returned.connect(self.push)
        self.input_layout.addWidget(self.inpedit, 0, 1, 1, 1)

        self.content_layout.addLayout(self.input_layout, 2, 0, 1, 2)

        self.interp = None
        self.reset_interpreter()

        self.buffer = []
        self.num_added_object_contexts = 0

    def setprompt(self, text: str):
        # self.prompt_label.setText(text)
        ...

    def add_obj_context(self, context_obj):
        """adds an object to the current context by initializing a new interpreter with a new context"""

        old_context = {} if self.interp is None else self.interp.locals
        name = 'obj' + (str(self.num_added_object_contexts +
                            1) if self.num_added_object_contexts > 0 else '')
        new_context = {name: context_obj}
        context = {**old_context, **new_context}  # merge dicts
        self.interp = code.InteractiveConsole(context)
        print('added as ' + name)

        self.num_added_object_contexts += 1

    def reset_interpreter(self):
        """Initializes a new plain interpreter"""

        # CONTEXT
        session = self.session

        def reset():
            self.reset_interpreter()

        context = {**locals()}
        # -------

        self.num_added_object_contexts = 0
        # self.reset_scope_button.hide()
        self.interp = code.InteractiveConsole(context)

    def push(self, commands: str) -> None:
        """execute entered command which may span multiple lines when code was pasted"""

        if commands == 'clear':
            self.out_display.clear()
        else:
            lines = commands.split('\n')  # usually just one entry

            # clean and print commands
            for line in lines:

                # # remove '> ' and '.' prefixes which may remain from copy&paste
                # if re.match('^[\>\.] ', line):
                #     line = line[2:]

                # print input
                self.writeoutput(
                    # self.prompt_label.text() +
                    line,
                    self.inpfmt)

                # prepare for multi-line input
                # self.setprompt('. ')
                self.buffer.append(line)

            # merge commands
            source = '\n'.join(self.buffer)
            more = self.interp.runsource(source, '<console>')

            if more:
                # self.setprompt('> ')
                if self.prompt_label.isHidden():
                    self.prompt_label.show()

                # add leading space for next input
                leading_space = re.match(r"\s*", self.buffer[-1]).group()
                self.inpedit.next_line = leading_space

            else:  # no more input required
                self.prompt_label.hide()
                self.buffer = []  # reset buffer

    def write(self, line: str) -> None:
        """capture stdout and print to outdisplay"""
        if len(line) != 1 or ord(line[0]) != 10:
            self.writeoutput(line.rstrip())  # , self.outfmt)

    def errorwrite(self, line: str) -> None:
        """capture stderr and print to outdisplay"""
        self.writeoutput(line, self.errfmt)

    def writeoutput(self, line: str, fmt: QTextCharFormat = None) -> None:
        """prints to outdisplay"""
        if fmt:
            self.out_display.setCurrentCharFormat(fmt)
        self.out_display.appendPlainText(line.rstrip())
        self.out_display.setCurrentCharFormat(self.outfmt)