diff -r 0c6d32ec64f1 -r 6abcf4275d0e eric6/UI/PythonDisViewer.py --- a/eric6/UI/PythonDisViewer.py Sun Sep 20 18:32:43 2020 +0200 +++ b/eric6/UI/PythonDisViewer.py Mon Sep 21 19:03:35 2020 +0200 @@ -11,6 +11,9 @@ import os import dis +import enum + + from PyQt5.QtCore import pyqtSlot, Qt, QTimer from PyQt5.QtGui import QCursor, QBrush from PyQt5.QtWidgets import ( @@ -21,6 +24,14 @@ import Preferences +class PythonDisViewerModes(enum.Enum): + """ + Class implementing the disassembly viewer operation modes. + """ + SourceDisassemblyMode = 0 + TracebackMode = 1 + + class PythonDisViewer(QWidget): """ Class implementing a widget to visualize the Python Disassembly for some @@ -29,12 +40,16 @@ StartLineRole = Qt.UserRole EndLineRole = Qt.UserRole + 1 - def __init__(self, viewmanager, parent=None): + def __init__(self, viewmanager, + mode=PythonDisViewerModes.SourceDisassemblyMode, + parent=None): """ Constructor @param viewmanager reference to the viewmanager object @type ViewManager + @param mode operation mode of the viewer + @type int @param parent reference to the parent widget @type QWidget """ @@ -53,9 +68,13 @@ self.__layout.addWidget(self.__currentInfoLabel) self.__layout.addWidget(self.__labeledInfoLabel) + self.setWindowTitle(self.tr("Disassembly")) + self.__vm = viewmanager self.__vmConnected = False + self.__mode = mode + self.__editor = None self.__source = "" @@ -86,9 +105,13 @@ self.__disWidget.itemCollapsed.connect(self.__resizeColumns) self.__disWidget.itemExpanded.connect(self.__resizeColumns) - self.__vm.disViewerStateChanged.connect(self.__disViewerStateChanged) + if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: + self.__vm.disViewerStateChanged.connect( + self.__disViewerStateChanged) - self.hide() + self.hide() + elif self.__mode == PythonDisViewerModes.TracebackMode: + self.__styleLabels() def __contextMenuRequested(self, coord): """ @@ -142,6 +165,16 @@ # highlight the corresponding entry QTimer.singleShot(0, self.__selectItemForEditorLine) + def __editorLanguageChanged(self, editor): + """ + Private slot to handle a change of the editor language. + + @param editor reference to the editor which changed language + @type Editor + """ + if editor is self.__editor: + QTimer.singleShot(0, self.__loadDIS) + def __lastEditorClosed(self): """ Private slot to handle the last editor closed signal of the view @@ -155,11 +188,18 @@ """ super(PythonDisViewer, self).show() - if not self.__vmConnected: + if ( + self.__mode == PythonDisViewerModes.SourceDisassemblyMode and + not self.__vmConnected + ): self.__vm.editorChangedEd.connect(self.__editorChanged) self.__vm.editorSavedEd.connect(self.__editorSaved) self.__vm.editorLineChangedEd.connect(self.__editorLineChanged) + self.__vm.editorLanguageChanged.connect( + self.__editorLanguageChanged) self.__vmConnected = True + + self.__styleLabels() def hide(self): """ @@ -170,10 +210,15 @@ if self.__editor: self.__editor.clearAllHighlights() - if self.__vmConnected: + if ( + self.__mode == PythonDisViewerModes.SourceDisassemblyMode and + self.__vmConnected + ): self.__vm.editorChangedEd.disconnect(self.__editorChanged) self.__vm.editorSavedEd.disconnect(self.__editorSaved) self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged) + self.__vm.editorLanguageChanged.disconnect( + self.__editorLanguageChanged) self.__vmConnected = False def shutdown(self): @@ -189,15 +234,16 @@ @param on flag indicating to show the Disassembly @type bool """ - editor = self.__vm.activeWindow() - if on and editor and editor.isPyFile(): - if editor is not self.__editor: - self.__editor = editor - self.show() - self.__loadDIS() - else: - self.hide() - self.__editor = None + if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: + editor = self.__vm.activeWindow() + if on: + if editor is not self.__editor: + self.__editor = editor + self.show() + self.__loadDIS() + else: + self.hide() + self.__editor = None def __expandAll(self): """ @@ -339,21 +385,31 @@ Private method to generate the Disassembly from the source of the current editor and visualize it. """ + if self.__mode != PythonDisViewerModes.SourceDisassemblyMode: + # wrong mode, just return + return + if not self.__editor: + self.__createErrorItem(self.tr( + "No editor has been opened." + )) return self.__disWidget.clear() self.__editor.clearAllHighlights() - if not self.__editor.isPyFile(): + source = self.__editor.text() + if not source.strip(): + # empty editor or white space only self.__createErrorItem(self.tr( - "The current editor text does not contain Python source." + "The current editor does not contain any source code." )) return - source = self.__editor.text() - if not source.strip(): - # empty editor or white space only + if not self.__editor.isPyFile(): + self.__createErrorItem(self.tr( + "The current editor does not contain Python source code." + )) return filename = self.__editor.getFileName() @@ -380,8 +436,69 @@ self.setUpdatesEnabled(True) QApplication.restoreOverrideCursor() + + @pyqtSlot(dict) + def showDisassembly(self, disassembly): + """ + Public slot to receive a code disassembly from the debug client. - self.__grabFocus() + @param disassembly dictionary containing the disassembly information + @type dict + """ + if self.__mode == PythonDisViewerModes.TracebackMode: + if ( + disassembly and + "instructions" in disassembly and + disassembly["instructions"] + ): + self.__disWidget.clear() + + self.setUpdatesEnabled(False) + block = self.__disWidget.blockSignals(True) + + titleItem = self.__createTitleItem( + self.tr("Disassembly of last traceback"), + disassembly["firstlineno"], + self.__disWidget + ) + + lasti = disassembly["lasti"] + lastStartItem = None + for instrDict in disassembly["instructions"]: + instr = dis.Instruction( + instrDict["opname"], + 0, # dummy value + instrDict["arg"], + "", # dummy value + instrDict["argrepr"], + instrDict["offset"], + instrDict["lineno"], + instrDict["isJumpTarget"], + ) + if instrDict["lineno"] > 0: + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + lastStartItem = self.__createInstructionItem( + instr, titleItem, lasti=lasti) + else: + self.__createInstructionItem( + instr, lastStartItem, lasti=lasti) + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + + QTimer.singleShot(0, self.__resizeColumns) + + self.__disWidget.blockSignals(block) + self.setUpdatesEnabled(True) + + if lasti: + lastInstructions = self.__disWidget.findItems( + "{0:d}".format(lasti), + Qt.MatchFixedString | Qt.MatchRecursive, 1) + if lastInstructions: + self.__disWidget.scrollToItem( + lastInstructions[0], + QAbstractItemView.PositionAtCenter) def __resizeColumns(self): """ @@ -452,12 +569,6 @@ itm.setSelected(True) self.__selectChildren(itm, cline) - def __grabFocus(self): - """ - Private method to grab the input focus. - """ - self.__disWidget.setFocus(Qt.OtherFocusReason) - @pyqtSlot(QTreeWidgetItem, int) def __disItemClicked(self, itm, column): """ @@ -468,6 +579,7 @@ @param column column number of the click @type int """ + # TODO: add code to deal with Traceback mode self.__editor.clearAllHighlights() if itm is not None: @@ -553,3 +665,36 @@ if self.isVisible(): self.__loadDIS() + + self.__styleLabels() + + def __styleLabels(self): + """ + Private method to style the info labels iaw. selected colors. + """ + # current instruction + self.__currentInfoLabel.setStyleSheet( + "QLabel {{ color : {0}; }}".format( + self.__currentInstructionColor.color().name() + ) + ) + font = self.__currentInfoLabel.font() + font.setItalic(True) + self.__currentInfoLabel.setFont(font) + + # labeled instruction + self.__labeledInfoLabel.setStyleSheet( + "QLabel {{ color : {0}; }}".format( + self.__jumpTargetColor.color().name() + ) + ) + font = self.__labeledInfoLabel.font() + font.setBold(True) + self.__labeledInfoLabel.setFont(font) + + @pyqtSlot() + def clear(self): + """ + Public method to clear the display. + """ + self.__disWidget.clear()