Enabling Code Completion in an embedded Python Interpreter

后端 未结 4 2076
青春惊慌失措
青春惊慌失措 2020-12-28 23:20

I have got a PyQT widget interpreter working, the code picked up from here is as follows:

import os
import re
import sys
import code

from PyQt4.QtGui impo         


        
相关标签:
4条回答
  • 2020-12-28 23:53

    If you want to save yourself some time - take a look at spyderlib, it contains a widget that embeds an interactive Python interpreter with some interaction sugar such as code completion. The bits of specific interest are

    • spyderlib/widgets/sourcecode/codeeditor.py
    • spyderlib/shell.py
    • spyderlib/editor.py
    • spyderlib/widgets/externalshell/pythonshell.py
    • spyderlib/utiils/module_completion.py
    • spyderlib/plugins/externalconsole.py
    • spyderlib/plugins/console.py
    • spyderlib/plugins/editor.py

    The only caveat I have with spyderlib is that you can't just use that doodad on it's own - somewhere I have a version I extracted that contains the bare minimum of support modules needed to run. If you run into the same problem I did regarding the bloat send me a msg and I'll check my stuff into github for you to grab.

    I also seem to remember there's an Qt based interactive Python interpreter widget that is used in NumPy or SciPy - I think it originally came from the ipython project however. It's pretty nice because it actually splits the interpeter from the execution of code - so if your code crashes, your interpreter doesn't crash with it. But, in that case you can't modify the Pythonic contents of other threads.. The spyderlib version can work both ways.

    0 讨论(0)
  • 2020-12-28 23:53

    I have an open-source PyQt based Python interpreter that you can find here: http://docs.projexsoftware.com/api/projexui/

    The specific class is the XConsoleEdit found in projexui.widgets.xconsoleedit. It has auto-completion built-in.

    Hope that helps!

    0 讨论(0)
  • 2020-12-28 23:55

    I think you are referring to rlcompleter's Completer object.

    You can used it like so:

    from rlcompleter import Completer
    
    line = str(...)
    
    completer = Completer(self.interpreter.locals)
    suggestion = completer.complete(line, 0)
    self.insertPlainText(suggestion)
    

    The numeric argument indicates the n-th suggestion, and you can iterate over it until it returns None.

    For example, say we have

    >>> my_data = '012345'
    

    then

    >>> completer.complete('my_', 0)
    'my_data'
    >>> completer.complete('my_data.s', 0)
    'my_data.split('
    >>> completer.complete('my_data.s', 1)
    'my_data.splitlines('
    

    Note that while the code above uses interpreter.locals, you can apply a wider search (but be sure to provide a dictionary).

    0 讨论(0)
  • 2020-12-28 23:56

    I get auto complete from rlcompleter2, but there are two problems in the following code,

    1. import xxx as yyy auto complete on yyy doesn't work
    2. the locals() are not copied into the interactiveinterpreter, I tried to use this code in Autodesk Maya, eg, run x=3 in maya script editor, and then run x in the pyqt interpreter, it says NameError: name 'x' is not defined. if you do not use maya, this error can be reproduced from external python interpreter as well, first define some variable, then launch this ui, the variable is not copied into the interpreter in the ui.
    import os
    import re
    import sys
    import code
    
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
    class MyInterpreter(QWidget):
    
        def __init__(self, parent):
    
            super(MyInterpreter, self).__init__(parent)
            hBox = QHBoxLayout()
    
            self.setLayout(hBox)
            self.textEdit = PyInterp(self)
    
            # this is how you pass in locals to the interpreter
            self.textEdit.initInterpreter(locals())
    
            self.resize(850, 400)
            # self.centerOnScreen()
    
            hBox.addWidget(self.textEdit)
            hBox.setMargin(0)
            hBox.setSpacing(0)
    
        def centerOnScreen(self):
            # center the widget on the screen
            resolution = QDesktopWidget().screenGeometry()
            self.move((resolution.width() / 2) - (self.frameSize().width() / 2),
                      (resolution.height() / 2) - (self.frameSize().height() / 2))
    
    
    class PyInterp(QTextEdit):
    
        class InteractiveInterpreter(code.InteractiveInterpreter):
    
            def __init__(self, locals):
                code.InteractiveInterpreter.__init__(self, locals)
    
            def runIt(self, command):
                code.InteractiveInterpreter.runsource(self, command)
    
        def __init__(self,  parent):
            super(PyInterp,  self).__init__(parent)
    
            sys.stdout = self
            sys.stderr = self
            self.refreshMarker = False  # to change back to >>> from ...
            self.multiLine = False  # code spans more than one line
            self.command = ''    # command to be ran
            self.printBanner()              # print sys info
            self.marker()                   # make the >>> or ... marker
            self.history = []    # list of commands entered
            self.historyIndex = -1
            self.interpreterLocals = {}
    
            # setting the color for bg and text
            # palette = QPalette()
            # palette.setColor(QPalette.Base, QColor(0, 0, 0))
            # palette.setColor(QPalette.Text, QColor(0, 255, 0))
            # self.setPalette(palette)
            self.setFont(QFont('Courier', 10))
    
            # initilize interpreter with self locals
            self.initInterpreter(locals())
    
            from rlcompleter2 import Completer
            self.completer = Completer()
    
        def printBanner(self):
            self.write(sys.version)
            self.write(' on ' + sys.platform + '\n')
            self.write('PyQt4 ' + PYQT_VERSION_STR + '\n')
            # msg = 'Type !hist for a history view and !hist(n) history index recall'
            # self.write(msg + '\n')
    
        def marker(self):
            if self.multiLine:
                self.insertPlainText('... ')
            else:
                self.insertPlainText('>>> ')
    
        def initInterpreter(self, interpreterLocals=None):
            if interpreterLocals:
                # when we pass in locals, we don't want it to be named "self"
                # so we rename it with the name of the class that did the passing
                # and reinsert the locals back into the interpreter dictionary
                selfName = interpreterLocals['self'].__class__.__name__
                interpreterLocalVars = interpreterLocals.pop('self')
                self.interpreterLocals[selfName] = interpreterLocalVars
            else:
                self.interpreterLocals = interpreterLocals
            self.interpreter = self.InteractiveInterpreter(self.interpreterLocals)
    
        def updateInterpreterLocals(self, newLocals):
            className = newLocals.__class__.__name__
            self.interpreterLocals[className] = newLocals
    
        def write(self, line):
            self.insertPlainText(line)
            self.ensureCursorVisible()
    
        def clearCurrentBlock(self):
            # block being current row
            length = len(self.document().lastBlock().text()[4:])
            if length == 0:
                return None
            else:
                # should have a better way of doing this but I can't find it
                [self.textCursor().deletePreviousChar() for x in xrange(length)]
            return True
    
        def recallHistory(self):
            # used when using the arrow keys to scroll through history
            self.clearCurrentBlock()
            if self.historyIndex <> -1:
                self.insertPlainText(self.history[self.historyIndex])
            return True
    
        def customCommands(self, command):
    
            if command == '!hist':  # display history
                self.append('')  # move down one line
                # vars that are in the command are prefixed with ____CC and deleted
                # once the command is done so they don't show up in dir()
                backup = self.interpreterLocals.copy()
                history = self.history[:]
                history.reverse()
                for i, x in enumerate(history):
                    iSize = len(str(i))
                    delta = len(str(len(history))) - iSize
                    line = line = ' ' * delta + '%i: %s' % (i, x) + '\n'
                    self.write(line)
                self.updateInterpreterLocals(backup)
                self.marker()
                return True
    
            if re.match('!hist\(\d+\)', command):  # recall command from history
                backup = self.interpreterLocals.copy()
                history = self.history[:]
                history.reverse()
                index = int(command[6:-1])
                self.clearCurrentBlock()
                command = history[index]
                if command[-1] == ':':
                    self.multiLine = True
                self.write(command)
                self.updateInterpreterLocals(backup)
                return True
    
            return False
    
        def keyPressEvent(self, event):
    
            if event.key() == Qt.Key_Tab:
                line = str(self.document().lastBlock().text())[4:]
                self.completer.construct(line)
    
                if len(self.completer.rl_matches) == 1:
                    self.clearCurrentBlock()
                    self.insertPlainText(self.completer.rl_matches[0])
                else:
                    print 'repeat:', self.completer.repeated
    
                    mod = self.completer.repeated % len(self.completer.completions)
                    if mod == 0:
                        # print '\n'.join(self.completer.rl_matches)
                        col_print(self.completer.rl_matches)
                    else:
    
                        print ' '
                        print '\n'.join(self.completer.rl_matches)
                        # print self.completer.rl_matches
                    self.marker()
                    self.insertPlainText(line)
    
                return
    
            if event.key() == Qt.Key_Escape:
                # proper exit
                self.interpreter.runIt('exit()')
    
            if event.key() == Qt.Key_Down:
                if self.historyIndex == len(self.history):
                    self.historyIndex -= 1
                try:
                    if self.historyIndex > -1:
                        self.historyIndex -= 1
                        self.recallHistory()
                    else:
                        self.clearCurrentBlock()
                except:
                    pass
                return None
    
            if event.key() == Qt.Key_Up:
                try:
                    if len(self.history) - 1 > self.historyIndex:
                        self.historyIndex += 1
                        self.recallHistory()
                    else:
                        self.historyIndex = len(self.history)
                except:
                    pass
                return None
    
            if event.key() == Qt.Key_Home:
                # set cursor to position 4 in current block. 4 because that's where
                # the marker stops
                blockLength = len(self.document().lastBlock().text()[4:])
                lineLength = len(self.document().toPlainText())
                position = lineLength - blockLength
                textCursor = self.textCursor()
                textCursor.setPosition(position)
                self.setTextCursor(textCursor)
                return None
    
            if event.key() in [Qt.Key_Left, Qt.Key_Backspace]:
                # don't allow deletion of marker
                # if qt version < 4.7, have to use position() - block().position()
                if self.textCursor().positionInBlock() == 4:
                    return None
    
            if event.key() in [Qt.Key_Return, Qt.Key_Enter]:
                # set cursor to end of line to avoid line splitting
                textCursor = self.textCursor()
                position = len(self.document().toPlainText())
                textCursor.setPosition(position)
                self.setTextCursor(textCursor)
    
                line = str(self.document().lastBlock().text())[4:]  # remove marker
                line.rstrip()
                self.historyIndex = -1
    
                if self.customCommands(line):
                    return None
                else:
                    try:
                        line[-1]
                        self.haveLine = True
                        if line[-1] == ':':
                            self.multiLine = True
                        self.history.insert(0, line)
                    except:
                        self.haveLine = False
    
                    if self.haveLine and self.multiLine:  # multi line command
                        self.command += line + '\n'  # + command and line
                        self.append('')  # move down one line
                        self.marker()  # handle marker style
                        return None
    
                    if self.haveLine and not self.multiLine:  # one line command
                        self.command = line  # line is the command
                        self.append('')  # move down one line
                        self.interpreter.runIt(self.command)
                        self.command = ''  # clear command
                        self.marker()  # handle marker style
                        return None
    
                    if self.multiLine and not self.haveLine:  # multi line done
                        self.append('')  # move down one line
                        self.interpreter.runIt(self.command)
                        self.command = ''  # clear command
                        self.multiLine = False  # back to single line
                        self.marker()  # handle marker style
                        return None
    
                    if not self.haveLine and not self.multiLine:  # just enter
                        self.append('')
                        self.marker()
                        return None
                    return None
    
            # allow all other key events
            super(PyInterp, self).keyPressEvent(event)
    
    # http://stackoverflow.com/a/30861871/2052889
    
    
    def col_print(lines, term_width=90, indent=0, pad=2):
        n_lines = len(lines)
        if n_lines == 0:
            return
    
        col_width = max(len(line) for line in lines)
        n_cols = int((term_width + pad - indent)/(col_width + pad))
        n_cols = min(n_lines, max(1, n_cols))
    
        col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
        if (n_cols - 1) * col_len >= n_lines:
            n_cols -= 1
    
        cols = [lines[i*col_len: i*col_len + col_len] for i in range(n_cols)]
    
        rows = list(zip(*cols))
        rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
        rows.extend(rows_missed)
    
        for row in rows:
            print(" "*indent + (" "*pad).join(line.ljust(col_width)
                                              for line in row))
    
    
    def main():
        app = QApplication(sys.argv)
        win = MyInterpreter(None)
        win.show()
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    

    desired effect: https://gfycat.com/DistantScrawnyCivet gfycat

    current effect: https://gfycat.com/DeafeningHeavyBoto gfycat

    0 讨论(0)
提交回复
热议问题