Console now has history

This commit is contained in:
Kovid Goyal 2010-09-23 01:02:03 -06:00
parent 9b44f55785
commit d9e5e74695
4 changed files with 61 additions and 7 deletions

View File

@ -24,6 +24,8 @@ def console_config():
c = Config('console', desc) c = Config('console', desc)
c.add_opt('theme', default='native', help='The color theme') c.add_opt('theme', default='native', help='The color theme')
c.add_opt('scrollback', default=10000,
help='Max number of lines to keep in the scrollback buffer')
return c return c

View File

@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
import sys, textwrap, traceback, StringIO import sys, textwrap, traceback, StringIO
from functools import partial from functools import partial
from codeop import CommandCompiler
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \ from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer
@ -16,8 +17,9 @@ from pygments.styles import get_all_styles
from calibre.utils.pyconsole.formatter import Formatter from calibre.utils.pyconsole.formatter import Formatter
from calibre.utils.pyconsole.controller import Controller from calibre.utils.pyconsole.controller import Controller
from calibre.utils.pyconsole.history import History
from calibre.utils.pyconsole import prints, prefs, __appname__, \ from calibre.utils.pyconsole import prints, prefs, __appname__, \
__version__, error_dialog __version__, error_dialog, dynamic
class EditBlock(object): # {{{ class EditBlock(object): # {{{
@ -73,6 +75,7 @@ class ThemeMenu(QMenu): # {{{
# }}} # }}}
class Console(QTextEdit): class Console(QTextEdit):
running = pyqtSignal() running = pyqtSignal()
@ -114,7 +117,9 @@ class Console(QTextEdit):
parent=None): parent=None):
QTextEdit.__init__(self, parent) QTextEdit.__init__(self, parent)
self.shutting_down = False self.shutting_down = False
self.compiler = CommandCompiler()
self.buf = self.old_buf = [] self.buf = self.old_buf = []
self.history = History([''], dynamic.get('console_history', []))
self.prompt_frame = None self.prompt_frame = None
self.allow_output = False self.allow_output = False
self.prompt_frame_format = QTextFrameFormat() self.prompt_frame_format = QTextFrameFormat()
@ -122,7 +127,7 @@ class Console(QTextEdit):
self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid) self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
self.prompt_len = len(prompt) self.prompt_len = len(prompt)
self.doc.setMaximumBlockCount(10000) self.doc.setMaximumBlockCount(int(prefs['scrollback']))
self.lexer = PythonLexer(ensurenl=False) self.lexer = PythonLexer(ensurenl=False)
self.tb_lexer = PythonTracebackLexer() self.tb_lexer = PythonTracebackLexer()
@ -139,6 +144,8 @@ class Console(QTextEdit):
self.key_dispatcher = { # {{{ self.key_dispatcher = { # {{{
Qt.Key_Enter : self.enter_pressed, Qt.Key_Enter : self.enter_pressed,
Qt.Key_Return : self.enter_pressed, Qt.Key_Return : self.enter_pressed,
Qt.Key_Up : self.up_pressed,
Qt.Key_Down : self.down_pressed,
Qt.Key_Home : self.home_pressed, Qt.Key_Home : self.home_pressed,
Qt.Key_End : self.end_pressed, Qt.Key_End : self.end_pressed,
Qt.Key_Left : self.left_pressed, Qt.Key_Left : self.left_pressed,
@ -153,15 +160,17 @@ class Console(QTextEdit):
'''.format(sys.version.splitlines()[0], __appname__, '''.format(sys.version.splitlines()[0], __appname__,
__version__)) __version__))
sys.excepthook = self.unhandled_exception
self.controllers = [] self.controllers = []
QTimer.singleShot(0, self.launch_controller) QTimer.singleShot(0, self.launch_controller)
sys.excepthook = self.unhandled_exception
with EditBlock(self.cursor): with EditBlock(self.cursor):
self.render_block(motd) self.render_block(motd)
def shutdown(self): def shutdown(self):
dynamic.set('console_history', self.history.serialize())
self.shutton_down = True self.shutton_down = True
for c in self.controllers: for c in self.controllers:
c.kill() c.kill()
@ -365,7 +374,7 @@ class Console(QTextEdit):
# }}} # }}}
# Keyboard handling {{{ # Keyboard management {{{
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
text = unicode(ev.text()) text = unicode(ev.text())
@ -394,6 +403,20 @@ class Console(QTextEdit):
self.setTextCursor(c) self.setTextCursor(c)
self.ensureCursorVisible() self.ensureCursorVisible()
def up_pressed(self):
lineno, pos = self.cursor_pos
if lineno < 0: return
if lineno == 0:
b = self.history.back()
if b is not None:
self.set_prompt(b)
else:
c = self.cursor
c.movePosition(c.Up)
self.setTextCursor(c)
self.ensureCursorVisible()
def backspace_pressed(self): def backspace_pressed(self):
lineno, pos = self.cursor_pos lineno, pos = self.cursor_pos
if lineno < 0: return if lineno < 0: return
@ -414,7 +437,6 @@ class Console(QTextEdit):
lineno, pos = self.cursor_pos lineno, pos = self.cursor_pos
if lineno < 0: return if lineno < 0: return
c = self.cursor c = self.cursor
lineno, pos = self.cursor_pos
cp = list(self.prompt(False)) cp = list(self.prompt(False))
if pos < len(cp[lineno]): if pos < len(cp[lineno]):
c.movePosition(c.NextCharacter) c.movePosition(c.NextCharacter)
@ -423,6 +445,22 @@ class Console(QTextEdit):
self.setTextCursor(c) self.setTextCursor(c)
self.ensureCursorVisible() self.ensureCursorVisible()
def down_pressed(self):
lineno, pos = self.cursor_pos
if lineno < 0: return
c = self.cursor
cp = list(self.prompt(False))
if lineno >= len(cp) - 1:
b = self.history.forward()
if b is not None:
self.set_prompt(b)
else:
c = self.cursor
c.movePosition(c.Down)
self.setTextCursor(c)
self.ensureCursorVisible()
def home_pressed(self): def home_pressed(self):
if self.prompt_frame is not None: if self.prompt_frame is not None:
mods = QApplication.keyboardModifiers() mods = QApplication.keyboardModifiers()
@ -454,6 +492,19 @@ class Console(QTextEdit):
return self.no_controller_error() return self.no_controller_error()
cp = list(self.prompt()) cp = list(self.prompt())
if cp[0]: if cp[0]:
try:
ret = self.compiler('\n'.join(cp))
except:
pass
else:
if ret is None:
c = self.prompt_frame.lastCursorPosition()
c.insertBlock()
self.setTextCursor(c)
self.render_current_prompt()
return
else:
self.history.enter(cp)
self.execute(cp) self.execute(cp)
def text_typed(self, text): def text_typed(self, text):
@ -461,6 +512,7 @@ class Console(QTextEdit):
self.move_cursor_to_prompt() self.move_cursor_to_prompt()
self.cursor.insertText(text) self.cursor.insertText(text)
self.render_current_prompt(restore_cursor=True) self.render_current_prompt(restore_cursor=True)
self.history.current = list(self.prompt())
# }}} # }}}

View File

@ -104,7 +104,6 @@ class Controller(QThread):
def returncode(self): def returncode(self):
return self.process.returncode return self.process.returncode
@property
def interrupt(self): def interrupt(self):
if hasattr(signal, 'SIGINT'): if hasattr(signal, 'SIGINT'):
os.kill(self.process.pid, signal.SIGINT) os.kill(self.process.pid, signal.SIGINT)

View File

@ -11,6 +11,7 @@ from Queue import Queue, Empty
from threading import Thread from threading import Thread
from binascii import unhexlify from binascii import unhexlify
from multiprocessing.connection import Client from multiprocessing.connection import Client
from repr import repr as safe_repr
from calibre.utils.pyconsole import preferred_encoding, isbytestring, \ from calibre.utils.pyconsole import preferred_encoding, isbytestring, \
POLL_TIMEOUT POLL_TIMEOUT
@ -35,7 +36,7 @@ def tounicode(raw): # {{{
try: try:
raw = raw.decode(preferred_encoding, 'replace') raw = raw.decode(preferred_encoding, 'replace')
except: except:
raw = repr(raw) raw = safe_repr(raw)
if isbytestring(raw): if isbytestring(raw):
try: try: