# source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget import sys, os, re import traceback, platform from PyQt4 import QtCore from PyQt4 import QtGui from electrum_nvc import util if platform.system() == 'Windows': MONOSPACE_FONT = 'Lucida Console' elif platform.system() == 'Darwin': MONOSPACE_FONT = 'Monaco' else: MONOSPACE_FONT = 'monospace' class Console(QtGui.QPlainTextEdit): def __init__(self, prompt='>> ', startup_message='', parent=None): QtGui.QPlainTextEdit.__init__(self, parent) self.prompt = prompt self.history = [] self.namespace = {} self.construct = [] self.setGeometry(50, 75, 600, 400) self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) self.setUndoRedoEnabled(False) self.document().setDefaultFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal)) self.showMessage(startup_message) self.updateNamespace({'run':self.run_script}) self.set_json(False) def set_json(self, b): self.is_json = b def run_script(self, filename): with open(filename) as f: script = f.read() # eval is generally considered bad practice. use it wisely! result = eval(script, self.namespace, self.namespace) def updateNamespace(self, namespace): self.namespace.update(namespace) def showMessage(self, message): self.appendPlainText(message) self.newPrompt() def clear(self): self.setPlainText('') self.newPrompt() def newPrompt(self): if self.construct: prompt = '.' * len(self.prompt) else: prompt = self.prompt self.completions_pos = self.textCursor().position() self.completions_visible = False self.appendPlainText(prompt) self.moveCursor(QtGui.QTextCursor.End) def getCommand(self): doc = self.document() curr_line = unicode(doc.findBlockByLineNumber(doc.lineCount() - 1).text()) curr_line = curr_line.rstrip() curr_line = curr_line[len(self.prompt):] return curr_line def setCommand(self, command): if self.getCommand() == command: return doc = self.document() curr_line = unicode(doc.findBlockByLineNumber(doc.lineCount() - 1).text()) self.moveCursor(QtGui.QTextCursor.End) for i in range(len(curr_line) - len(self.prompt)): self.moveCursor(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor) self.textCursor().removeSelectedText() self.textCursor().insertText(command) self.moveCursor(QtGui.QTextCursor.End) def show_completions(self, completions): if self.completions_visible: self.hide_completions() c = self.textCursor() c.setPosition(self.completions_pos) completions = map(lambda x: x.split('.')[-1], completions) t = '\n' + ' '.join(completions) if len(t) > 500: t = t[:500] + '...' c.insertText(t) self.completions_end = c.position() self.moveCursor(QtGui.QTextCursor.End) self.completions_visible = True def hide_completions(self): if not self.completions_visible: return c = self.textCursor() c.setPosition(self.completions_pos) l = self.completions_end - self.completions_pos for x in range(l): c.deleteChar() self.moveCursor(QtGui.QTextCursor.End) self.completions_visible = False def getConstruct(self, command): if self.construct: prev_command = self.construct[-1] self.construct.append(command) if not prev_command and not command: ret_val = '\n'.join(self.construct) self.construct = [] return ret_val else: return '' else: if command and command[-1] == (':'): self.construct.append(command) return '' else: return command def getHistory(self): return self.history def setHisory(self, history): self.history = history def addToHistory(self, command): if command.find("importprivkey") > -1: return if command and (not self.history or self.history[-1] != command): self.history.append(command) self.history_index = len(self.history) def getPrevHistoryEntry(self): if self.history: self.history_index = max(0, self.history_index - 1) return self.history[self.history_index] return '' def getNextHistoryEntry(self): if self.history: hist_len = len(self.history) self.history_index = min(hist_len, self.history_index + 1) if self.history_index < hist_len: return self.history[self.history_index] return '' def getCursorPosition(self): c = self.textCursor() return c.position() - c.block().position() - len(self.prompt) def setCursorPosition(self, position): self.moveCursor(QtGui.QTextCursor.StartOfLine) for i in range(len(self.prompt) + position): self.moveCursor(QtGui.QTextCursor.Right) def register_command(self, c, func): methods = { c: func} self.updateNamespace(methods) def runCommand(self): command = self.getCommand() self.addToHistory(command) command = self.getConstruct(command) if command: tmp_stdout = sys.stdout class stdoutProxy(): def __init__(self, write_func): self.write_func = write_func self.skip = False def flush(self): pass def write(self, text): if not self.skip: stripped_text = text.rstrip('\n') self.write_func(stripped_text) QtCore.QCoreApplication.processEvents() self.skip = not self.skip if type(self.namespace.get(command)) == type(lambda:None): self.appendPlainText("'%s' is a function. Type '%s()' to use it in the Python console."%(command, command)) self.newPrompt() return sys.stdout = stdoutProxy(self.appendPlainText) try: try: # eval is generally considered bad practice. use it wisely! result = eval(command, self.namespace, self.namespace) if result != None: if self.is_json: util.print_json(result) else: self.appendPlainText(repr(result)) except SyntaxError: # exec is generally considered bad practice. use it wisely! exec command in self.namespace except SystemExit: self.close() except Exception: traceback_lines = traceback.format_exc().split('\n') # Remove traceback mentioning this file, and a linebreak for i in (3,2,1,-1): traceback_lines.pop(i) self.appendPlainText('\n'.join(traceback_lines)) sys.stdout = tmp_stdout self.newPrompt() self.set_json(False) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Tab: self.completions() return self.hide_completions() if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): self.runCommand() return if event.key() == QtCore.Qt.Key_Home: self.setCursorPosition(0) return if event.key() == QtCore.Qt.Key_PageUp: return elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace): if self.getCursorPosition() == 0: return elif event.key() == QtCore.Qt.Key_Up: self.setCommand(self.getPrevHistoryEntry()) return elif event.key() == QtCore.Qt.Key_Down: self.setCommand(self.getNextHistoryEntry()) return elif event.key() == QtCore.Qt.Key_L and event.modifiers() == QtCore.Qt.ControlModifier: self.clear() super(Console, self).keyPressEvent(event) def completions(self): cmd = self.getCommand() lastword = re.split(' |\(|\)',cmd)[-1] beginning = cmd[0:-len(lastword)] path = lastword.split('.') ns = self.namespace.keys() if len(path) == 1: ns = ns prefix = '' else: obj = self.namespace.get(path[0]) prefix = path[0] + '.' ns = dir(obj) completions = [] for x in ns: if x[0] == '_':continue xx = prefix + x if xx.startswith(lastword): completions.append(xx) completions.sort() if not completions: self.hide_completions() elif len(completions) == 1: self.hide_completions() self.setCommand(beginning + completions[0]) else: # find common prefix p = os.path.commonprefix(completions) if len(p)>len(lastword): self.hide_completions() self.setCommand(beginning + p) else: self.show_completions(completions) welcome_message = ''' --------------------------------------------------------------- Welcome to a primitive Python interpreter. --------------------------------------------------------------- ''' if __name__ == '__main__': app = QtGui.QApplication(sys.argv) console = Console(startup_message=welcome_message) console.updateNamespace({'myVar1' : app, 'myVar2' : 1234}) console.show(); sys.exit(app.exec_())