mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Improve debug console (#2435)
* List actions and locals in debug console * Ignore whitespace when wrapping line with pp * Scroll down after printing in debug console Was previously preserving relative vertical position. * Add feature to open and save debug scripts * Refactor debug console into own module * Add buffers to switch scripts * Add action to delete script
This commit is contained in:
parent
5afbf8934f
commit
b55161cd39
4 changed files with 357 additions and 179 deletions
322
qt/aqt/debug_console.py
Normal file
322
qt/aqt/debug_console.py
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TextIO, cast
|
||||||
|
|
||||||
|
import anki.cards
|
||||||
|
import aqt
|
||||||
|
import aqt.forms
|
||||||
|
from aqt import gui_hooks
|
||||||
|
from aqt.profiles import ProfileManager
|
||||||
|
from aqt.qt import *
|
||||||
|
from aqt.utils import (
|
||||||
|
disable_help_button,
|
||||||
|
restoreGeom,
|
||||||
|
restoreSplitter,
|
||||||
|
saveGeom,
|
||||||
|
saveSplitter,
|
||||||
|
send_to_trash,
|
||||||
|
tr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def show_debug_console() -> None:
|
||||||
|
assert aqt.mw
|
||||||
|
console = DebugConsole(aqt.mw)
|
||||||
|
gui_hooks.debug_console_will_show(console)
|
||||||
|
console.show()
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_FOLDER = "debug_scripts"
|
||||||
|
UNSAVED_SCRIPT = "Unsaved script"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Action:
|
||||||
|
name: str
|
||||||
|
shortcut: str
|
||||||
|
action: Callable[[], None]
|
||||||
|
|
||||||
|
|
||||||
|
class DebugConsole(QDialog):
|
||||||
|
silentlyClose = True
|
||||||
|
_last_index = 0
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget) -> None:
|
||||||
|
self._buffers: dict[int, str] = {}
|
||||||
|
super().__init__(parent)
|
||||||
|
self._setup_ui()
|
||||||
|
disable_help_button(self)
|
||||||
|
restoreGeom(self, "DebugConsoleWindow")
|
||||||
|
restoreSplitter(self.frm.splitter, "DebugConsoleWindow")
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
self.frm = aqt.forms.debug.Ui_Dialog()
|
||||||
|
self.frm.setupUi(self)
|
||||||
|
self._text: QPlainTextEdit = self.frm.text
|
||||||
|
self._log: QPlainTextEdit = self.frm.log
|
||||||
|
self._script: QComboBox = self.frm.script
|
||||||
|
self._setup_text_edits()
|
||||||
|
self._setup_scripts()
|
||||||
|
self._setup_actions()
|
||||||
|
self._setup_context_menu()
|
||||||
|
qconnect(self.frm.widgetsButton.clicked, self._on_widgetGallery)
|
||||||
|
qconnect(self._script.currentIndexChanged, self._on_script_change)
|
||||||
|
|
||||||
|
def _setup_text_edits(self):
|
||||||
|
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
|
||||||
|
font.setPointSize(self._text.font().pointSize() + 1)
|
||||||
|
self._text.setFont(font)
|
||||||
|
self._log.setFont(font)
|
||||||
|
|
||||||
|
def _setup_scripts(self) -> None:
|
||||||
|
self._dir = ProfileManager.get_created_base_folder(None).joinpath(SCRIPT_FOLDER)
|
||||||
|
self._dir.mkdir(exist_ok=True)
|
||||||
|
self._script.addItem(UNSAVED_SCRIPT)
|
||||||
|
self._script.addItems(os.listdir(self._dir))
|
||||||
|
|
||||||
|
def _setup_actions(self) -> None:
|
||||||
|
for action in self._actions():
|
||||||
|
qconnect(
|
||||||
|
QShortcut(QKeySequence(action.shortcut), self).activated, action.action
|
||||||
|
)
|
||||||
|
|
||||||
|
def _actions(self):
|
||||||
|
return [
|
||||||
|
Action("Execute", "ctrl+return", self.onDebugRet),
|
||||||
|
Action("Execute and print", "ctrl+shift+return", self.onDebugPrint),
|
||||||
|
Action("Clear log", "ctrl+l", self._log.clear),
|
||||||
|
Action("Clear code", "ctrl+shift+l", self._text.clear),
|
||||||
|
Action("Save script", "ctrl+s", self._save_script),
|
||||||
|
Action("Open script", "ctrl+o", self._open_script),
|
||||||
|
Action("Delete script", "ctrl+d", self._delete_script),
|
||||||
|
]
|
||||||
|
|
||||||
|
def reject(self) -> None:
|
||||||
|
super().reject()
|
||||||
|
saveSplitter(self.frm.splitter, "DebugConsoleWindow")
|
||||||
|
saveGeom(self, "DebugConsoleWindow")
|
||||||
|
|
||||||
|
def _on_script_change(self, new_index: int) -> None:
|
||||||
|
self._buffers[self._last_index] = self._text.toPlainText()
|
||||||
|
self._text.setPlainText(self._get_script(new_index) or "")
|
||||||
|
self._last_index = new_index
|
||||||
|
|
||||||
|
def _get_script(self, idx: int) -> str | None:
|
||||||
|
if script := self._buffers.get(idx, ""):
|
||||||
|
return script
|
||||||
|
if path := self._get_item(idx):
|
||||||
|
return path.read_text(encoding="utf8")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_item(self, idx: int) -> Path | None:
|
||||||
|
if not idx:
|
||||||
|
return None
|
||||||
|
path = Path(self._script.itemText(idx))
|
||||||
|
return path if path.is_absolute() else self._dir.joinpath(path)
|
||||||
|
|
||||||
|
def _get_index(self, path: Path) -> int:
|
||||||
|
return self._script.findText(self._path_to_item(path))
|
||||||
|
|
||||||
|
def _path_to_item(self, path: Path) -> str:
|
||||||
|
return path.name if path.is_relative_to(self._dir) else str(path)
|
||||||
|
|
||||||
|
def _current_script_path(self) -> Path | None:
|
||||||
|
return self._get_item(self._script.currentIndex())
|
||||||
|
|
||||||
|
def _save_script(self) -> None:
|
||||||
|
if not (path := self._current_script_path()):
|
||||||
|
new_file = QFileDialog.getSaveFileName(
|
||||||
|
self, directory=str(self._dir), filter="Python file (*.py)"
|
||||||
|
)[0]
|
||||||
|
if not new_file:
|
||||||
|
return
|
||||||
|
path = Path(new_file)
|
||||||
|
|
||||||
|
path.write_text(self._text.toPlainText(), encoding="utf8")
|
||||||
|
|
||||||
|
item = self._path_to_item(path)
|
||||||
|
if (idx := self._get_index(path)) == -1:
|
||||||
|
self._script.addItem(item)
|
||||||
|
idx = self._script.count() - 1
|
||||||
|
# update existing buffer, so text edit doesn't change when index changes
|
||||||
|
self._buffers[idx] = self._text.toPlainText()
|
||||||
|
self._script.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def _open_script(self) -> None:
|
||||||
|
file = QFileDialog.getOpenFileName(
|
||||||
|
self, directory=str(self._dir), filter="Python file (*.py)"
|
||||||
|
)[0]
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
|
||||||
|
path = Path(file)
|
||||||
|
item = self._path_to_item(path)
|
||||||
|
if (idx := self._get_index(path)) == -1:
|
||||||
|
self._script.addItem(item)
|
||||||
|
idx = self._script.count() - 1
|
||||||
|
elif idx in self._buffers:
|
||||||
|
del self._buffers[idx]
|
||||||
|
|
||||||
|
if idx == self._script.currentIndex():
|
||||||
|
self._text.setPlainText(path.read_text(encoding="utf8"))
|
||||||
|
else:
|
||||||
|
self._script.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def _delete_script(self) -> None:
|
||||||
|
if not (path := self._current_script_path()):
|
||||||
|
return
|
||||||
|
send_to_trash(path)
|
||||||
|
deleted_idx = self._script.currentIndex()
|
||||||
|
self._script.setCurrentIndex(0)
|
||||||
|
self._script.removeItem(deleted_idx)
|
||||||
|
self._drop_buffer_and_shift_keys(deleted_idx)
|
||||||
|
|
||||||
|
def _drop_buffer_and_shift_keys(self, idx: int) -> None:
|
||||||
|
def shift(old_idx: int) -> int:
|
||||||
|
return old_idx - 1 if old_idx > idx else old_idx
|
||||||
|
|
||||||
|
self._buffers = {shift(i): val for i, val in self._buffers.items() if i != idx}
|
||||||
|
|
||||||
|
def _setup_context_menu(self) -> None:
|
||||||
|
for text_edit in (self._log, self._text):
|
||||||
|
text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
|
qconnect(
|
||||||
|
text_edit.customContextMenuRequested,
|
||||||
|
partial(self._on_context_menu, text_edit),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_context_menu(self, text_edit: QPlainTextEdit) -> None:
|
||||||
|
menu = text_edit.createStandardContextMenu()
|
||||||
|
menu.addSeparator()
|
||||||
|
for action in self._actions():
|
||||||
|
entry = menu.addAction(action.name)
|
||||||
|
entry.setShortcut(QKeySequence(action.shortcut))
|
||||||
|
qconnect(entry.triggered, action.action)
|
||||||
|
menu.exec(QCursor.pos())
|
||||||
|
|
||||||
|
def _on_widgetGallery(self) -> None:
|
||||||
|
from aqt.widgetgallery import WidgetGallery
|
||||||
|
|
||||||
|
self.widgetGallery = WidgetGallery(self)
|
||||||
|
self.widgetGallery.show()
|
||||||
|
|
||||||
|
def _captureOutput(self, on: bool) -> None:
|
||||||
|
mw2 = self
|
||||||
|
|
||||||
|
class Stream:
|
||||||
|
def write(self, data: str) -> None:
|
||||||
|
mw2._output += data
|
||||||
|
|
||||||
|
if on:
|
||||||
|
self._output = ""
|
||||||
|
self._oldStderr = sys.stderr
|
||||||
|
self._oldStdout = sys.stdout
|
||||||
|
s = cast(TextIO, Stream())
|
||||||
|
sys.stderr = s
|
||||||
|
sys.stdout = s
|
||||||
|
else:
|
||||||
|
sys.stderr = self._oldStderr
|
||||||
|
sys.stdout = self._oldStdout
|
||||||
|
|
||||||
|
def _card_repr(self, card: anki.cards.Card) -> None:
|
||||||
|
import copy
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
if not card:
|
||||||
|
print("no card")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Front:", card.question())
|
||||||
|
print("\n")
|
||||||
|
print("Back:", card.answer())
|
||||||
|
|
||||||
|
print("\nNote:")
|
||||||
|
note = copy.copy(card.note())
|
||||||
|
for k, v in note.items():
|
||||||
|
print(f"- {k}:", v)
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
del note.fields
|
||||||
|
del note._fmap
|
||||||
|
pprint.pprint(note.__dict__)
|
||||||
|
|
||||||
|
print("\nCard:")
|
||||||
|
c = copy.copy(card)
|
||||||
|
c._render_output = None
|
||||||
|
pprint.pprint(c.__dict__)
|
||||||
|
|
||||||
|
def _debugCard(self) -> anki.cards.Card | None:
|
||||||
|
assert aqt.mw
|
||||||
|
card = aqt.mw.reviewer.card
|
||||||
|
self._card_repr(card)
|
||||||
|
return card
|
||||||
|
|
||||||
|
def _debugBrowserCard(self) -> anki.cards.Card | None:
|
||||||
|
card = aqt.dialogs._dialogs["Browser"][1].card
|
||||||
|
self._card_repr(card)
|
||||||
|
return card
|
||||||
|
|
||||||
|
def onDebugPrint(self) -> None:
|
||||||
|
cursor = self._text.textCursor()
|
||||||
|
position = cursor.position()
|
||||||
|
cursor.select(QTextCursor.SelectionType.LineUnderCursor)
|
||||||
|
line = cursor.selectedText()
|
||||||
|
whitespace, stripped = _split_off_leading_whitespace(line)
|
||||||
|
pfx, sfx = "pp(", ")"
|
||||||
|
if not stripped.startswith(pfx):
|
||||||
|
line = f"{whitespace}{pfx}{stripped}{sfx}"
|
||||||
|
cursor.insertText(line)
|
||||||
|
cursor.setPosition(position + len(pfx))
|
||||||
|
self._text.setTextCursor(cursor)
|
||||||
|
self.onDebugRet()
|
||||||
|
|
||||||
|
def onDebugRet(self) -> None:
|
||||||
|
import pprint
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
text = self._text.toPlainText()
|
||||||
|
card = self._debugCard
|
||||||
|
bcard = self._debugBrowserCard
|
||||||
|
mw = aqt.mw
|
||||||
|
pp = pprint.pprint
|
||||||
|
self._captureOutput(True)
|
||||||
|
try:
|
||||||
|
# pylint: disable=exec-used
|
||||||
|
exec(text)
|
||||||
|
except:
|
||||||
|
self._output += traceback.format_exc()
|
||||||
|
self._captureOutput(False)
|
||||||
|
buf = ""
|
||||||
|
for c, line in enumerate(text.strip().split("\n")):
|
||||||
|
if c == 0:
|
||||||
|
buf += f">>> {line}\n"
|
||||||
|
else:
|
||||||
|
buf += f"... {line}\n"
|
||||||
|
try:
|
||||||
|
to_append = buf + (self._output or "<no output>")
|
||||||
|
to_append = gui_hooks.debug_console_did_evaluate_python(
|
||||||
|
to_append, text, self.frm
|
||||||
|
)
|
||||||
|
self._log.appendPlainText(to_append)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
to_append = tr.qt_misc_non_unicode_text()
|
||||||
|
to_append = gui_hooks.debug_console_did_evaluate_python(
|
||||||
|
to_append, text, self.frm
|
||||||
|
)
|
||||||
|
self._log.appendPlainText(to_append)
|
||||||
|
slider = self._log.verticalScrollBar()
|
||||||
|
slider.setValue(slider.maximum())
|
||||||
|
self._log.ensureCursorVisible()
|
||||||
|
|
||||||
|
|
||||||
|
def _split_off_leading_whitespace(text: str) -> tuple[str, str]:
|
||||||
|
stripped = text.lstrip()
|
||||||
|
whitespace = text[: len(text) - len(stripped)]
|
||||||
|
return whitespace, stripped
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>643</width>
|
<width>637</width>
|
||||||
<height>582</height>
|
<height>582</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -14,10 +14,20 @@
|
||||||
<string>qt_misc_debug_console</string>
|
<string>qt_misc_debug_console</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset>
|
||||||
<normaloff>:/icons/anki.png</normaloff>:/icons/anki.png</iconset>
|
<normaloff>:/icons/anki.png</normaloff>:/icons/anki.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="script">
|
||||||
|
<property name="currentText">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>-1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -49,7 +59,20 @@
|
||||||
<enum>QPlainTextEdit::NoWrap</enum>
|
<enum>QPlainTextEdit::NoWrap</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string notr="true">Type commands here (Enter to submit)</string>
|
<string notr="true">Actions:
|
||||||
|
Ctrl+Enter Execute
|
||||||
|
Ctrl+Shift+Enter Execute and print current line
|
||||||
|
Ctrl+L Clear log
|
||||||
|
Ctrl+Shift+L Clear input
|
||||||
|
Ctrl+S Save script
|
||||||
|
Ctrl+O Open script
|
||||||
|
Ctrl+D Delete script
|
||||||
|
|
||||||
|
Locals:
|
||||||
|
mw: AnkiQt Main window
|
||||||
|
card: Callable[[], Card | None] Reviewer card
|
||||||
|
bcard: Callable[[], Card | None] Browser card
|
||||||
|
pp: Callable[[object], None] Pretty print</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QPlainTextEdit" name="log">
|
<widget class="QPlainTextEdit" name="log">
|
||||||
|
|
173
qt/aqt/main.py
173
qt/aqt/main.py
|
@ -11,7 +11,7 @@ import signal
|
||||||
import weakref
|
import weakref
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from typing import Any, Literal, Sequence, TextIO, TypeVar, cast
|
from typing import Any, Literal, Sequence, TypeVar, cast
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
import anki.cards
|
import anki.cards
|
||||||
|
@ -46,6 +46,7 @@ from anki.utils import (
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user
|
from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user
|
||||||
from aqt.dbcheck import check_db
|
from aqt.dbcheck import check_db
|
||||||
|
from aqt.debug_console import show_debug_console
|
||||||
from aqt.emptycards import show_empty_cards
|
from aqt.emptycards import show_empty_cards
|
||||||
from aqt.flags import FlagManager
|
from aqt.flags import FlagManager
|
||||||
from aqt.import_export.exporting import ExportDialog
|
from aqt.import_export.exporting import ExportDialog
|
||||||
|
@ -74,17 +75,14 @@ from aqt.utils import (
|
||||||
askUser,
|
askUser,
|
||||||
checkInvalidFilename,
|
checkInvalidFilename,
|
||||||
current_window,
|
current_window,
|
||||||
disable_help_button,
|
|
||||||
disallow_full_screen,
|
disallow_full_screen,
|
||||||
getFile,
|
getFile,
|
||||||
getOnlyText,
|
getOnlyText,
|
||||||
openHelp,
|
openHelp,
|
||||||
openLink,
|
openLink,
|
||||||
restoreGeom,
|
restoreGeom,
|
||||||
restoreSplitter,
|
|
||||||
restoreState,
|
restoreState,
|
||||||
saveGeom,
|
saveGeom,
|
||||||
saveSplitter,
|
|
||||||
saveState,
|
saveState,
|
||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
|
@ -1102,7 +1100,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
|
|
||||||
def setupKeys(self) -> None:
|
def setupKeys(self) -> None:
|
||||||
globalShortcuts = [
|
globalShortcuts = [
|
||||||
("Ctrl+:", self.onDebug),
|
("Ctrl+:", show_debug_console),
|
||||||
("d", lambda: self.moveToState("deckBrowser")),
|
("d", lambda: self.moveToState("deckBrowser")),
|
||||||
("s", self.onStudyKey),
|
("s", self.onStudyKey),
|
||||||
("a", self.onAddCard),
|
("a", self.onAddCard),
|
||||||
|
@ -1631,171 +1629,6 @@ title="{}" {}>{}</button>""".format(
|
||||||
def onEmptyCards(self) -> None:
|
def onEmptyCards(self) -> None:
|
||||||
show_empty_cards(self)
|
show_empty_cards(self)
|
||||||
|
|
||||||
# Debugging
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
def onDebug(self) -> None:
|
|
||||||
frm = self.debug_diag_form = aqt.forms.debug.Ui_Dialog()
|
|
||||||
|
|
||||||
class DebugDialog(QDialog):
|
|
||||||
silentlyClose = True
|
|
||||||
|
|
||||||
def reject(self) -> None:
|
|
||||||
super().reject()
|
|
||||||
saveSplitter(frm.splitter, "DebugConsoleWindow")
|
|
||||||
saveGeom(self, "DebugConsoleWindow")
|
|
||||||
|
|
||||||
d = self.debugDiag = DebugDialog()
|
|
||||||
disable_help_button(d)
|
|
||||||
frm.setupUi(d)
|
|
||||||
restoreGeom(d, "DebugConsoleWindow")
|
|
||||||
restoreSplitter(frm.splitter, "DebugConsoleWindow")
|
|
||||||
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
|
|
||||||
font.setPointSize(frm.text.font().pointSize() + 1)
|
|
||||||
frm.text.setFont(font)
|
|
||||||
frm.log.setFont(font)
|
|
||||||
s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+return"), d)
|
|
||||||
qconnect(s.activated, lambda: self.onDebugRet(frm))
|
|
||||||
s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+shift+return"), d)
|
|
||||||
qconnect(s.activated, lambda: self.onDebugPrint(frm))
|
|
||||||
s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+l"), d)
|
|
||||||
qconnect(s.activated, frm.log.clear)
|
|
||||||
s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+shift+l"), d)
|
|
||||||
qconnect(s.activated, frm.text.clear)
|
|
||||||
|
|
||||||
qconnect(frm.widgetsButton.clicked, self._on_widgetGallery)
|
|
||||||
|
|
||||||
def addContextMenu(
|
|
||||||
ev: Union[QCloseEvent, QContextMenuEvent], name: str
|
|
||||||
) -> None:
|
|
||||||
ev.accept()
|
|
||||||
menu = frm.log.createStandardContextMenu(QCursor.pos())
|
|
||||||
menu.addSeparator()
|
|
||||||
if name == "log":
|
|
||||||
a = menu.addAction("Clear Log")
|
|
||||||
a.setShortcut(QKeySequence("ctrl+l"))
|
|
||||||
qconnect(a.triggered, frm.log.clear)
|
|
||||||
elif name == "text":
|
|
||||||
a = menu.addAction("Clear Code")
|
|
||||||
a.setShortcut(QKeySequence("ctrl+shift+l"))
|
|
||||||
qconnect(a.triggered, frm.text.clear)
|
|
||||||
menu.exec(QCursor.pos())
|
|
||||||
|
|
||||||
frm.log.contextMenuEvent = lambda ev: addContextMenu(ev, "log") # type: ignore[assignment]
|
|
||||||
frm.text.contextMenuEvent = lambda ev: addContextMenu(ev, "text") # type: ignore[assignment]
|
|
||||||
gui_hooks.debug_console_will_show(d)
|
|
||||||
d.show()
|
|
||||||
|
|
||||||
def _on_widgetGallery(self) -> None:
|
|
||||||
from aqt.widgetgallery import WidgetGallery
|
|
||||||
|
|
||||||
self.widgetGallery = WidgetGallery(self)
|
|
||||||
self.widgetGallery.show()
|
|
||||||
|
|
||||||
def _captureOutput(self, on: bool) -> None:
|
|
||||||
mw2 = self
|
|
||||||
|
|
||||||
class Stream:
|
|
||||||
def write(self, data: str) -> None:
|
|
||||||
mw2._output += data
|
|
||||||
|
|
||||||
if on:
|
|
||||||
self._output = ""
|
|
||||||
self._oldStderr = sys.stderr
|
|
||||||
self._oldStdout = sys.stdout
|
|
||||||
s = cast(TextIO, Stream())
|
|
||||||
sys.stderr = s
|
|
||||||
sys.stdout = s
|
|
||||||
else:
|
|
||||||
sys.stderr = self._oldStderr
|
|
||||||
sys.stdout = self._oldStdout
|
|
||||||
|
|
||||||
def _card_repr(self, card: anki.cards.Card) -> None:
|
|
||||||
import copy
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
if not card:
|
|
||||||
print("no card")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Front:", card.question())
|
|
||||||
print("\n")
|
|
||||||
print("Back:", card.answer())
|
|
||||||
|
|
||||||
print("\nNote:")
|
|
||||||
note = copy.copy(card.note())
|
|
||||||
for k, v in note.items():
|
|
||||||
print(f"- {k}:", v)
|
|
||||||
|
|
||||||
print("\n")
|
|
||||||
del note.fields
|
|
||||||
del note._fmap
|
|
||||||
pprint.pprint(note.__dict__)
|
|
||||||
|
|
||||||
print("\nCard:")
|
|
||||||
c = copy.copy(card)
|
|
||||||
c._render_output = None
|
|
||||||
pprint.pprint(c.__dict__)
|
|
||||||
|
|
||||||
def _debugCard(self) -> anki.cards.Card | None:
|
|
||||||
card = self.reviewer.card
|
|
||||||
self._card_repr(card)
|
|
||||||
return card
|
|
||||||
|
|
||||||
def _debugBrowserCard(self) -> anki.cards.Card | None:
|
|
||||||
card = aqt.dialogs._dialogs["Browser"][1].card
|
|
||||||
self._card_repr(card)
|
|
||||||
return card
|
|
||||||
|
|
||||||
def onDebugPrint(self, frm: aqt.forms.debug.Ui_Dialog) -> None:
|
|
||||||
cursor = frm.text.textCursor()
|
|
||||||
position = cursor.position()
|
|
||||||
cursor.select(QTextCursor.SelectionType.LineUnderCursor)
|
|
||||||
line = cursor.selectedText()
|
|
||||||
pfx, sfx = "pp(", ")"
|
|
||||||
if not line.startswith(pfx):
|
|
||||||
line = f"{pfx}{line}{sfx}"
|
|
||||||
cursor.insertText(line)
|
|
||||||
cursor.setPosition(position + len(pfx))
|
|
||||||
frm.text.setTextCursor(cursor)
|
|
||||||
self.onDebugRet(frm)
|
|
||||||
|
|
||||||
def onDebugRet(self, frm: aqt.forms.debug.Ui_Dialog) -> None:
|
|
||||||
import pprint
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
text = frm.text.toPlainText()
|
|
||||||
card = self._debugCard
|
|
||||||
bcard = self._debugBrowserCard
|
|
||||||
mw = self
|
|
||||||
pp = pprint.pprint
|
|
||||||
self._captureOutput(True)
|
|
||||||
try:
|
|
||||||
# pylint: disable=exec-used
|
|
||||||
exec(text)
|
|
||||||
except:
|
|
||||||
self._output += traceback.format_exc()
|
|
||||||
self._captureOutput(False)
|
|
||||||
buf = ""
|
|
||||||
for c, line in enumerate(text.strip().split("\n")):
|
|
||||||
if c == 0:
|
|
||||||
buf += f">>> {line}\n"
|
|
||||||
else:
|
|
||||||
buf += f"... {line}\n"
|
|
||||||
try:
|
|
||||||
to_append = buf + (self._output or "<no output>")
|
|
||||||
to_append = gui_hooks.debug_console_did_evaluate_python(
|
|
||||||
to_append, text, frm
|
|
||||||
)
|
|
||||||
frm.log.appendPlainText(to_append)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
to_append = tr.qt_misc_non_unicode_text()
|
|
||||||
to_append = gui_hooks.debug_console_did_evaluate_python(
|
|
||||||
to_append, text, frm
|
|
||||||
)
|
|
||||||
frm.log.appendPlainText(to_append)
|
|
||||||
frm.log.ensureCursorVisible()
|
|
||||||
|
|
||||||
# System specific code
|
# System specific code
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.main
|
import aqt.main
|
||||||
from aqt.qt import QDialog, qconnect
|
from aqt.qt import QDialog, QWidget, qconnect
|
||||||
from aqt.theme import WidgetStyle
|
from aqt.theme import WidgetStyle
|
||||||
from aqt.utils import restoreGeom, saveGeom
|
from aqt.utils import restoreGeom, saveGeom
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ from aqt.utils import restoreGeom, saveGeom
|
||||||
class WidgetGallery(QDialog):
|
class WidgetGallery(QDialog):
|
||||||
silentlyClose = True
|
silentlyClose = True
|
||||||
|
|
||||||
def __init__(self, mw: aqt.main.AnkiQt) -> None:
|
def __init__(self, parent: QWidget) -> None:
|
||||||
super().__init__(mw)
|
assert aqt.mw
|
||||||
self.mw = mw.weakref()
|
super().__init__(parent)
|
||||||
|
|
||||||
self.form = aqt.forms.widgets.Ui_Dialog()
|
self.form = aqt.forms.widgets.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
|
@ -29,10 +29,10 @@ class WidgetGallery(QDialog):
|
||||||
self.form.styleComboBox.addItems(
|
self.form.styleComboBox.addItems(
|
||||||
[member.name.lower().capitalize() for member in WidgetStyle]
|
[member.name.lower().capitalize() for member in WidgetStyle]
|
||||||
)
|
)
|
||||||
self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style())
|
self.form.styleComboBox.setCurrentIndex(aqt.mw.pm.get_widget_style())
|
||||||
qconnect(
|
qconnect(
|
||||||
self.form.styleComboBox.currentIndexChanged,
|
self.form.styleComboBox.currentIndexChanged,
|
||||||
self.mw.pm.set_widget_style,
|
aqt.mw.pm.set_widget_style,
|
||||||
)
|
)
|
||||||
|
|
||||||
def reject(self) -> None:
|
def reject(self) -> None:
|
||||||
|
|
Loading…
Reference in a new issue