title: rlcompleter2 code completion in an embedded Python Interpreter?
body: <p>I took <a href="https://stackoverflow.com/questions/12431555/enabling-code-completion-in-an-embedded-python-interpreter/34626330#34626330">this</a> code as a starting point, and get auto complete from <a href="https://pypi.python.org/pypi/rlcompleter2/" rel="nofollow noreferrer">rlcompleter2</a>, but there are two problems in the following code,</p>
  
  <ol>
  <li><code>import xxx as yyy</code>  auto complete on yyy doesn't work</li>
  <li>the locals() are not copied into the interactiveinterpreter, I tried to use this code in Autodesk Maya, eg, run <code>x=3</code> in maya script editor, and then run <code>x</code> in the pyqt interpreter, it says <code>NameError: name 'x' is not defined</code>. 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.</li>
  </ol>
  
  <pre class="lang-py prettyprint-override"><code>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 &gt;&gt;&gt; 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 &gt;&gt;&gt; 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('&gt;&gt;&gt; ')
  
      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 &lt;&gt; -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 &gt; -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 &gt; 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 &lt; 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)
  
  # https://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 &gt;= 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()
  </code></pre>
  
  <p>desired effect:
  <a href="https://gfycat.com/DistantScrawnyCivet" rel="nofollow noreferrer">https://gfycat.com/DistantScrawnyCivet</a>
  <img src="https://zippy.gfycat.com/DistantScrawnyCivet.gif" alt="gfycat"></p>
  
  <p>current effect:
  <a href="https://gfycat.com/DeafeningHeavyBoto" rel="nofollow noreferrer">https://gfycat.com/DeafeningHeavyBoto</a>
  <img src="https://fat.gfycat.com/DeafeningHeavyBoto.gif" alt="gfycat"></p>