Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions cq_editor/widgets/traceback_viewer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from traceback import extract_tb, format_exception_only
from itertools import dropwhile

from PyQt5.QtWidgets import QWidget, QTreeWidget, QTreeWidgetItem, QAction, QLabel
from PyQt5.QtWidgets import (
QWidget,
QTreeWidget,
QTreeWidgetItem,
QAction,
QLabel,
QApplication,
)
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtGui import QFontMetrics, QKeySequence

from ..mixins import ComponentMixin
from ..utils import layout
Expand Down Expand Up @@ -39,6 +46,20 @@ def __init__(self, parent):
self.current_exception = QLabel(self)
self.current_exception.setStyleSheet("QLabel {color : red; }")

# the message is shown elided, so let it at least be selected
self.current_exception.setTextInteractionFlags(
Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard
)

# the full exception text, unescaped and not elided
self._exception_text = ""

self.copy_action = QAction("Copy traceback", self)
self.copy_action.setShortcut(QKeySequence.Copy)
self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
self.copy_action.triggered.connect(self.copyTraceback)
self.tree.addAction(self.copy_action)

layout(self, (self.current_exception, self.tree), self)

self.tree.currentItemChanged.connect(self.handleSelection)
Expand Down Expand Up @@ -78,6 +99,9 @@ def addTraceback(self, exc_info, code):

exc_name = t.__name__
exc_msg = str(exc)

self._exception_text = "{}: {}".format(exc_name, exc_msg)

exc_msg = exc_msg.replace("<", "&lt;").replace(">", "&gt;") # replace <>

truncated_msg = self.truncate_text(exc_msg)
Expand All @@ -98,9 +122,47 @@ def addTraceback(self, exc_info, code):
)
)
else:
self._exception_text = ""
self.current_exception.setText("")
self.current_exception.setToolTip("")

def tracebackText(self):
"""
The traceback as it is shown in the pane, in the usual Python format.
"""

if not self._exception_text:
return ""

lines = ["Traceback (most recent call last):"]

root = self.tree.root

for i in range(root.childCount()):
item = root.child(i)
filename, lineno, code = (item.data(col, 0) for col in range(3))

lines.append(' File "{}", line {}'.format(filename, lineno))

if code:
lines.append(" {}".format(code))

lines.append(self._exception_text)

return "\n".join(lines)

@pyqtSlot()
def copyTraceback(self):
"""
Puts the traceback on the clipboard, so that it can be pasted into a
bug report or a search box.
"""

text = self.tracebackText()

if text:
QApplication.clipboard().setText(text)

@pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
def handleSelection(self, item, *args):

Expand Down
34 changes: 33 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
import cadquery as cq

from PyQt5.QtCore import Qt, QSettings, QPoint, QEvent, QSize
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtWidgets import QApplication, QFileDialog, QMessageBox
from PyQt5.QtGui import QMouseEvent

from cq_editor.__main__ import MainWindow
from cq_editor.widgets.editor import Editor
from cq_editor.widgets.traceback_viewer import TracebackPane
from cq_editor.cq_utils import export, get_occ_color

code = """import cadquery as cq
Expand Down Expand Up @@ -970,6 +971,37 @@ def test_search(editor):
qtbot.keyClick(editor, Qt.Key_F3, modifier=Qt.AltModifier)


def test_traceback_copy(qtbot):
"""
The traceback should be copyable, so that it can be pasted elsewhere.
"""

pane = TracebackPane(None)
qtbot.addWidget(pane)

try:
exec(compile(code_err2, "<string>", "exec"), {})
except Exception:
exc_info = sys.exc_info()

pane.addTraceback(exc_info, code_err2)

pane.copy_action.triggered.emit()

text = QApplication.clipboard().text()

assert text.startswith("Traceback (most recent call last):")
assert 'File "<string>", line 3' in text
assert text.endswith("NameError: name 'f' is not defined")

# the message is shown elided, so it has to be selectable as well
assert pane.current_exception.textInteractionFlags() & Qt.TextSelectableByMouse

# nothing to copy when there is no traceback
pane.addTraceback(None, "")
assert pane.tracebackText() == ""


def test_line_number_area(editor):
"""
Tests to make sure the line number area on the left of the editor is working correctly.
Expand Down