Enabling code completion in the Python built-in interpreter

I have a PyQT widget interpreter, the code obtained from here looks like this:

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

Is there an easy way to get some tab completion for local characters only?

+7
source share
4 answers

I think you mean rlcompleter Completed Object.

You can use it like this:

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

The numeric argument indicates the nth sentence, and you can iterate over it until it returns None .

For example, let's say that

 >>> 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 interpreter.locals used in the code above, you can apply a broader search (but be sure to include a dictionary).

+5
source

If you want to save some time, look at spyderlib , it contains a widget that has an interactive Python interpreter built in with some sugar interactions, such as code completion. Bits of special interest

  • spyderlib / widgets / Source code / 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 cannot just use this doodad on your own level - somewhere I have a version that I extracted that contains the minimum minimum support modules needed to run. If you run into the same issue as with bloat, send me a message and I will check my details on github so you can capture it.

I also remember that there was a built-in interactive Python interpreter based on Qt that is used in NumPy or SciPy - I think it was originally from the ipython project. This is pretty good because it actually splits the interpreter from executing the code - so if your code works, your interpreter will not work with it. But in this case, you cannot modify the Python contents of other threads. The spyderlib version can work both ways.

+2
source

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

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

Hope this helps!

+1
source

I get automatic completion from rlcompleter2 , but there are two problems in the following code:

  • import xxx as yyy autofill on yyy does not work.
  • locals () are not copied to the interactive interpreter, I tried to use this code in Autodesk Maya, for example, run x=3 in the Maya script editor and then run x in the pyqt interpreter, it says NameError: name 'x' is not defined . if you are not using maya, this error can also be reproduced from an external python interpreter, first define some variable, then run this ui, the variable will not be copied to the interpreter in 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 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: https://gfycat.com/DeafeningHeavyBoto gfycat

+1
source

All Articles