From 07fda5818c30d23aa30ebaaa841bdc3c7d582daf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 7 Aug 2014 14:43:45 +0200 Subject: [PATCH] Add broken debugging console. --- doc/notes | 9 +++ qutebrowser/app.py | 10 ++- qutebrowser/widgets/console.py | 124 +++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 qutebrowser/widgets/console.py diff --git a/doc/notes b/doc/notes index 6d34e43e6..a5442c605 100644 --- a/doc/notes +++ b/doc/notes @@ -69,3 +69,12 @@ Completion view (not QTreeView) Perhaps using a QHBoxLayout of QTableViews and creating/destroying them based on the completion would be a better idea? + + +Debug console +============= + +https://github.com/karelklic/flashfit/blob/1a2393cb2fb3e44d2e34bf568386d9d4a9b22148/gui_console.py +http://code.google.com/p/pyqtlive/source/browse/pycute4.py +http://stackoverflow.com/questions/12431555/enabling-code-completion-in-an-embedded-python-interpreter +http://docs.projexsoftware.com/wp-content/docs/projexui/0.5.1/api/projexui/widgets/xconsoleedit-source.html diff --git a/qutebrowser/app.py b/qutebrowser/app.py index b9b4224be..78ab6b62a 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -51,6 +51,7 @@ import qutebrowser.utils.utilcmds as utilcmds from qutebrowser.config.config import ConfigManager from qutebrowser.keyinput.modeman import ModeManager from qutebrowser.widgets.mainwindow import MainWindow +from qutebrowser.widgets.console import ConsoleWidget from qutebrowser.widgets.crash import (ExceptionCrashDialog, FatalCrashDialog, ReportDialog) from qutebrowser.keyinput.modeparsers import (NormalKeyParser, HintKeyParser, @@ -73,6 +74,7 @@ class Application(QApplication): Attributes: mainwindow: The MainWindow QWidget. + debugconsole: The ConsoleWidget for debugging. commandrunner: The main CommandRunner instance. searchrunner: The main SearchRunner instance. config: The main ConfigManager @@ -154,8 +156,9 @@ class Application(QApplication): self.downloadmanager = DownloadManager(self) log.init.debug("Initializing main window...") self.mainwindow = MainWindow() - self.modeman.mainwindow = self.mainwindow + log.init.debug("Initializing debug console...") + self.debugconsole = ConsoleWidget() log.init.debug("Initializing eventfilter...") self.installEventFilter(self.modeman) self.setQuitOnLastWindowClosed(False) @@ -655,6 +658,11 @@ class Application(QApplication): self._crashdlg = ReportDialog(pages, history, widgets, objects) self._crashdlg.show() + @cmdutils.register(debug=True, name='debug-console') + def show_debugconsole(self): + """Show the debugging console.""" + self.debugconsole.show() + def interrupt(self, signum, _frame): """Handler for signals to gracefully shutdown (SIGINT/SIGTERM). diff --git a/qutebrowser/widgets/console.py b/qutebrowser/widgets/console.py new file mode 100644 index 000000000..2cb834fc9 --- /dev/null +++ b/qutebrowser/widgets/console.py @@ -0,0 +1,124 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Debugging console.""" + +from code import InteractiveInterpreter + +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject +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) + + +class ConsoleLineEdit(QLineEdit): + + """A QLineEdit which executes entered code and provides a history.""" + + write = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self._more = False + self._buffer = [] + self._interpreter = ConsoleInteractiveInterpreter() + self._interpreter.write_output.connect(self.write) + self.history = History() + self.returnPressed.connect(self.execute) + + @pyqtSlot(str) + def execute(self): + """Execute the line of code which was entered.""" + text = self.text() + self.history.append(text) + self.push(text) + self.setText('') + + def push(self, line): + """Push a line to the interpreter.""" + self._buffer.append(line) + source = '\n'.join(self._buffer) + self._more = self._interpreter.runsource(source, '') + if not self._more: + self._buffer = [] + + def history_prev(self): + """Go back in the history.""" + try: + if not self.history.browsing: + item = self.history.start(self.text().strip()) + else: + item = self.history.previtem() + except (HistoryEmptyError, HistoryEndReachedError): + return + self.setText(item) + + def history_next(self): + """Go forward in the history.""" + if not self.history.browsing: + return + try: + item = self.history.nextitem() + except HistoryEndReachedError: + return + self.setText(item) + + def keyPressEvent(self, e): + """Override keyPressEvent to handle up/down keypresses.""" + if e.key() == Qt.Key_Up: + self.history_prev() + e.accept() + elif e.key() == Qt.Key_Down: + self.history_next() + e.accept() + else: + super().keyPressEvent(e) + + +class ConsoleWidget(QWidget): + + """A widget with an interactive Python console.""" + + def __init__(self, parent=None): + super().__init__(parent) + self.lineedit = ConsoleLineEdit() + self.output = QTextEdit(acceptRichText=True, readOnly=True) + self.lineedit.write.connect(self.output.append) + self.vbox = QVBoxLayout() + self.vbox.setSpacing(0) + self.vbox.addWidget(self.output) + self.vbox.addWidget(self.lineedit) + self.setLayout(self.vbox)