From fe6d962bee771a34dd4937b6f332e0d0d4114676 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Sep 2010 19:33:17 -0600 Subject: [PATCH] Use dialog instead of main window. Set console stylesheet based on pygments style. Don't block if there is a lot of output --- imgsrc/console.svg | 4339 ++++++++++++++++++++++ resources/images/console.png | Bin 0 -> 5110 bytes src/calibre/utils/pyconsole/__init__.py | 9 + src/calibre/utils/pyconsole/console.py | 117 +- src/calibre/utils/pyconsole/formatter.py | 14 +- src/calibre/utils/pyconsole/main.py | 44 +- 6 files changed, 4482 insertions(+), 41 deletions(-) create mode 100644 imgsrc/console.svg create mode 100644 resources/images/console.png diff --git a/imgsrc/console.svg b/imgsrc/console.svg new file mode 100644 index 0000000000..0d502bb1da --- /dev/null +++ b/imgsrc/console.svg @@ -0,0 +1,4339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/console.png b/resources/images/console.png new file mode 100644 index 0000000000000000000000000000000000000000..168f0ccb2a177087d2ee6157bb67166688c84c05 GIT binary patch literal 5110 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 -1 and restore_cursor: self.cursor_pos = (row, col) + self.ensureCursorVisible() + # }}} # Non-prompt Rendering {{{ @@ -185,16 +237,26 @@ class Console(QTextEdit): self.formatter.render(self.tb_lexer.get_tokens(tb), self.cursor) except: prints(tb, end='') + self.ensureCursorVisible() + QCoreApplication.processEvents() def show_output(self, raw): + def do_show(): + try: + self.buf.append(raw) + self.formatter.render_raw(raw, self.cursor) + except: + import traceback + prints(traceback.format_exc()) + prints(raw, end='') + if self.prompt_frame is not None: - # At a prompt, so redirect output - return prints(raw, end='') - try: - self.buf.append(raw) - self.formatter.render_raw(raw, self.cursor) - except: - prints(raw, end='') + with Prepender(self): + do_show() + else: + do_show() + self.ensureCursorVisible() + QCoreApplication.processEvents() # }}} @@ -203,16 +265,11 @@ class Console(QTextEdit): def keyPressEvent(self, ev): text = unicode(ev.text()) key = ev.key() - if key in (Qt.Key_Enter, Qt.Key_Return): - self.enter_pressed() - elif key == Qt.Key_Home: - self.home_pressed() - elif key == Qt.Key_End: - self.end_pressed() - elif key == Qt.Key_Left: - self.left_pressed() - elif key == Qt.Key_Right: - self.right_pressed() + action = self.key_dispatcher.get(key, None) + if callable(action): + action() + elif key in (Qt.Key_Escape,): + QTextEdit.keyPressEvent(self, ev) elif text: self.text_typed(text) else: @@ -230,6 +287,7 @@ class Console(QTextEdit): c.movePosition(c.Up) c.movePosition(c.EndOfLine) self.setTextCursor(c) + self.ensureCursorVisible() def right_pressed(self): lineno, pos = self.cursor_pos @@ -242,6 +300,7 @@ class Console(QTextEdit): elif lineno < len(cp)-1: c.movePosition(c.NextCharacter, n=1+self.prompt_len) self.setTextCursor(c) + self.ensureCursorVisible() def home_pressed(self): if self.prompt_frame is not None: @@ -249,12 +308,14 @@ class Console(QTextEdit): c.movePosition(c.StartOfLine) c.movePosition(c.NextCharacter, n=self.prompt_len) self.setTextCursor(c) + self.ensureCursorVisible() def end_pressed(self): if self.prompt_frame is not None: c = self.cursor c.movePosition(c.EndOfLine) self.setTextCursor(c) + self.ensureCursorVisible() def enter_pressed(self): if self.prompt_frame is None: @@ -267,7 +328,13 @@ class Console(QTextEdit): self.prompt_frame = None oldbuf = self.buf self.buf = [] - ret = self.interpreter.runsource('\n'.join(cp)) + self.running.emit() + try: + ret = self.interpreter.runsource('\n'.join(cp)) + except SystemExit: + ret = False + self.show_output('Raising SystemExit not allowed\n') + self.running_done.emit() if ret: # Incomplete command self.buf = oldbuf self.prompt_frame = old_pf @@ -275,7 +342,13 @@ class Console(QTextEdit): c.insertBlock() self.setTextCursor(c) else: # Command completed - old_pf.setFrameFormat(QTextFrameFormat()) + try: + old_pf.setFrameFormat(QTextFrameFormat()) + except RuntimeError: + # Happens if enough lines of output that the old + # frame was deleted + pass + self.render_current_prompt() def text_typed(self, text): diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py index 7f99983ef6..9409007ec6 100644 --- a/src/calibre/utils/pyconsole/formatter.py +++ b/src/calibre/utils/pyconsole/formatter.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import QTextCharFormat, QFont, QBrush, QColor from pygments.formatter import Formatter as PF -from pygments.token import Token +from pygments.token import Token, Generic class Formatter(object): @@ -22,11 +22,16 @@ class Formatter(object): pf = PF(**options) self.styles = {} self.normal = self.base_fmt() + self.background_color = pf.style.background_color + self.color = 'black' + for ttype, ndef in pf.style: fmt = self.base_fmt() if ndef['color']: fmt.setForeground(QBrush(QColor('#%s'%ndef['color']))) fmt.setUnderlineColor(QColor('#%s'%ndef['color'])) + if ttype == Generic.Output: + self.color = '#%s'%ndef['color'] if ndef['bold']: fmt.setFontWeight(QFont.Bold) if ndef['italic']: @@ -40,6 +45,11 @@ class Formatter(object): self.styles[ttype] = fmt + self.stylesheet = ''' + QTextEdit { color: %s; background-color: %s } + '''%(self.color, self.background_color) + + def base_fmt(self): fmt = QTextCharFormat() fmt.setFontFamily('monospace') @@ -74,7 +84,7 @@ class Formatter(object): def render_prompt(self, is_continuation, cursor): pr = self.continuation if is_continuation else self.prompt - fmt = self.styles[Token.Generic.Subheading] + fmt = self.styles[Generic.Prompt] cursor.insertText(pr, fmt) diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py index af99ec66bb..f098ce2ee2 100644 --- a/src/calibre/utils/pyconsole/main.py +++ b/src/calibre/utils/pyconsole/main.py @@ -6,19 +6,31 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' __version__ = '0.1.0' -from PyQt4.Qt import QMainWindow, QToolBar, QStatusBar, QLabel, QFont, Qt, \ - QApplication +from functools import partial + +from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \ + QApplication, QIcon, QVBoxLayout from calibre.constants import __appname__, __version__ from calibre.utils.pyconsole.console import Console -class MainWindow(QMainWindow): +class MainWindow(QDialog): - def __init__(self, default_status_msg): + def __init__(self, + default_status_msg=_('Welcome to') + ' ' + __appname__+' console', + parent=None): - QMainWindow.__init__(self) + QDialog.__init__(self, parent) + self.l = QVBoxLayout() + self.setLayout(self.l) - self.resize(600, 700) + self.resize(800, 600) + + # Setup tool bar {{{ + self.tool_bar = QToolBar(self) + self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly) + self.l.addWidget(self.tool_bar) + # }}} # Setup status bar {{{ self.status_bar = QStatusBar(self) @@ -28,25 +40,23 @@ class MainWindow(QMainWindow): self.status_bar._font.setBold(True) self.status_bar.defmsg.setFont(self.status_bar._font) self.status_bar.addWidget(self.status_bar.defmsg) - self.setStatusBar(self.status_bar) # }}} - # Setup tool bar {{{ - self.tool_bar = QToolBar(self) - self.addToolBar(Qt.BottomToolBarArea, self.tool_bar) - self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly) - # }}} - - self.editor = Console(parent=self) - self.setCentralWidget(self.editor) - + self.console = Console(parent=self) + self.console.running.connect(partial(self.status_bar.showMessage, + _('Code is running'))) + self.console.running_done.connect(self.status_bar.clearMessage) + self.l.addWidget(self.console) + self.l.addWidget(self.status_bar) + self.setWindowTitle(__appname__ + ' console') + self.setWindowIcon(QIcon(I('console.png'))) def main(): QApplication.setApplicationName(__appname__+' console') QApplication.setOrganizationName('Kovid Goyal') app = QApplication([]) - m = MainWindow(_('Welcome to') + ' ' + __appname__+' console') + m = MainWindow() m.show() app.exec_()