URI: 
       tconsole.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tconsole.py (11471B)
       ---
            1 
            2 # source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget
            3 
            4 import sys
            5 import os
            6 import re
            7 import traceback
            8 
            9 from PyQt5 import QtCore
           10 from PyQt5 import QtGui
           11 from PyQt5 import QtWidgets
           12 
           13 from electrum import util
           14 from electrum.i18n import _
           15 
           16 from .util import MONOSPACE_FONT
           17 
           18 # sys.ps1 and sys.ps2 are only declared if an interpreter is in interactive mode.
           19 sys.ps1 = '>>> '
           20 sys.ps2 = '... '
           21 
           22 
           23 class OverlayLabel(QtWidgets.QLabel):
           24     STYLESHEET = '''
           25     QLabel, QLabel link {
           26         color: rgb(0, 0, 0);
           27         background-color: rgb(248, 240, 200);
           28         border: 1px solid;
           29         border-color: rgb(255, 114, 47);
           30         padding: 2px;
           31     }
           32     '''
           33     def __init__(self, text, parent):
           34         super().__init__(text, parent)
           35         self.setMinimumHeight(150)
           36         self.setGeometry(0, 0, self.width(), self.height())
           37         self.setStyleSheet(self.STYLESHEET)
           38         self.setMargin(0)
           39         parent.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
           40         self.setWordWrap(True)
           41 
           42     def mousePressEvent(self, e):
           43         self.hide()
           44 
           45     def on_resize(self, w):
           46         padding = 2  # px, from the stylesheet above
           47         self.setFixedWidth(w - padding)
           48 
           49 
           50 class Console(QtWidgets.QPlainTextEdit):
           51     def __init__(self, parent=None):
           52         QtWidgets.QPlainTextEdit.__init__(self, parent)
           53 
           54         self.history = []
           55         self.namespace = {}
           56         self.construct = []
           57 
           58         self.setGeometry(50, 75, 600, 400)
           59         self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere)
           60         self.setUndoRedoEnabled(False)
           61         self.document().setDefaultFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal))
           62         self.newPrompt("")  # make sure there is always a prompt, even before first server.banner
           63 
           64         self.updateNamespace({'run':self.run_script})
           65         self.set_json(False)
           66 
           67         warning_text = "<h1>{}</h1><br>{}<br><br>{}".format(
           68             _("Warning!"),
           69             _("Do not paste code here that you don't understand. Executing the wrong code could lead "
           70               "to your coins being irreversibly lost."),
           71             _("Click here to hide this message.")
           72         )
           73         self.messageOverlay = OverlayLabel(warning_text, self)
           74 
           75     def resizeEvent(self, e):
           76         super().resizeEvent(e)
           77         vertical_scrollbar_width = self.verticalScrollBar().width() * self.verticalScrollBar().isVisible()
           78         self.messageOverlay.on_resize(self.width() - vertical_scrollbar_width)
           79 
           80     def set_json(self, b):
           81         self.is_json = b
           82 
           83     def run_script(self, filename):
           84         with open(filename) as f:
           85             script = f.read()
           86 
           87         self.exec_command(script)
           88 
           89     def updateNamespace(self, namespace):
           90         self.namespace.update(namespace)
           91 
           92     def showMessage(self, message):
           93         curr_line = self.getCommand(strip=False)
           94         self.appendPlainText(message)
           95         self.newPrompt(curr_line)
           96 
           97     def clear(self):
           98         curr_line = self.getCommand()
           99         self.setPlainText('')
          100         self.newPrompt(curr_line)
          101 
          102     def keyboard_interrupt(self):
          103         self.construct = []
          104         self.appendPlainText('KeyboardInterrupt')
          105         self.newPrompt('')
          106 
          107     def newPrompt(self, curr_line):
          108         if self.construct:
          109             prompt = sys.ps2 + curr_line
          110         else:
          111             prompt = sys.ps1 + curr_line
          112 
          113         self.completions_pos = self.textCursor().position()
          114         self.completions_visible = False
          115 
          116         self.appendPlainText(prompt)
          117         self.moveCursor(QtGui.QTextCursor.End)
          118 
          119     def getCommand(self, *, strip=True):
          120         doc = self.document()
          121         curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()
          122         if strip:
          123             curr_line = curr_line.rstrip()
          124         curr_line = curr_line[len(sys.ps1):]
          125         return curr_line
          126 
          127     def setCommand(self, command):
          128         if self.getCommand() == command:
          129             return
          130 
          131         doc = self.document()
          132         curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()
          133         self.moveCursor(QtGui.QTextCursor.End)
          134         for i in range(len(curr_line) - len(sys.ps1)):
          135             self.moveCursor(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor)
          136 
          137         self.textCursor().removeSelectedText()
          138         self.textCursor().insertText(command)
          139         self.moveCursor(QtGui.QTextCursor.End)
          140 
          141     def show_completions(self, completions):
          142         if self.completions_visible:
          143             self.hide_completions()
          144 
          145         c = self.textCursor()
          146         c.setPosition(self.completions_pos)
          147 
          148         completions = map(lambda x: x.split('.')[-1], completions)
          149         t = '\n' + ' '.join(completions)
          150         if len(t) > 500:
          151             t = t[:500] + '...'
          152         c.insertText(t)
          153         self.completions_end = c.position()
          154 
          155         self.moveCursor(QtGui.QTextCursor.End)
          156         self.completions_visible = True
          157 
          158     def hide_completions(self):
          159         if not self.completions_visible:
          160             return
          161         c = self.textCursor()
          162         c.setPosition(self.completions_pos)
          163         l = self.completions_end - self.completions_pos
          164         for x in range(l): c.deleteChar()
          165 
          166         self.moveCursor(QtGui.QTextCursor.End)
          167         self.completions_visible = False
          168 
          169     def getConstruct(self, command):
          170         if self.construct:
          171             self.construct.append(command)
          172             if not command:
          173                 ret_val = '\n'.join(self.construct)
          174                 self.construct = []
          175                 return ret_val
          176             else:
          177                 return ''
          178         else:
          179             if command and command[-1] == (':'):
          180                 self.construct.append(command)
          181                 return ''
          182             else:
          183                 return command
          184 
          185     def addToHistory(self, command):
          186         if not self.construct and command[0:1] == ' ':
          187             return
          188 
          189         if command and (not self.history or self.history[-1] != command):
          190             self.history.append(command)
          191         self.history_index = len(self.history)
          192 
          193     def getPrevHistoryEntry(self):
          194         if self.history:
          195             self.history_index = max(0, self.history_index - 1)
          196             return self.history[self.history_index]
          197         return ''
          198 
          199     def getNextHistoryEntry(self):
          200         if self.history:
          201             hist_len = len(self.history)
          202             self.history_index = min(hist_len, self.history_index + 1)
          203             if self.history_index < hist_len:
          204                 return self.history[self.history_index]
          205         return ''
          206 
          207     def getCursorPosition(self):
          208         c = self.textCursor()
          209         return c.position() - c.block().position() - len(sys.ps1)
          210 
          211     def setCursorPosition(self, position):
          212         self.moveCursor(QtGui.QTextCursor.StartOfLine)
          213         for i in range(len(sys.ps1) + position):
          214             self.moveCursor(QtGui.QTextCursor.Right)
          215 
          216     def run_command(self):
          217         command = self.getCommand()
          218         self.addToHistory(command)
          219 
          220         command = self.getConstruct(command)
          221 
          222         if command:
          223             self.exec_command(command)
          224         self.newPrompt('')
          225         self.set_json(False)
          226 
          227     def exec_command(self, command):
          228         tmp_stdout = sys.stdout
          229 
          230         class stdoutProxy():
          231             def __init__(self, write_func):
          232                 self.write_func = write_func
          233                 self.skip = False
          234 
          235             def flush(self):
          236                 pass
          237 
          238             def write(self, text):
          239                 if not self.skip:
          240                     stripped_text = text.rstrip('\n')
          241                     self.write_func(stripped_text)
          242                     QtCore.QCoreApplication.processEvents()
          243                 self.skip = not self.skip
          244 
          245         if type(self.namespace.get(command)) == type(lambda:None):
          246             self.appendPlainText("'{}' is a function. Type '{}()' to use it in the Python console."
          247                                  .format(command, command))
          248             return
          249 
          250         sys.stdout = stdoutProxy(self.appendPlainText)
          251         try:
          252             try:
          253                 # eval is generally considered bad practice. use it wisely!
          254                 result = eval(command, self.namespace, self.namespace)
          255                 if result is not None:
          256                     if self.is_json:
          257                         util.print_msg(util.json_encode(result))
          258                     else:
          259                         self.appendPlainText(repr(result))
          260             except SyntaxError:
          261                 # exec is generally considered bad practice. use it wisely!
          262                 exec(command, self.namespace, self.namespace)
          263         except SystemExit:
          264             self.close()
          265         except BaseException:
          266             traceback_lines = traceback.format_exc().split('\n')
          267             # Remove traceback mentioning this file, and a linebreak
          268             for i in (3,2,1,-1):
          269                 traceback_lines.pop(i)
          270             self.appendPlainText('\n'.join(traceback_lines))
          271         sys.stdout = tmp_stdout
          272 
          273     def keyPressEvent(self, event):
          274         if event.key() == QtCore.Qt.Key_Tab:
          275             self.completions()
          276             return
          277 
          278         self.hide_completions()
          279 
          280         if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
          281             self.run_command()
          282             return
          283         if event.key() == QtCore.Qt.Key_Home:
          284             self.setCursorPosition(0)
          285             return
          286         if event.key() == QtCore.Qt.Key_PageUp:
          287             return
          288         elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace):
          289             if self.getCursorPosition() == 0:
          290                 return
          291         elif event.key() == QtCore.Qt.Key_Up:
          292             self.setCommand(self.getPrevHistoryEntry())
          293             return
          294         elif event.key() == QtCore.Qt.Key_Down:
          295             self.setCommand(self.getNextHistoryEntry())
          296             return
          297         elif event.key() == QtCore.Qt.Key_L and event.modifiers() == QtCore.Qt.ControlModifier:
          298             self.clear()
          299         elif event.key() == QtCore.Qt.Key_C and event.modifiers() == QtCore.Qt.ControlModifier:
          300             if not self.textCursor().selectedText():
          301                 self.keyboard_interrupt()
          302 
          303         super(Console, self).keyPressEvent(event)
          304 
          305     def completions(self):
          306         cmd = self.getCommand()
          307         # note for regex: new words start after ' ' or '(' or ')'
          308         lastword = re.split(r'[ ()]', cmd)[-1]
          309         beginning = cmd[0:-len(lastword)]
          310 
          311         path = lastword.split('.')
          312         prefix = '.'.join(path[:-1])
          313         prefix = (prefix + '.') if prefix else prefix
          314         ns = self.namespace.keys()
          315 
          316         if len(path) == 1:
          317             ns = ns
          318         else:
          319             assert len(path) > 1
          320             obj = self.namespace.get(path[0])
          321             try:
          322                 for attr in path[1:-1]:
          323                     obj = getattr(obj, attr)
          324             except AttributeError:
          325                 ns = []
          326             else:
          327                 ns = dir(obj)
          328 
          329         completions = []
          330         for name in ns:
          331             if name[0] == '_':continue
          332             if name.startswith(path[-1]):
          333                 completions.append(prefix+name)
          334         completions.sort()
          335 
          336         if not completions:
          337             self.hide_completions()
          338         elif len(completions) == 1:
          339             self.hide_completions()
          340             self.setCommand(beginning + completions[0])
          341         else:
          342             # find common prefix
          343             p = os.path.commonprefix(completions)
          344             if len(p)>len(lastword):
          345                 self.hide_completions()
          346                 self.setCommand(beginning + p)
          347             else:
          348                 self.show_completions(completions)