eric6/UI/PythonDisViewer.py

changeset 7710
9aad21c7765d
parent 7707
6abcf4275d0e
child 7711
5e6792b85a8a
diff -r 7c3968742fd2 -r 9aad21c7765d eric6/UI/PythonDisViewer.py
--- a/eric6/UI/PythonDisViewer.py	Tue Sep 22 19:03:19 2020 +0200
+++ b/eric6/UI/PythonDisViewer.py	Tue Sep 22 19:30:03 2020 +0200
@@ -17,12 +17,13 @@
 from PyQt5.QtCore import pyqtSlot, Qt, QTimer
 from PyQt5.QtGui import QCursor, QBrush
 from PyQt5.QtWidgets import (
-    QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget,
-    QVBoxLayout, QLabel, QMenu
+    QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, QMenu
 )
 
 import Preferences
 
+from .Ui_PythonDisViewer import Ui_PythonDisViewer
+
 
 class PythonDisViewerModes(enum.Enum):
     """
@@ -32,13 +33,14 @@
     TracebackMode = 1
 
 
-class PythonDisViewer(QWidget):
+class PythonDisViewer(QWidget, Ui_PythonDisViewer):
     """
     Class implementing a widget to visualize the Python Disassembly for some
     Python sources.
     """
     StartLineRole = Qt.UserRole
     EndLineRole = Qt.UserRole + 1
+    CodeInfoRole = Qt.UserRole + 2
     
     def __init__(self, viewmanager,
                  mode=PythonDisViewerModes.SourceDisassemblyMode,
@@ -54,19 +56,7 @@
         @type QWidget
         """
         super(PythonDisViewer, self).__init__(parent)
-        
-        self.__layout = QVBoxLayout(self)
-        self.setLayout(self.__layout)
-        self.__disWidget = QTreeWidget(self)
-        self.__layout.addWidget(self.__disWidget)
-        self.__layout.setContentsMargins(0, 0, 0, 0)
-        
-        self.__currentInfoLabel = QLabel(self.tr(
-            "italic: current instruction"))
-        self.__labeledInfoLabel = QLabel(self.tr(
-            "bold: labeled instruction"))
-        self.__layout.addWidget(self.__currentInfoLabel)
-        self.__layout.addWidget(self.__labeledInfoLabel)
+        self.setupUi(self)
         
         self.setWindowTitle(self.tr("Disassembly"))
         
@@ -78,21 +68,30 @@
         self.__editor = None
         self.__source = ""
         
-        self.__disWidget.setHeaderLabels(
+        self.disWidget.setHeaderLabels(
             [self.tr("Line"), self.tr("Offset"), self.tr("Operation"),
              self.tr("Parameters"), self.tr("Interpreted Parameters")])
-        self.__disWidget.setSortingEnabled(False)
-        self.__disWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
-        self.__disWidget.setSelectionMode(QAbstractItemView.SingleSelection)
-        self.__disWidget.setAlternatingRowColors(True)
+        self.codeInfoWidget.setHeaderLabels(
+            [self.tr("Key"), self.tr("Value")])
         
-        self.__menu = QMenu(self.__disWidget)
-        self.__menu.addAction(self.tr('Expand All'), self.__expandAll)
-        self.__menu.addAction(self.tr('Collapse All'), self.__collapseAll)
+        self.__disMenu = QMenu(self.disWidget)
+        if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
+            self.__codeInfoAct = self.__disMenu.addAction(
+                self.tr("Show Code Info"), self.__showCodeInfo)
+            self.__disMenu.addSeparator()
+        self.__disMenu.addAction(
+            self.tr('Expand All'), self.__expandAllDis)
+        self.__disMenu.addAction(
+            self.tr('Collapse All'), self.__collapseAllDis)
         
-        self.__disWidget.setContextMenuPolicy(Qt.CustomContextMenu)
-        self.__disWidget.customContextMenuRequested.connect(
-            self.__contextMenuRequested)
+        self.__codeInfoMenu = QMenu(self.codeInfoWidget)
+        if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
+            self.__codeInfoMenu.addAction(
+                self.tr("Hide"), self.codeInfoWidget.hide)
+        self.__codeInfoMenu.addAction(
+            self.tr('Expand All'), self.__expandAllCodeInfo)
+        self.__codeInfoMenu.addAction(
+            self.tr('Collapse All'), self.__collapseAllCodeInfo)
         
         self.__errorColor = QBrush(
             Preferences.getPython("DisViewerErrorColor"))
@@ -101,27 +100,44 @@
         self.__jumpTargetColor = QBrush(
             Preferences.getPython("DisViewerLabeledColor"))
         
-        self.__disWidget.itemClicked.connect(self.__disItemClicked)
-        self.__disWidget.itemCollapsed.connect(self.__resizeColumns)
-        self.__disWidget.itemExpanded.connect(self.__resizeColumns)
+        self.disWidget.itemClicked.connect(self.__disItemClicked)
+        self.disWidget.itemCollapsed.connect(self.__resizeDisColumns)
+        self.disWidget.itemExpanded.connect(self.__resizeDisColumns)
+        self.disWidget.customContextMenuRequested.connect(
+            self.__disContextMenuRequested)
+        
+        self.codeInfoWidget.itemCollapsed.connect(self.__resizeCodeInfoColumns)
+        self.codeInfoWidget.itemExpanded.connect(self.__resizeCodeInfoColumns)
+        self.codeInfoWidget.customContextMenuRequested.connect(
+            self.__codeInfoContextMenuRequested)
         
         if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
             self.__vm.disViewerStateChanged.connect(
                 self.__disViewerStateChanged)
+            
+            self.codeInfoWidget.hide()
         
             self.hide()
+        
         elif self.__mode == PythonDisViewerModes.TracebackMode:
             self.__styleLabels()
     
-    def __contextMenuRequested(self, coord):
+    def __disContextMenuRequested(self, coord):
         """
-        Private slot to show the context menu.
+        Private slot to show the context menu of the disassembly widget.
         
         @param coord position of the mouse pointer
         @type QPoint
         """
-        coord = self.__disWidget.mapToGlobal(coord)
-        self.__menu.popup(coord)
+        if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
+            itm = self.disWidget.itemAt(coord)
+            self.__codeInfoAct.setEnabled(bool(itm.data(0, self.CodeInfoRole)))
+            self.disWidget.setCurrentItem(itm)
+        
+        if self.disWidget.topLevelItemCount() > 0:
+            # don't show context menu on empty list
+            coord = self.disWidget.mapToGlobal(coord)
+            self.__disMenu.popup(coord)
     
     def __editorChanged(self, editor):
         """
@@ -245,23 +261,23 @@
                 self.hide()
                 self.__editor = None
     
-    def __expandAll(self):
+    def __expandAllDis(self):
         """
-        Private slot to expand all items.
+        Private slot to expand all items of the disassembly widget.
         """
-        block = self.__disWidget.blockSignals(True)
-        self.__disWidget.expandAll()
-        self.__disWidget.blockSignals(block)
-        self.__resizeColumns()
+        block = self.disWidget.blockSignals(True)
+        self.disWidget.expandAll()
+        self.disWidget.blockSignals(block)
+        self.__resizeDisColumns()
     
-    def __collapseAll(self):
+    def __collapseAllDis(self):
         """
-        Private slot to collapse all items.
+        Private slot to collapse all items of the disassembly widget.
         """
-        block = self.__disWidget.blockSignals(True)
-        self.__disWidget.collapseAll()
-        self.__disWidget.blockSignals(block)
-        self.__resizeColumns()
+        block = self.disWidget.blockSignals(True)
+        self.disWidget.collapseAll()
+        self.disWidget.blockSignals(block)
+        self.__resizeDisColumns()
     
     def __createErrorItem(self, error):
         """
@@ -272,7 +288,7 @@
         @return generated item
         @rtype QTreeWidgetItem
         """
-        itm = QTreeWidgetItem(self.__disWidget, [error])
+        itm = QTreeWidgetItem(self.disWidget, [error])
         itm.setFirstColumnSpanned(True)
         itm.setForeground(0, self.__errorColor)
         return itm
@@ -380,6 +396,31 @@
             endLine = itm.data(0, self.StartLineRole)
         itm.setData(0, self.EndLineRole, endLine)
     
+    def __createCodeInfo(self, co):
+        """
+        Private method to create a dictionary containing the code info data.
+        
+        @param co reference to the code object to generate the info for
+        @type code
+        @return dictionary containing the code info data
+        @rtype dict
+        """
+        return {
+            "name": co.co_name,
+            "filename": co.co_filename,
+            "argcount": co.co_argcount,
+            "posonlyargcount": co.co_posonlyargcount,
+            "kwonlyargcount": co.co_kwonlyargcount,
+            "nlocals": co.co_nlocals,
+            "stacksize": co.co_stacksize,
+            "flags": dis.pretty_flags(co.co_flags),
+            "consts": co.co_consts,
+            "names": co.co_names,
+            "varnames": co.co_varnames,
+            "freevars": co.co_freevars,
+            "cellvars": co.co_cellvars,
+        }
+    
     def __loadDIS(self):
         """
         Private method to generate the Disassembly from the source of the
@@ -395,7 +436,7 @@
             ))
             return
         
-        self.__disWidget.clear()
+        self.disWidget.clear()
         self.__editor.clearAllHighlights()
         
         source = self.__editor.text()
@@ -427,12 +468,12 @@
         
         if codeObject:
             self.setUpdatesEnabled(False)
-            block = self.__disWidget.blockSignals(True)
+            block = self.disWidget.blockSignals(True)
             
-            self.__disassembleObject(codeObject, self.__disWidget, filename)
-            QTimer.singleShot(0, self.__resizeColumns)
+            self.__disassembleObject(codeObject, self.disWidget, filename)
+            QTimer.singleShot(0, self.__resizeDisColumns)
             
-            self.__disWidget.blockSignals(block)
+            self.disWidget.blockSignals(block)
             self.setUpdatesEnabled(True)
         
         QApplication.restoreOverrideCursor()
@@ -451,15 +492,15 @@
                 "instructions" in disassembly and
                 disassembly["instructions"]
             ):
-                self.__disWidget.clear()
+                self.disWidget.clear()
                 
                 self.setUpdatesEnabled(False)
-                block = self.__disWidget.blockSignals(True)
+                block = self.disWidget.blockSignals(True)
                 
                 titleItem = self.__createTitleItem(
                     self.tr("Disassembly of last traceback"),
                     disassembly["firstlineno"],
-                    self.__disWidget
+                    self.disWidget
                 )
                 
                 lasti = disassembly["lasti"]
@@ -486,26 +527,30 @@
                 if lastStartItem:
                     self.__updateItemEndLine(lastStartItem)
                 
-                QTimer.singleShot(0, self.__resizeColumns)
+                QTimer.singleShot(0, self.__resizeDisColumns)
                 
-                self.__disWidget.blockSignals(block)
+                self.disWidget.blockSignals(block)
                 self.setUpdatesEnabled(True)
                 
                 if lasti:
-                    lastInstructions = self.__disWidget.findItems(
+                    lastInstructions = self.disWidget.findItems(
                         "{0:d}".format(lasti),
                         Qt.MatchFixedString | Qt.MatchRecursive, 1)
                     if lastInstructions:
-                        self.__disWidget.scrollToItem(
+                        self.disWidget.scrollToItem(
                             lastInstructions[0],
                             QAbstractItemView.PositionAtCenter)
+                
+                if "codeInfo" in disassembly:
+                    self.__showCodeInfoData(disassembly["codeInfo"])
     
-    def __resizeColumns(self):
+    def __resizeDisColumns(self):
         """
-        Private method to resize the columns to suitable values.
+        Private method to resize the columns of the disassembly widget to
+        suitable values.
         """
-        for col in range(self.__disWidget.columnCount()):
-            self.__disWidget.resizeColumnToContents(col)
+        for col in range(self.disWidget.columnCount()):
+            self.disWidget.resizeColumnToContents(col)
     
     def resizeEvent(self, evt):
         """
@@ -515,13 +560,14 @@
         @type QResizeEvent
         """
         # just adjust the sizes of the columns
-        self.__resizeColumns()
+        self.__resizeDisColumns()
+        self.__resizeCodeInfoColumns()
     
     def __clearSelection(self):
         """
         Private method to clear all selected items.
         """
-        for itm in self.__disWidget.selectedItems():
+        for itm in self.disWidget.selectedItems():
             itm.setSelected(False)
     
     def __selectChildren(self, itm, lineno):
@@ -544,7 +590,7 @@
                 self.__selectChildren(child, lineno)
             
             if child.data(0, self.StartLineRole) == lineno:
-                self.__disWidget.scrollToItem(
+                self.disWidget.scrollToItem(
                     child, QAbstractItemView.PositionAtCenter)
     
     def __selectItemForEditorLine(self):
@@ -560,8 +606,8 @@
         # make the line numbers 1-based
         cline += 1
         
-        for index in range(self.__disWidget.topLevelItemCount()):
-            itm = self.__disWidget.topLevelItem(index)
+        for index in range(self.disWidget.topLevelItemCount()):
+            itm = self.disWidget.topLevelItem(index)
             if (
                 itm.data(0, self.StartLineRole) <= cline <=
                 itm.data(0, self.EndLineRole)
@@ -632,6 +678,10 @@
             title = self.tr("Code Object '{0}'").format(name)
         titleItem = self.__createTitleItem(title, co.co_firstlineno,
                                            parentItem)
+        codeInfo = self.__createCodeInfo(co)
+        if codeInfo:
+            titleItem.setData(0, self.CodeInfoRole, codeInfo)
+        
         lastStartItem = None
         for instr in dis.get_instructions(co):
             if instr.starts_line:
@@ -673,28 +723,140 @@
         Private method to style the info labels iaw. selected colors.
         """
         # current instruction
-        self.__currentInfoLabel.setStyleSheet(
+        self.currentInfoLabel.setStyleSheet(
             "QLabel {{ color : {0}; }}".format(
                 self.__currentInstructionColor.color().name()
             )
         )
-        font = self.__currentInfoLabel.font()
+        font = self.currentInfoLabel.font()
         font.setItalic(True)
-        self.__currentInfoLabel.setFont(font)
+        self.currentInfoLabel.setFont(font)
         
         # labeled instruction
-        self.__labeledInfoLabel.setStyleSheet(
+        self.labeledInfoLabel.setStyleSheet(
             "QLabel {{ color : {0}; }}".format(
                 self.__jumpTargetColor.color().name()
             )
         )
-        font = self.__labeledInfoLabel.font()
+        font = self.labeledInfoLabel.font()
         font.setBold(True)
-        self.__labeledInfoLabel.setFont(font)
+        self.labeledInfoLabel.setFont(font)
     
     @pyqtSlot()
     def clear(self):
         """
         Public method to clear the display.
         """
-        self.__disWidget.clear()
+        self.disWidget.clear()
+    
+    def __showCodeInfo(self):
+        """
+        Private slot handling the context menu action to show code info.
+        """
+        itm = self.disWidget.currentItem()
+        codeInfo = itm.data(0, self.CodeInfoRole)
+        if codeInfo:
+            self.codeInfoWidget.show()
+            self.__showCodeInfoData(codeInfo)
+    
+    def __showCodeInfoData(self, codeInfo):
+        """
+        Private method to show the passed code info data.
+        
+        @param codeInfo dictionary containing the code info data
+        @type dict
+        """
+        def createCodeInfoItems(title, infoList):
+            """
+            Function to create code info items for the given list.
+            
+            @param title title string for the list
+            @type str
+            @param infoList list of info strings
+            @type list of str
+            """
+            parent = QTreeWidgetItem(self.codeInfoWidget,
+                                     [title, str(len(infoList))])
+            # TODO: make this a configuration item
+            parent.setExpanded(False)
+            
+            for index, value in enumerate(infoList):
+                itm = QTreeWidgetItem(parent, [str(index), str(value)])
+                itm.setTextAlignment(0, Qt.AlignRight)
+        
+        self.codeInfoWidget.clear()
+        
+        if codeInfo:
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Name"), codeInfo["name"]])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Filename"), codeInfo["filename"]])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Argument Count"), str(codeInfo["argcount"])])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Positional-only Arguments"),
+                str(codeInfo["posonlyargcount"])])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Keyword-only arguments"),
+                str(codeInfo["kwonlyargcount"])])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Number of Locals"), str(codeInfo["nlocals"])])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Stack Size"), str(codeInfo["stacksize"])])
+            QTreeWidgetItem(self.codeInfoWidget, [
+                self.tr("Flags"), codeInfo["flags"]])
+            if codeInfo["consts"]:
+                createCodeInfoItems(self.tr("Constants"),
+                                    codeInfo["consts"])
+            if codeInfo["names"]:
+                createCodeInfoItems(self.tr("Names"),
+                                    codeInfo["names"])
+            if codeInfo["varnames"]:
+                createCodeInfoItems(self.tr("Variable Names"),
+                                    codeInfo["varnames"])
+            if codeInfo["freevars"]:
+                createCodeInfoItems(self.tr("Free Variables"),
+                                    codeInfo["freevars"])
+            if codeInfo["cellvars"]:
+                createCodeInfoItems(self.tr("Cell Variables"),
+                                    codeInfo["cellvars"])
+            
+            QTimer.singleShot(0, self.__resizeCodeInfoColumns)
+    
+    def __resizeCodeInfoColumns(self):
+        """
+        Private method to resize the columns of the code info widget to
+        suitable values.
+        """
+        for col in range(self.codeInfoWidget.columnCount()):
+            self.codeInfoWidget.resizeColumnToContents(col)
+    
+    def __expandAllCodeInfo(self):
+        """
+        Private slot to expand all items of the code info widget.
+        """
+        block = self.codeInfoWidget.blockSignals(True)
+        self.codeInfoWidget.expandAll()
+        self.codeInfoWidget.blockSignals(block)
+        self.__resizeCodeInfoColumns()
+    
+    def __collapseAllCodeInfo(self):
+        """
+        Private slot to collapse all items of the code info widget.
+        """
+        block = self.codeInfoWidget.blockSignals(True)
+        self.codeInfoWidget.collapseAll()
+        self.codeInfoWidget.blockSignals(block)
+        self.__resizeCodeInfoColumns()
+    
+    def __codeInfoContextMenuRequested(self, coord):
+        """
+        Private slot to show the context menu of the code info widget.
+        
+        @param coord position of the mouse pointer
+        @type QPoint
+        """
+        if self.disWidget.topLevelItemCount() > 0:
+            # don't show context menu on empty list
+            coord = self.codeInfoWidget.mapToGlobal(coord)
+            self.__codeInfoMenu.popup(coord)

eric ide

mercurial