From c4061147220f8130239e8d12836a8473b37d4f2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 16:07:39 -0600 Subject: [PATCH 1/7] Fix #6914 (Calibre misfiles books (during a rename?)) --- src/calibre/library/database2.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 1a2eef2c81..08d9dc3edd 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -451,6 +451,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): parent = os.path.dirname(spath) if len(os.listdir(parent)) == 0: self.rmtree(parent, permanent=True) + ''' + This is commented out as it can lead to book file loss if the second + rename fails. This makes it not worthwhile, IMO. + curpath = self.library_path c1, c2 = current_path.split('/'), path.split('/') if not self.is_case_sensitive and len(c1) == len(c2): @@ -473,6 +477,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): os.rename(os.path.join(curpath, oldseg), tempname) os.rename(tempname, os.path.join(curpath, newseg)) curpath = os.path.join(curpath, newseg) + ''' def add_listener(self, listener): ''' From 7e233696a1c2bbe3ca4c0469a99540da665fc94f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 16:11:53 -0600 Subject: [PATCH 2/7] ... --- src/calibre/manual/tutorials.rst | 1 + src/calibre/utils/pyconsole/__init__.py | 4 ++-- src/calibre/utils/pyconsole/main.py | 22 +++++++++++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/calibre/manual/tutorials.rst b/src/calibre/manual/tutorials.rst index d07316deb9..1e4cab8493 100644 --- a/src/calibre/manual/tutorials.rst +++ b/src/calibre/manual/tutorials.rst @@ -11,6 +11,7 @@ Here you will find tutorials to get you started using |app|'s more advanced feat .. toctree:: :maxdepth: 1 + news xpath template_lang portable diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py index 32eb926143..cf7e5eab50 100644 --- a/src/calibre/utils/pyconsole/__init__.py +++ b/src/calibre/utils/pyconsole/__init__.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import sys from calibre import prints as prints_ -from calibre.utils.config import Config, ConfigProxy +from calibre.utils.config import Config, ConfigProxy, JSONConfig def console_config(): @@ -20,7 +20,7 @@ def console_config(): return c prefs = ConfigProxy(console_config()) - +dynamic = JSONConfig('console') def prints(*args, **kwargs): kwargs['file'] = sys.__stdout__ diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py index a708ca1652..4027897ddd 100644 --- a/src/calibre/utils/pyconsole/main.py +++ b/src/calibre/utils/pyconsole/main.py @@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \ QApplication, QIcon, QVBoxLayout, QAction from calibre.constants import __appname__, __version__ +from calibre.utils.pyconsole import dynamic from calibre.utils.pyconsole.console import Console class MainWindow(QDialog): @@ -26,6 +27,9 @@ class MainWindow(QDialog): self.setLayout(self.l) self.resize(800, 600) + geom = dynamic.get('console_window_geometry', None) + if geom is not None: + self.restoreGeometry(geom) # Setup tool bar {{{ self.tool_bar = QToolBar(self) @@ -62,17 +66,25 @@ class MainWindow(QDialog): self.restart_requested = True self.reject() -def main(): - QApplication.setApplicationName(__appname__+' console') - QApplication.setOrganizationName('Kovid Goyal') - app = QApplication([]) - app + def closeEvent(self, *args): + dynamic.set('console_window_geometry', + bytearray(self.saveGeometry())) + return QDialog.closeEvent(self, *args) + + +def show(): while True: m = MainWindow() m.exec_() if not m.restart_requested: break +def main(): + QApplication.setApplicationName(__appname__+' console') + QApplication.setOrganizationName('Kovid Goyal') + app = QApplication([]) + app + show() if __name__ == '__main__': main() From 2bd3bda0fe3df008af642d69b5aa4e6019767d43 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 16:41:09 -0600 Subject: [PATCH 3/7] Restore case renaming code, but in a more robust form --- src/calibre/library/database2.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 08d9dc3edd..627ab6358b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -451,9 +451,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): parent = os.path.dirname(spath) if len(os.listdir(parent)) == 0: self.rmtree(parent, permanent=True) - ''' - This is commented out as it can lead to book file loss if the second - rename fails. This makes it not worthwhile, IMO. curpath = self.library_path c1, c2 = current_path.split('/'), path.split('/') @@ -469,15 +466,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # the directories, so no need to do them here. for oldseg, newseg in zip(c1, c2): if oldseg.lower() == newseg.lower() and oldseg != newseg: - while True: - # need a temp name in the current segment for renames - tempname = os.path.join(curpath, 'TEMP.%f'%time.time()) - if not os.path.exists(tempname): - break - os.rename(os.path.join(curpath, oldseg), tempname) - os.rename(tempname, os.path.join(curpath, newseg)) + try: + os.rename(os.path.join(curpath, oldseg), os.path.join(curpath, newseg)) + except: + break # Fail silently since nothing catastrophic has happened curpath = os.path.join(curpath, newseg) - ''' def add_listener(self, listener): ''' From 69e4cb006545c9db1bc7704c4363a9a111d9c3c1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 17:05:35 -0600 Subject: [PATCH 4/7] ... --- src/calibre/utils/pyconsole/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py index cf7e5eab50..b3f811faca 100644 --- a/src/calibre/utils/pyconsole/__init__.py +++ b/src/calibre/utils/pyconsole/__init__.py @@ -9,6 +9,7 @@ import sys from calibre import prints as prints_ from calibre.utils.config import Config, ConfigProxy, JSONConfig +from calibre.utils.ipc.launch import Worker def console_config(): @@ -26,4 +27,6 @@ def prints(*args, **kwargs): kwargs['file'] = sys.__stdout__ prints_(*args, **kwargs) +class Process(Worker): + pass From c5e26ad9d5660aebdc21afc57e0626e83b2c0b92 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 21:43:08 -0600 Subject: [PATCH 5/7] Refactor console to run interpreter in separate process --- src/calibre/utils/pyconsole/__init__.py | 3 + src/calibre/utils/pyconsole/console.py | 114 ++++++++----- src/calibre/utils/pyconsole/controller.py | 125 +++++++++++++++ src/calibre/utils/pyconsole/interpreter.py | 177 +++++++++++++++++++++ src/calibre/utils/pyconsole/main.py | 1 + src/calibre/utils/pyconsole/repl.py | 67 -------- 6 files changed, 381 insertions(+), 106 deletions(-) create mode 100644 src/calibre/utils/pyconsole/controller.py create mode 100644 src/calibre/utils/pyconsole/interpreter.py delete mode 100644 src/calibre/utils/pyconsole/repl.py diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py index 3b32a5a9f3..3be9382413 100644 --- a/src/calibre/utils/pyconsole/__init__.py +++ b/src/calibre/utils/pyconsole/__init__.py @@ -13,6 +13,9 @@ from calibre.utils.ipc.launch import Worker from calibre.constants import __appname__, __version__, iswindows from calibre.gui2 import error_dialog +# Time to wait for communication to/from the interpreter process +POLL_TIMEOUT = 0.01 # seconds + preferred_encoding, isbytestring, __appname__, __version__, error_dialog, \ iswindows diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index 77c8d9a2f6..1acb6e96a9 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -9,13 +9,13 @@ import sys, textwrap, traceback, StringIO from functools import partial from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \ - QApplication, QColor, QPalette, QMenu, QActionGroup + QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer from pygments.lexers import PythonLexer, PythonTracebackLexer from pygments.styles import get_all_styles from calibre.utils.pyconsole.formatter import Formatter -from calibre.utils.pyconsole.repl import Interpreter, DummyFile +from calibre.utils.pyconsole.controller import Controller from calibre.utils.pyconsole import prints, prefs, __appname__, \ __version__, error_dialog @@ -113,7 +113,8 @@ class Console(QTextEdit): continuation='... ', parent=None): QTextEdit.__init__(self, parent) - self.buf = [] + self.shutting_down = False + self.buf = self.old_buf = [] self.prompt_frame = None self.allow_output = False self.prompt_frame_format = QTextFrameFormat() @@ -152,20 +153,80 @@ class Console(QTextEdit): '''.format(sys.version.splitlines()[0], __appname__, __version__)) + self.controllers = [] + QTimer.singleShot(0, self.launch_controller) + + sys.excepthook = self.unhandled_exception + with EditBlock(self.cursor): self.render_block(motd) - sys.stdout = sys.stderr = DummyFile(parent=self) - sys.stdout.write_output.connect(self.show_output) - self.interpreter = Interpreter(parent=self) - self.interpreter.show_error.connect(self.show_error) - - sys.excepthook = self.unhandled_exception + def shutdown(self): + self.shutton_down = True + for c in self.controllers: + c.kill() def contextMenuEvent(self, event): self.context_menu.popup(event.globalPos()) event.accept() + # Controller management {{{ + @property + def controller(self): + return self.controllers[-1] + + def no_controller_error(self): + error_dialog(self, _('No interpreter'), + _('No active interpreter found. Try restarting the' + ' console'), show=True) + + def launch_controller(self, *args): + c = Controller(self) + c.write_output.connect(self.show_output, type=Qt.QueuedConnection) + c.show_error.connect(self.show_error, type=Qt.QueuedConnection) + c.interpreter_died.connect(self.interpreter_died, + type=Qt.QueuedConnection) + c.interpreter_done.connect(self.execution_done) + self.controllers.append(c) + + def interpreter_died(self, controller, returncode): + if not self.shutting_down and controller.current_command is not None: + error_dialog(self, _('Interpreter died'), + _('Interpreter dies while excuting a command. To see ' + 'the command, click Show details'), + det_msg=controller.current_command, show=True) + + def execute(self, prompt_lines): + c = self.root_frame.lastCursorPosition() + self.setTextCursor(c) + self.old_prompt_frame = self.prompt_frame + self.prompt_frame = None + self.old_buf = self.buf + self.buf = [] + self.running.emit() + self.controller.runsource('\n'.join(prompt_lines)) + + def execution_done(self, controller, ret): + if controller is self.controller: + self.running_done.emit() + if ret: # Incomplete command + self.buf = self.old_buf + self.prompt_frame = self.old_prompt_frame + c = self.prompt_frame.lastCursorPosition() + c.insertBlock() + self.setTextCursor(c) + else: # Command completed + try: + self.old_prompt_frame.setFrameFormat(QTextFrameFormat()) + except RuntimeError: + # Happens if enough lines of output that the old + # frame was deleted + pass + + self.render_current_prompt() + + # }}} + # Prompt management {{{ @dynamic_property @@ -264,7 +325,7 @@ class Console(QTextEdit): if restore_prompt: self.render_current_prompt() - def show_error(self, is_syntax_err, tb): + def show_error(self, is_syntax_err, tb, controller=None): if self.prompt_frame is not None: # At a prompt, so redirect output return prints(tb, end='') @@ -279,7 +340,7 @@ class Console(QTextEdit): self.ensureCursorVisible() QApplication.processEvents() - def show_output(self, raw): + def show_output(self, raw, which='stdout', controller=None): def do_show(): try: self.buf.append(raw) @@ -384,36 +445,11 @@ class Console(QTextEdit): def enter_pressed(self): if self.prompt_frame is None: return + if not self.controller.is_alive: + return self.no_controller_error() cp = list(self.prompt()) if cp[0]: - c = self.root_frame.lastCursorPosition() - self.setTextCursor(c) - old_pf = self.prompt_frame - self.prompt_frame = None - oldbuf = self.buf - self.buf = [] - 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 - c = old_pf.lastCursorPosition() - c.insertBlock() - self.setTextCursor(c) - else: # Command completed - try: - old_pf.setFrameFormat(QTextFrameFormat()) - except RuntimeError: - # Happens if enough lines of output that the old - # frame was deleted - pass - - self.render_current_prompt() + self.execute(cp) def text_typed(self, text): if self.prompt_frame is not None: diff --git a/src/calibre/utils/pyconsole/controller.py b/src/calibre/utils/pyconsole/controller.py new file mode 100644 index 0000000000..173881d14e --- /dev/null +++ b/src/calibre/utils/pyconsole/controller.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, cPickle, signal, time +from Queue import Queue, Empty +from multiprocessing.connection import Listener, arbitrary_address +from binascii import hexlify + +from PyQt4.Qt import QThread, pyqtSignal + +from calibre.utils.pyconsole import Process, iswindows, POLL_TIMEOUT + +class Controller(QThread): + + # show_error(is_syntax_error, traceback, self) + show_error = pyqtSignal(object, object, object) + # write_output(unicode_object, stdout or stderr, self) + write_output = pyqtSignal(object, object, object) + # Indicates interpreter has finished evaluating current command + interpreter_done = pyqtSignal(object, object) + # interpreter_died(self, returncode or None if no return code available) + interpreter_died = pyqtSignal(object, object) + + def __init__(self, parent): + QThread.__init__(self, parent) + self.keep_going = True + self.current_command = None + + self.out_queue = Queue() + self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX') + self.auth_key = os.urandom(32) + if iswindows and self.address[1] == ':': + self.address = self.address[2:] + self.listener = Listener(address=self.address, + authkey=self.auth_key, backlog=4) + + self.env = { + 'CALIBRE_LAUNCH_INTERPRETER': '1', + 'CALIBRE_WORKER_ADDRESS': + hexlify(cPickle.dumps(self.listener.address, -1)), + 'CALIBRE_WORKER_KEY': hexlify(self.auth_key) + } + self.process = Process(self.env) + self.output_file_buf = self.process(redirect_output=False) + self.conn = self.listener.accept() + self.start() + + def run(self): + while self.keep_going and self.is_alive: + try: + self.communicate() + except KeyboardInterrupt: + pass + except EOFError: + break + self.interpreter_died.emit(self, self.returncode) + try: + self.listener.close() + except: + pass + + def communicate(self): + if self.conn.poll(POLL_TIMEOUT): + self.dispatch_incoming_message(self.conn.recv()) + try: + obj = self.out_queue.get_nowait() + except Empty: + pass + else: + try: + self.conn.send(obj) + except: + raise EOFError('controller failed to send') + + def dispatch_incoming_message(self, obj): + try: + cmd, data = obj + except: + print 'Controller received invalid message' + print repr(obj) + return + if cmd in ('stdout', 'stderr'): + self.write_output.emit(data, cmd, self) + elif cmd == 'syntaxerror': + self.show_error.emit(True, data, self) + elif cmd == 'traceback': + self.show_error(self, False, data) + elif cmd == 'done': + self.current_command = None + self.interpreter_done.emit(self, data) + + def runsource(self, cmd): + self.current_command = cmd + self.out_queue.put(('run', cmd)) + + def __nonzero__(self): + return self.process.is_alive + + @property + def returncode(self): + return self.process.returncode + + @property + def interrupt(self): + if hasattr(signal, 'SIGINT'): + os.kill(self.process.pid, signal.SIGINT) + elif hasattr(signal, 'CTRL_C_EVENT'): + os.kill(self.process.pid, signal.CTRL_C_EVENT) + + @property + def is_alive(self): + return self.process.is_alive + + def kill(self): + self.out_queue.put(('quit', 0)) + t = 0 + while self.is_alive and t < 10: + time.sleep(0.1) + self.process.kill() + self.keep_going = False + diff --git a/src/calibre/utils/pyconsole/interpreter.py b/src/calibre/utils/pyconsole/interpreter.py new file mode 100644 index 0000000000..6a1aff26c9 --- /dev/null +++ b/src/calibre/utils/pyconsole/interpreter.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, cPickle, os +from code import InteractiveInterpreter +from Queue import Queue, Empty +from threading import Thread +from binascii import unhexlify +from multiprocessing.connection import Client + +from calibre.utils.pyconsole import preferred_encoding, isbytestring, \ + POLL_TIMEOUT + +''' +Messages sent by client: + + (stdout, unicode) + (stderr, unicode) + (syntaxerror, unicode) + (traceback, unicode) + (done, True iff incomplete command) + +Messages that can be received by client: + (quit, return code) + (run, unicode) + +''' + +def tounicode(raw): # {{{ + if isbytestring(raw): + try: + raw = raw.decode(preferred_encoding, 'replace') + except: + raw = repr(raw) + + if isbytestring(raw): + try: + raw.decode('utf-8', 'replace') + except: + raw = u'Undecodable bytestring' + return raw +# }}} + +class DummyFile(object): # {{{ + + def __init__(self, what, out_queue): + self.closed = False + self.name = 'console' + self.softspace = 0 + self.what = what + self.out_queue = out_queue + + def flush(self): + pass + + def close(self): + pass + + def write(self, raw): + self.out_queue.put((self.what, tounicode(raw))) +# }}} + +class Comm(Thread): # {{{ + + def __init__(self, conn, out_queue, in_queue): + Thread.__init__(self) + self.daemon = True + self.conn = conn + self.out_queue = out_queue + self.in_queue = in_queue + self.keep_going = True + + def run(self): + while self.keep_going: + try: + self.communicate() + except KeyboardInterrupt: + pass + except EOFError: + pass + + def communicate(self): + if self.conn.poll(POLL_TIMEOUT): + try: + obj = self.conn.recv() + except: + pass + else: + self.in_queue.put(obj) + try: + obj = self.out_queue.get_nowait() + except Empty: + pass + else: + try: + self.conn.send(obj) + except: + raise EOFError('interpreter failed to send') +# }}} + +class Interpreter(InteractiveInterpreter): # {{{ + + def __init__(self, queue, local={}): + if '__name__' not in local: + local['__name__'] = '__console__' + if '__doc__' not in local: + local['__doc__'] = None + self.out_queue = queue + sys.stdout = DummyFile('stdout', queue) + sys.stderr = DummyFile('sdterr', queue) + InteractiveInterpreter.__init__(self, locals=local) + + def showtraceback(self, *args, **kwargs): + self.is_syntax_error = False + InteractiveInterpreter.showtraceback(self, *args, **kwargs) + + def showsyntaxerror(self, *args, **kwargs): + self.is_syntax_error = True + InteractiveInterpreter.showsyntaxerror(self, *args, **kwargs) + + def write(self, raw): + what = 'syntaxerror' if self.is_syntax_error else 'traceback' + self.out_queue.put((what, tounicode(raw))) + +# }}} + +def connect(): + os.chdir(os.environ['ORIGWD']) + address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS'])) + key = unhexlify(os.environ['CALIBRE_WORKER_KEY']) + return Client(address, authkey=key) + +def main(): + out_queue = Queue() + in_queue = Queue() + conn = connect() + comm = Comm(conn, out_queue, in_queue) + comm.start() + interpreter = Interpreter(out_queue) + + ret = 0 + + while True: + try: + try: + cmd, data = in_queue.get(1) + except Empty: + pass + else: + if cmd == 'quit': + ret = data + comm.keep_going = False + comm.join() + break + elif cmd == 'run': + if not comm.is_alive(): + ret = 1 + break + ret = False + try: + ret = interpreter.runsource(data) + except KeyboardInterrupt: + pass + except SystemExit: + out_queue.put(('stderr', 'SystemExit ignored\n')) + out_queue.put(('done', ret)) + except KeyboardInterrupt: + pass + + return ret + +if __name__ == '__main__': + main() diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py index a64bc15ec7..664f41ef2e 100644 --- a/src/calibre/utils/pyconsole/main.py +++ b/src/calibre/utils/pyconsole/main.py @@ -68,6 +68,7 @@ class MainWindow(QDialog): def closeEvent(self, *args): dynamic.set('console_window_geometry', bytearray(self.saveGeometry())) + self.console.shutdown() return QDialog.closeEvent(self, *args) diff --git a/src/calibre/utils/pyconsole/repl.py b/src/calibre/utils/pyconsole/repl.py deleted file mode 100644 index de6262de14..0000000000 --- a/src/calibre/utils/pyconsole/repl.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from code import InteractiveInterpreter - -from PyQt4.Qt import QObject, pyqtSignal - -from calibre import isbytestring -from calibre.constants import preferred_encoding - -class Interpreter(QObject, InteractiveInterpreter): - - # show_error(is_syntax_error, traceback) - show_error = pyqtSignal(object, object) - - def __init__(self, local={}, parent=None): - QObject.__init__(self, parent) - if '__name__' not in local: - local['__name__'] = '__console__' - if '__doc__' not in local: - local['__doc__'] = None - InteractiveInterpreter.__init__(self, locals=local) - - def showtraceback(self, *args, **kwargs): - self.is_syntax_error = False - InteractiveInterpreter.showtraceback(self, *args, **kwargs) - - def showsyntaxerror(self, *args, **kwargs): - self.is_syntax_error = True - InteractiveInterpreter.showsyntaxerror(self, *args, **kwargs) - - def write(self, tb): - self.show_error.emit(self.is_syntax_error, tb) - -class DummyFile(QObject): - - # write_output(unicode_object) - write_output = pyqtSignal(object) - - def __init__(self, parent=None): - QObject.__init__(self, parent) - self.closed = False - self.name = 'console' - self.softspace = 0 - - def flush(self): - pass - - def close(self): - pass - - def write(self, raw): - #import sys, traceback - #print >> sys.__stdout__, 'file,write stack:\n', ''.join(traceback.format_stack()) - if isbytestring(raw): - try: - raw = raw.decode(preferred_encoding, 'replace') - except: - raw = repr(raw) - if isbytestring(raw): - raw = raw.decode(preferred_encoding, 'replace') - self.write_output.emit(raw) - From 950f592b87173daaeb84244560c276adb0cb49f8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 21:48:17 -0600 Subject: [PATCH 6/7] ... --- src/calibre/utils/pyconsole/console.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index 1acb6e96a9..2611965345 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -268,6 +268,11 @@ class Console(QTextEdit): return property(fget=fget, fset=fset, doc=doc) + def move_cursor_to_prompt(self): + if self.prompt_frame is not None and self.cursor_pos[0] < 0: + c = self.prompt_frame.lastCursorPosition() + self.setTextCursor(c) + def prompt(self, strip_prompt_strings=True): if not self.prompt_frame: yield u'' if strip_prompt_strings else self.formatter.prompt @@ -453,6 +458,7 @@ class Console(QTextEdit): def text_typed(self, text): if self.prompt_frame is not None: + self.move_cursor_to_prompt() self.cursor.insertText(text) self.render_current_prompt(restore_cursor=True) From 9b44f557850a7cdbceac98194cc9bda77d76330c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Sep 2010 21:54:58 -0600 Subject: [PATCH 7/7] ... --- src/calibre/utils/pyconsole/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/pyconsole/controller.py b/src/calibre/utils/pyconsole/controller.py index 173881d14e..368e665079 100644 --- a/src/calibre/utils/pyconsole/controller.py +++ b/src/calibre/utils/pyconsole/controller.py @@ -88,7 +88,7 @@ class Controller(QThread): elif cmd == 'syntaxerror': self.show_error.emit(True, data, self) elif cmd == 'traceback': - self.show_error(self, False, data) + self.show_error.emit(False, data, self) elif cmd == 'done': self.current_command = None self.interpreter_done.emit(self, data)