debug-console: Handle I/O and exceptions.

This commit is contained in:
Florian Bruhin 2014-08-13 06:09:18 +02:00
parent 103a81a976
commit 2e760a92cf
2 changed files with 65 additions and 21 deletions

View File

@ -20,6 +20,7 @@
"""Other utilities which don't fit anywhere else. """
import os
import io
import sys
import shlex
import os.path
@ -27,6 +28,7 @@ import urllib.request
from urllib.parse import urljoin, urlencode
from collections import OrderedDict
from functools import reduce
from contextlib import contextmanager
from PyQt5.QtCore import QCoreApplication, QStandardPaths, Qt
from PyQt5.QtGui import QKeySequence, QColor
@ -439,3 +441,53 @@ def normalize_keystr(keystr):
for mod in ('Ctrl', 'Meta', 'Alt', 'Shift'):
keystr = keystr.replace(mod + '-', mod + '+')
return keystr.lower()
class FakeIOStream(io.TextIOBase):
"""A fake file-like stream which calls a function for write-calls."""
def __init__(self, write_func):
self.write = write_func
def flush(self):
"""This is only here to satisfy pylint."""
return super().flush()
def isatty(self):
"""This is only here to satisfy pylint."""
return super().isatty()
@contextmanager
def fake_io(write_func):
"""Run code with stdout and stderr replaced by FakeIOStreams.
Args:
write_func: The function to call when write is called.
"""
old_stdout = sys.stdout
old_stderr = sys.stderr
fake_stderr = FakeIOStream(write_func)
fake_stdout = FakeIOStream(write_func)
sys.stderr = fake_stderr
sys.stdout = fake_stdout
yield
# If the code we did run did change sys.stdout/sys.stderr, we leave it
# unchanged. Otherwise, we reset it.
if sys.stdout is fake_stdout:
sys.stdout = old_stdout
if sys.stderr is fake_stderr:
sys.stderr = old_stderr
@contextmanager
def disabled_excepthook():
"""Run code with the exception hook temporarely disabled."""
old_excepthook = sys.excepthook
sys.excepthook = sys.__excepthook__
yield
# If the code we did run did change sys.excepthook, we leave it
# unchanged. Otherwise, we reset it.
if sys.excepthook is sys.__excepthook__:
sys.excepthook = old_excepthook

View File

@ -21,27 +21,12 @@
from code import InteractiveInterpreter
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt5.QtWidgets import QLineEdit, QTextEdit, QWidget, QVBoxLayout
from qutebrowser.models.cmdhistory import (History, HistoryEmptyError,
HistoryEndReachedError)
class ConsoleInteractiveInterpreter(InteractiveInterpreter, QObject):
"""Subclass of InteractiveInterpreter to use a signal instead of stderr.
FIXME: This approach doesn't actually work.
"""
write_output = pyqtSignal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
InteractiveInterpreter.__init__(self)
def write(self, data):
self.write_output.emit(data)
from qutebrowser.utils.misc import fake_io, disabled_excepthook
class ConsoleLineEdit(QLineEdit):
@ -54,8 +39,7 @@ class ConsoleLineEdit(QLineEdit):
super().__init__(parent)
self._more = False
self._buffer = []
self._interpreter = ConsoleInteractiveInterpreter()
self._interpreter.write_output.connect(self.write)
self._interpreter = InteractiveInterpreter()
self.history = History()
self.returnPressed.connect(self.execute)
@ -71,7 +55,15 @@ class ConsoleLineEdit(QLineEdit):
"""Push a line to the interpreter."""
self._buffer.append(line)
source = '\n'.join(self._buffer)
self._more = self._interpreter.runsource(source, '<console>')
# We do two special things with the contextmanagers here:
# - We replace stdout/stderr to capture output. Even if we could
# override InteractiveInterpreter's write method, most things are
# printed elsewhere (e.g. by exec). Other Python GUI shells do the
# same.
# - We disable our exception hook, so exceptions from the console get
# printed and don't ooen a crashdialog.
with fake_io(self.write.emit), disabled_excepthook():
self._more = self._interpreter.runsource(source, '<console>')
if not self._more:
self._buffer = []