7

我有一个 PyQT 小部件解释器工作,从这里提取的代码如下:

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(650, 300)
        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', 12))

        # initilize interpreter with self locals
        self.initInterpreter(locals())


    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_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 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)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyInterpreter(None)
    win.show()
    sys.exit(app.exec_())

有没有一种简单的方法可以让一些选项卡完成只针对本地符号?

4

4 回答 4

6

我认为您指的是rlcompleter的 Completer 对象。

你可以像这样使用它:

from rlcompleter import Completer

line = str(...)

completer = Completer(self.interpreter.locals)
suggestion = completer.complete(line, 0)
self.insertPlainText(suggestion)

数字参数表示第n个建议,您可以对其进行迭代,直到它返回None

例如,假设我们有

>>> my_data = '012345'

然后

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

请注意,虽然上面的代码使用interpreter.locals,但您可以应用更广泛的搜索(但请务必提供字典)。

于 2012-09-15T00:43:33.020 回答
2

如果您想节省一些时间 - 看看spyderlib,它包含一个嵌入交互式 Python 解释器的小部件,其中包含一些交互糖,例如代码完成。特别感兴趣的位是

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

我对 spyderlib 的唯一警告是,你不能只使用它自己的小玩意儿——在某个地方,我提取了一个版本,其中包含运行所需的最少支持模块。如果您遇到与膨胀有关的相同问题,请给我发送消息,我会将我的内容检查到 github 中供您获取。

我似乎还记得在 NumPy 或 SciPy 中使用了一个基于 Qt 的交互式 Python 解释器小部件——我认为它最初来自 ipython 项目。这非常好,因为它实际上将解释器从代码执行中分离出来——所以如果你的代码崩溃了,你的解释器不会随之崩溃。但是,在这种情况下,您不能修改其他线程的 Pythonic 内容。spyderlib 版本可以双向工作。

于 2012-09-15T01:04:24.973 回答
1

我有一个基于 PyQt 的开源 Python 解释器,您可以在这里找到:http: //docs.projexsoftware.com/api/projexui/

具体的类是在 projexui.widgets.xconsoleedit 中找到的 XConsoleEdit。它具有内置的自动完成功能。

希望有帮助!

于 2012-09-16T04:46:12.793 回答
1

我从rlcompleter2获得自动完成,但以下代码中有两个问题,

  1. import xxx as yyy 在 yyy 上自动完成不起作用
  2. locals() 没有复制到交互式解释器中,我尝试在 Autodesk Maya 中使用此代码,例如,在 Mayax=3脚本编辑器中运行,然后x在 pyqt 解释器中运行,它说NameError: name 'x' is not defined。如果你不使用maya,这个错误也可以从外部python解释器重现,首先定义一些变量,然后启动这个ui,变量不会复制到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()

想要的效果: https ://gfycat.com/DistantScrawnyCivet gfycat

当前效果: https ://gfycat.com/DeafeningHeavyBoto gfycat

于 2016-01-06T05:38:04.617 回答