示例#1
0
    def __init__(self, parent=None):
        super(PythonWidget, self).__init__(parent)

        # PythonWidget attributes.
        self.locals = dict(__name__='__console__', __doc__=None)
        self.interpreter = InteractiveInterpreter(self.locals)

        # PythonWidget protected attributes.
        self._buffer = StringIO()
        self._bracket_matcher = BracketMatcher(self._control)
        self._call_tip_widget = CallTipWidget(self._control)
        self._completion_lexer = CompletionLexer(PythonLexer())
        self._hidden = False
        self._highlighter = PythonWidgetHighlighter(self)
        self._last_refresh_time = 0

        # file-like object attributes.
        self.encoding = sys.stdin.encoding

        # Configure the ConsoleWidget.
        self.tab_width = 4
        self._set_continuation_prompt('... ')

        # Configure the CallTipWidget.
        self._call_tip_widget.setFont(self.font)
        self.font_changed.connect(self._call_tip_widget.setFont)

        # Connect signal handlers.
        document = self._control.document()
        document.contentsChange.connect(self._document_contents_change)

        # Display the banner and initial prompt.
        self.reset()
示例#2
0
    def __init__(self, parent=None):
        super(PythonWidget, self).__init__(parent)

        # PythonWidget attributes.
        self.locals = dict(__name__='__console__', __doc__=None)
        self.interpreter = InteractiveInterpreter(self.locals)

        # PythonWidget protected attributes.
        self._buffer = StringIO()
        self._bracket_matcher = BracketMatcher(self._control)
        self._call_tip_widget = CallTipWidget(self._control)
        self._completion_lexer = CompletionLexer(PythonLexer())
        self._hidden = False
        self._highlighter = PythonWidgetHighlighter(self)
        self._last_refresh_time = 0

        # file-like object attributes.
        self.encoding = sys.stdin.encoding

        # Configure the ConsoleWidget.
        self.tab_width = 4
        self._set_continuation_prompt('... ')

        # Configure the CallTipWidget.
        self._call_tip_widget.setFont(self.font)
        self.font_changed.connect(self._call_tip_widget.setFont)

        # Connect signal handlers.
        document = self._control.document()
        document.contentsChange.connect(self._document_contents_change)

        # Display the banner and initial prompt.
        self.reset()
示例#3
0
class PythonWidget(HistoryConsoleWidget):
    """ A basic in-process Python interpreter.
    """

    # Emitted when a command has been executed in the interpeter.
    executed = QtCore.Signal()

    #--------------------------------------------------------------------------
    # 'object' interface
    #--------------------------------------------------------------------------

    def __init__(self, parent=None):
        super(PythonWidget, self).__init__(parent)

        # PythonWidget attributes.
        self.locals = dict(__name__='__console__', __doc__=None)
        self.interpreter = InteractiveInterpreter(self.locals)

        # PythonWidget protected attributes.
        self._buffer = StringIO()
        self._bracket_matcher = BracketMatcher(self._control)
        self._call_tip_widget = CallTipWidget(self._control)
        self._completion_lexer = CompletionLexer(PythonLexer())
        self._hidden = False
        self._highlighter = PythonWidgetHighlighter(self)
        self._last_refresh_time = 0

        # file-like object attributes.
        self.encoding = sys.stdin.encoding

        # Configure the ConsoleWidget.
        self.tab_width = 4
        self._set_continuation_prompt('... ')

        # Configure the CallTipWidget.
        self._call_tip_widget.setFont(self.font)
        self.font_changed.connect(self._call_tip_widget.setFont)

        # Connect signal handlers.
        document = self._control.document()
        document.contentsChange.connect(self._document_contents_change)

        # Display the banner and initial prompt.
        self.reset()

    #--------------------------------------------------------------------------
    # file-like object interface
    #--------------------------------------------------------------------------

    def flush(self):
        """ Flush the buffer by writing its contents to the screen.
        """
        self._buffer.seek(0)
        text = self._buffer.getvalue()
        self._buffer.close()
        self._buffer = StringIO()

        self._append_plain_text(text)
        self._control.moveCursor(QtGui.QTextCursor.End)

    def readline(self, prompt=None):
        """ Read and return one line of input from the user.
        """
        return self._readline(prompt)

    def write(self, text, refresh=True):
        """ Write text to the buffer, possibly flushing it if 'refresh' is set.
        """
        if not self._hidden:
            self._buffer.write(text)
            if refresh:
                current_time = time()
                if current_time - self._last_refresh_time > 0.05:
                    self.flush()
                    self._last_refresh_time = current_time

    def writelines(self, lines, refresh=True):
        """ Write a list of lines to the buffer.
        """
        for line in lines:
            self.write(line, refresh=refresh)

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' abstract interface
    #---------------------------------------------------------------------------

    def _is_complete(self, source, interactive):
        """ Returns whether 'source' can be completely processed and a new
            prompt created. When triggered by an Enter/Return key press,
            'interactive' is True; otherwise, it is False.
        """
        if interactive:
            lines = source.splitlines()
            if len(lines) == 1:
                try:
                    return compile_command(source) is not None
                except:
                    # We'll let the interpeter handle the error.
                    return True
            else:
                return lines[-1].strip() == ''
        else:
            return True

    def _execute(self, source, hidden):
        """ Execute 'source'. If 'hidden', do not show any output.

        See parent class :meth:`execute` docstring for full details.
        """
        # Save the current std* and point them here
        old_stdin = sys.stdin
        old_stdout = sys.stdout
        old_stderr = sys.stderr
        sys.stdin = sys.stdout = sys.stderr = self

        # Run the source code in the interpeter
        self._hidden = hidden
        try:
            more = self.interpreter.runsource(source)
        finally:
            self._hidden = False

            # Restore std* unless the executed changed them
            if sys.stdin is self:
                sys.stdin = old_stdin
            if sys.stdout is self:
                sys.stdout = old_stdout
            if sys.stderr is self:
                sys.stderr = old_stderr

            self.executed.emit()
            self._show_interpreter_prompt()

    def _prompt_started_hook(self):
        """ Called immediately after a new prompt is displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = True

    def _prompt_finished_hook(self):
        """ Called immediately after a prompt is finished, i.e. when some input
            will be processed and a new prompt displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = False

    def _tab_pressed(self):
        """ Called when the tab key is pressed. Returns whether to continue
            processing the event.
        """
        # Perform tab completion if:
        # 1) The cursor is in the input buffer.
        # 2) There is a non-whitespace character before the cursor.
        text = self._get_input_buffer_cursor_line()
        if text is None:
            return False
        complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
        if complete:
            self._complete()
        return not complete

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' protected interface
    #---------------------------------------------------------------------------

    def _event_filter_console_keypress(self, event):
        """ Reimplemented for smart backspace.
        """
        if event.key() == QtCore.Qt.Key_Backspace and \
                not event.modifiers() & QtCore.Qt.AltModifier:
            # Smart backspace: remove four characters in one backspace if:
            # 1) everything left of the cursor is whitespace
            # 2) the four characters immediately left of the cursor are spaces
            col = self._get_input_buffer_cursor_column()
            cursor = self._control.textCursor()
            if col > 3 and not cursor.hasSelection():
                text = self._get_input_buffer_cursor_line()[:col]
                if text.endswith('    ') and not text.strip():
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        QtGui.QTextCursor.KeepAnchor, 4)
                    cursor.removeSelectedText()
                    return True

        return super(PythonWidget, self)._event_filter_console_keypress(event)

    def _insert_continuation_prompt(self, cursor):
        """ Reimplemented for auto-indentation.
        """
        super(PythonWidget, self)._insert_continuation_prompt(cursor)
        source = self.input_buffer
        space = 0
        for c in source.splitlines()[-1]:
            if c == '\t':
                space += 4
            elif c == ' ':
                space += 1
            else:
                break
        if source.rstrip().endswith(':'):
            space += 4
        cursor.insertText(' ' * space)

    #---------------------------------------------------------------------------
    # 'PythonWidget' public interface
    #---------------------------------------------------------------------------

    def execute_file(self, path, hidden=False):
        """ Attempts to execute file with 'path'. If 'hidden', no output is
            shown.
        """
        self.execute('execfile("%s")' % path, hidden=hidden)

    def reset(self):
        """ Resets the widget to its initial state. Similar to ``clear``, but
            also re-writes the banner.
        """
        self._reading = False
        self._highlighter.highlighting_on = False

        self._control.clear()
        self._append_plain_text(self._get_banner())
        self._show_interpreter_prompt()

    #---------------------------------------------------------------------------
    # 'PythonWidget' protected interface
    #---------------------------------------------------------------------------

    def _call_tip(self):
        """ Shows a call tip, if appropriate, at the current cursor location.
        """
        # Decide if it makes sense to show a call tip
        cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.Left)
        if cursor.document().characterAt(cursor.position()) != '(':
            return False
        context = self._get_context(cursor)
        if not context:
            return False

        # Look up the context and show a tip for it
        symbol, leftover = self._get_symbol_from_context(context)
        doc = getattr(symbol, '__doc__', None)
        if doc is not None and not leftover:
            self._call_tip_widget.show_call_info(doc=doc)
            return True
        return False

    def _complete(self):
        """ Performs completion at the current cursor location.
        """
        context = self._get_context()
        if context:
            symbol, leftover = self._get_symbol_from_context(context)
            if len(leftover) == 1:
                leftover = leftover[0]
                if symbol is None:
                    names = self.interpreter.locals.keys()
                    names += __builtin__.__dict__.keys()
                else:
                    names = dir(symbol)
                completions = [n for n in names if n.startswith(leftover)]
                if completions:
                    cursor = self._get_cursor()
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        n=len(context[-1]))
                    self._complete_with_items(cursor, completions)

    def _get_banner(self):
        """ Gets a banner to display at the beginning of a session.
        """
        banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
            '"license" for more information.'
        return banner % (sys.version, sys.platform)

    def _get_context(self, cursor=None):
        """ Gets the context for the specified cursor (or the current cursor
            if none is specified).
        """
        if cursor is None:
            cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
                            QtGui.QTextCursor.KeepAnchor)
        text = cursor.selection().toPlainText()
        return self._completion_lexer.get_context(text)

    def _get_symbol_from_context(self, context):
        """ Find a python object in the interpeter namespace from a context (a
            list of names).
        """
        context = map(str, context)
        if len(context) == 0:
            return None, context

        base_symbol_string = context[0]
        symbol = self.interpreter.locals.get(base_symbol_string, None)
        if symbol is None:
            symbol = __builtin__.__dict__.get(base_symbol_string, None)
        if symbol is None:
            return None, context

        context = context[1:]
        for i, name in enumerate(context):
            new_symbol = getattr(symbol, name, None)
            if new_symbol is None:
                return symbol, context[i:]
            else:
                symbol = new_symbol

        return symbol, []

    def _show_interpreter_prompt(self):
        """ Shows a prompt for the interpreter.
        """
        self.flush()
        self._show_prompt('>>> ')

    #------ Signal handlers ----------------------------------------------------

    def _document_contents_change(self, position, removed, added):
        """ Called whenever the document's content changes. Display a call tip
            if appropriate.
        """
        # Calculate where the cursor should be *after* the change:
        position += added

        document = self._control.document()
        if position == self._get_cursor().position():
            self._call_tip()
示例#4
0
class PythonWidget(HistoryConsoleWidget):
    """ A basic in-process Python interpreter.
    """

    # Emitted when a command has been executed in the interpeter.
    executed = QtCore.Signal()

    #--------------------------------------------------------------------------
    # 'object' interface
    #--------------------------------------------------------------------------

    def __init__(self, parent=None):
        super(PythonWidget, self).__init__(parent)

        # PythonWidget attributes.
        self.locals = dict(__name__='__console__', __doc__=None)
        self.interpreter = InteractiveInterpreter(self.locals)

        # PythonWidget protected attributes.
        self._buffer = StringIO()
        self._bracket_matcher = BracketMatcher(self._control)
        self._call_tip_widget = CallTipWidget(self._control)
        self._completion_lexer = CompletionLexer(PythonLexer())
        self._hidden = False
        self._highlighter = PythonWidgetHighlighter(self)
        self._last_refresh_time = 0

        # file-like object attributes.
        self.encoding = sys.stdin.encoding

        # Configure the ConsoleWidget.
        self.tab_width = 4
        self._set_continuation_prompt('... ')

        # Configure the CallTipWidget.
        self._call_tip_widget.setFont(self.font)
        self.font_changed.connect(self._call_tip_widget.setFont)

        # Connect signal handlers.
        document = self._control.document()
        document.contentsChange.connect(self._document_contents_change)

        # Display the banner and initial prompt.
        self.reset()

    #--------------------------------------------------------------------------
    # file-like object interface
    #--------------------------------------------------------------------------

    def flush(self):
        """ Flush the buffer by writing its contents to the screen.
        """
        self._buffer.seek(0)
        text = self._buffer.getvalue()
        self._buffer.close()
        self._buffer = StringIO()

        self._append_plain_text(text)
        self._control.moveCursor(QtGui.QTextCursor.End)

    def readline(self, prompt=None):
        """ Read and return one line of input from the user.
        """
        return self._readline(prompt)

    def write(self, text, refresh=True):
        """ Write text to the buffer, possibly flushing it if 'refresh' is set.
        """
        if not self._hidden:
            self._buffer.write(text)
            if refresh:
                current_time = time()
                if current_time - self._last_refresh_time > 0.05:
                    self.flush()
                    self._last_refresh_time = current_time

    def writelines(self, lines, refresh=True):
        """ Write a list of lines to the buffer.
        """
        for line in lines:
            self.write(line, refresh=refresh)

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' abstract interface
    #---------------------------------------------------------------------------

    def _is_complete(self, source, interactive):
        """ Returns whether 'source' can be completely processed and a new
            prompt created. When triggered by an Enter/Return key press,
            'interactive' is True; otherwise, it is False.
        """
        if interactive:
            lines = source.splitlines()
            if len(lines) == 1:
                try:
                    return compile_command(source) is not None
                except:
                    # We'll let the interpeter handle the error.
                    return True
            else:
                return lines[-1].strip() == ''
        else:
            return True

    def _execute(self, source, hidden):
        """ Execute 'source'. If 'hidden', do not show any output.

        See parent class :meth:`execute` docstring for full details.
        """
        # Save the current std* and point them here
        old_stdin = sys.stdin
        old_stdout = sys.stdout
        old_stderr = sys.stderr
        sys.stdin = sys.stdout = sys.stderr = self

        # Run the source code in the interpeter
        self._hidden = hidden
        try:
            more = self.interpreter.runsource(source)
        finally:
            self._hidden = False

            # Restore std* unless the executed changed them
            if sys.stdin is self:
                sys.stdin = old_stdin
            if sys.stdout is self:
                sys.stdout = old_stdout
            if sys.stderr is self:
                sys.stderr = old_stderr

            self.executed.emit()
            self._show_interpreter_prompt()

    def _prompt_started_hook(self):
        """ Called immediately after a new prompt is displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = True

    def _prompt_finished_hook(self):
        """ Called immediately after a prompt is finished, i.e. when some input
            will be processed and a new prompt displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = False

    def _tab_pressed(self):
        """ Called when the tab key is pressed. Returns whether to continue
            processing the event.
        """
        # Perform tab completion if:
        # 1) The cursor is in the input buffer.
        # 2) There is a non-whitespace character before the cursor.
        text = self._get_input_buffer_cursor_line()
        if text is None:
            return False
        complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
        if complete:
            self._complete()
        return not complete

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' protected interface
    #---------------------------------------------------------------------------

    def _event_filter_console_keypress(self, event):
        """ Reimplemented for smart backspace.
        """
        if event.key() == QtCore.Qt.Key_Backspace and \
                not event.modifiers() & QtCore.Qt.AltModifier:
            # Smart backspace: remove four characters in one backspace if:
            # 1) everything left of the cursor is whitespace
            # 2) the four characters immediately left of the cursor are spaces
            col = self._get_input_buffer_cursor_column()
            cursor = self._control.textCursor()
            if col > 3 and not cursor.hasSelection():
                text = self._get_input_buffer_cursor_line()[:col]
                if text.endswith('    ') and not text.strip():
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        QtGui.QTextCursor.KeepAnchor, 4)
                    cursor.removeSelectedText()
                    return True

        return super(PythonWidget, self)._event_filter_console_keypress(event)

    def _insert_continuation_prompt(self, cursor):
        """ Reimplemented for auto-indentation.
        """
        super(PythonWidget, self)._insert_continuation_prompt(cursor)
        source = self.input_buffer
        space = 0
        for c in source.splitlines()[-1]:
            if c == '\t':
                space += 4
            elif c == ' ':
                space += 1
            else:
                break
        if source.rstrip().endswith(':'):
            space += 4
        cursor.insertText(' ' * space)

    #---------------------------------------------------------------------------
    # 'PythonWidget' public interface
    #---------------------------------------------------------------------------

    def execute_file(self, path, hidden=False):
        """ Attempts to execute file with 'path'. If 'hidden', no output is
            shown.
        """

        self.execute("exec(open(%s).read())" % repr(path), hidden=hidden)

    def reset(self):
        """ Resets the widget to its initial state. Similar to ``clear``, but
            also re-writes the banner.
        """
        self._reading = False
        self._highlighter.highlighting_on = False

        self._control.clear()
        self._append_plain_text(self._get_banner())
        self._show_interpreter_prompt()

    #---------------------------------------------------------------------------
    # 'PythonWidget' protected interface
    #---------------------------------------------------------------------------

    def _call_tip(self):
        """ Shows a call tip, if appropriate, at the current cursor location.
        """
        # Decide if it makes sense to show a call tip
        cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.Left)
        if cursor.document().characterAt(cursor.position()) != '(':
            return False
        context = self._get_context(cursor)
        if not context:
            return False

        # Look up the context and show a tip for it
        symbol, leftover = self._get_symbol_from_context(context)
        doc = getattr(symbol, '__doc__', None)
        if doc is not None and not leftover:
            self._call_tip_widget.show_call_info(doc=doc)
            return True
        return False

    def _complete(self):
        """ Performs completion at the current cursor location.
        """
        context = self._get_context()
        if context:
            symbol, leftover = self._get_symbol_from_context(context)
            if len(leftover) == 1:
                leftover = leftover[0]
                if symbol is None:
                    names = self.interpreter.locals.keys()
                    names += __builtin__.__dict__.keys()
                else:
                    names = dir(symbol)
                completions = [ n for n in names if n.startswith(leftover) ]
                if completions:
                    cursor = self._get_cursor()
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        n=len(context[-1]))
                    self._complete_with_items(cursor, completions)

    def _get_banner(self):
        """ Gets a banner to display at the beginning of a session.
        """
        banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
            '"license" for more information.'
        return banner % (sys.version, sys.platform)

    def _get_context(self, cursor=None):
        """ Gets the context for the specified cursor (or the current cursor
            if none is specified).
        """
        if cursor is None:
            cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
                            QtGui.QTextCursor.KeepAnchor)
        text = cursor.selection().toPlainText()
        return self._completion_lexer.get_context(text)

    def _get_symbol_from_context(self, context):
        """ Find a python object in the interpeter namespace from a context (a
            list of names).
        """
        context = map(str, context)
        if len(context) == 0:
            return None, context

        base_symbol_string = context[0]
        symbol = self.interpreter.locals.get(base_symbol_string, None)
        if symbol is None:
            symbol = __builtin__.__dict__.get(base_symbol_string, None)
        if symbol is None:
            return None, context

        context = context[1:]
        for i, name in enumerate(context):
            new_symbol = getattr(symbol, name, None)
            if new_symbol is None:
                return symbol, context[i:]
            else:
                symbol = new_symbol

        return symbol, []

    def _show_interpreter_prompt(self):
        """ Shows a prompt for the interpreter.
        """
        self.flush()
        self._show_prompt('>>> ')

    #------ Signal handlers ----------------------------------------------------

    def _document_contents_change(self, position, removed, added):
        """ Called whenever the document's content changes. Display a call tip
            if appropriate.
        """
        # Calculate where the cursor should be *after* the change:
        position += added

        document = self._control.document()
        if position == self._get_cursor().position():
            self._call_tip()