src/eric7/UI/PythonAstViewer.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/UI/PythonAstViewer.py
--- a/src/eric7/UI/PythonAstViewer.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/UI/PythonAstViewer.py	Wed Jul 13 14:55:47 2022 +0200
@@ -13,7 +13,11 @@
 from PyQt6.QtCore import pyqtSlot, Qt, QTimer
 from PyQt6.QtGui import QBrush
 from PyQt6.QtWidgets import (
-    QTreeWidget, QTreeWidgetItem, QAbstractItemView, QWidget, QVBoxLayout
+    QTreeWidget,
+    QTreeWidgetItem,
+    QAbstractItemView,
+    QWidget,
+    QVBoxLayout,
 )
 
 from asttokens import ASTTokens
@@ -28,56 +32,57 @@
     Class implementing a widget to visualize the Python AST for some Python
     sources.
     """
+
     StartLineRole = Qt.ItemDataRole.UserRole
     StartIndexRole = Qt.ItemDataRole.UserRole + 1
     EndLineRole = Qt.ItemDataRole.UserRole + 2
     EndIndexRole = Qt.ItemDataRole.UserRole + 3
-    
+
     def __init__(self, viewmanager, parent=None):
         """
         Constructor
-        
+
         @param viewmanager reference to the viewmanager object
         @type ViewManager
         @param parent reference to the parent widget
         @type QWidget
         """
         super().__init__(parent)
-        
+
         self.__layout = QVBoxLayout(self)
         self.setLayout(self.__layout)
         self.__astWidget = QTreeWidget(self)
         self.__layout.addWidget(self.__astWidget)
         self.__layout.setContentsMargins(0, 0, 0, 0)
-        
+
         self.__vm = viewmanager
         self.__vmConnected = False
-        
+
         self.__editor = None
         self.__source = ""
-        
-        self.__astWidget.setHeaderLabels([self.tr("Node"),
-                                          self.tr("Code Range")])
+
+        self.__astWidget.setHeaderLabels([self.tr("Node"), self.tr("Code Range")])
         self.__astWidget.setSortingEnabled(False)
         self.__astWidget.setSelectionBehavior(
-            QAbstractItemView.SelectionBehavior.SelectRows)
+            QAbstractItemView.SelectionBehavior.SelectRows
+        )
         self.__astWidget.setSelectionMode(
-            QAbstractItemView.SelectionMode.SingleSelection)
+            QAbstractItemView.SelectionMode.SingleSelection
+        )
         self.__astWidget.setAlternatingRowColors(True)
-        
-        self.__errorColor = QBrush(
-            Preferences.getPython("ASTViewerErrorColor"))
-        
+
+        self.__errorColor = QBrush(Preferences.getPython("ASTViewerErrorColor"))
+
         self.__astWidget.itemClicked.connect(self.__astItemClicked)
-        
+
         self.__vm.astViewerStateChanged.connect(self.__astViewerStateChanged)
-        
+
         self.hide()
-    
+
     def __editorChanged(self, editor):
         """
         Private slot to handle a change of the current editor.
-        
+
         @param editor reference to the current editor
         @type Editor
         """
@@ -87,21 +92,21 @@
             self.__editor = editor
             if self.__editor:
                 self.__loadAST()
-    
+
     def __editorSaved(self, editor):
         """
         Private slot to reload the AST after the connected editor was saved.
-        
+
         @param editor reference to the editor that performed a save action
         @type Editor
         """
         if editor and editor is self.__editor:
             self.__loadAST()
-    
+
     def __editorDoubleClicked(self, editor, pos, buttons):
         """
         Private slot to handle a mouse button double click in the editor.
-        
+
         @param editor reference to the editor, that emitted the signal
         @type Editor
         @param pos position of the double click
@@ -113,70 +118,67 @@
             if editor.isModified():
                 # reload the source
                 QTimer.singleShot(0, self.__loadAST)
-            
+
             # highlight the corresponding entry
             QTimer.singleShot(0, self.__selectItemForEditorSelection)
             QTimer.singleShot(0, self.__grabFocus)
-    
+
     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
         manager.
         """
         self.hide()
-    
+
     def show(self):
         """
         Public slot to show the AST viewer.
         """
         super().show()
-        
+
         if not self.__vmConnected:
             self.__vm.editorChangedEd.connect(self.__editorChanged)
             self.__vm.editorSavedEd.connect(self.__editorSaved)
             self.__vm.editorDoubleClickedEd.connect(self.__editorDoubleClicked)
-            self.__vm.editorLanguageChanged.connect(
-                self.__editorLanguageChanged)
+            self.__vm.editorLanguageChanged.connect(self.__editorLanguageChanged)
             self.__vmConnected = True
-    
+
     def hide(self):
         """
         Public slot to hide the AST viewer.
         """
         super().hide()
-        
+
         if self.__editor:
             self.__editor.clearAllHighlights()
-        
+
         if self.__vmConnected:
             self.__vm.editorChangedEd.disconnect(self.__editorChanged)
             self.__vm.editorSavedEd.disconnect(self.__editorSaved)
-            self.__vm.editorDoubleClickedEd.disconnect(
-                self.__editorDoubleClicked)
-            self.__vm.editorLanguageChanged.disconnect(
-                self.__editorLanguageChanged)
+            self.__vm.editorDoubleClickedEd.disconnect(self.__editorDoubleClicked)
+            self.__vm.editorLanguageChanged.disconnect(self.__editorLanguageChanged)
             self.__vmConnected = False
-    
+
     def shutdown(self):
         """
         Public method to perform shutdown actions.
         """
         self.__editor = None
-    
+
     def __astViewerStateChanged(self, on):
         """
         Private slot to toggle the display of the AST viewer.
-        
+
         @param on flag indicating to show the AST
         @type bool
         """
@@ -189,11 +191,11 @@
         else:
             self.hide()
             self.__editor = None
-    
+
     def __createErrorItem(self, error):
         """
         Private method to create a top level error item.
-        
+
         @param error error message
         @type str
         @return generated item
@@ -203,35 +205,33 @@
         itm.setFirstColumnSpanned(True)
         itm.setForeground(0, self.__errorColor)
         return itm
-    
+
     def __loadAST(self):
         """
         Private method to generate the AST from the source of the current
         editor and visualize it.
         """
         if not self.__editor:
-            self.__createErrorItem(self.tr(
-                "No editor has been opened."
-            ))
+            self.__createErrorItem(self.tr("No editor has been opened."))
             return
-        
+
         self.__astWidget.clear()
         self.__editor.clearAllHighlights()
-        
+
         source = self.__editor.text()
         if not source.strip():
             # empty editor or white space only
-            self.__createErrorItem(self.tr(
-                "The current editor does not contain any source code."
-            ))
+            self.__createErrorItem(
+                self.tr("The current editor does not contain any source code.")
+            )
             return
-        
+
         if not self.__editor.isPyFile():
-            self.__createErrorItem(self.tr(
-                "The current editor does not contain Python source code."
-            ))
+            self.__createErrorItem(
+                self.tr("The current editor does not contain Python source code.")
+            )
             return
-        
+
         with EricOverrideCursor():
             try:
                 # generate the AST
@@ -241,23 +241,23 @@
             except Exception as exc:
                 self.__createErrorItem(str(exc))
                 astValid = False
-            
+
             if astValid:
                 self.setUpdatesEnabled(False)
-                
+
                 # populate the AST tree
                 self.__populateNode(self.tr("Module"), root, self.__astWidget)
                 self.__selectItemForEditorSelection()
                 QTimer.singleShot(0, self.__resizeColumns)
-                
+
                 self.setUpdatesEnabled(True)
-        
+
         self.__grabFocus()
-    
+
     def __populateNode(self, name, nodeOrFields, parent):
         """
         Private method to populate the tree view with a node.
-        
+
         @param name name of the node
         @type str
         @param nodeOrFields reference to the node or a list node fields
@@ -277,52 +277,48 @@
         else:
             fields = []
             value = repr(nodeOrFields)
-        
+
         text = self.tr("{0}: {1}").format(name, value)
         itm = QTreeWidgetItem(parent, [text])
         itm.setExpanded(True)
-        
-        if (
-            hasattr(nodeOrFields, "lineno") and
-            hasattr(nodeOrFields, "col_offset")
-        ):
+
+        if hasattr(nodeOrFields, "lineno") and hasattr(nodeOrFields, "col_offset"):
             itm.setData(0, self.StartLineRole, nodeOrFields.lineno)
             itm.setData(0, self.StartIndexRole, nodeOrFields.col_offset)
             startStr = self.tr("{0},{1}").format(
-                nodeOrFields.lineno, nodeOrFields.col_offset)
+                nodeOrFields.lineno, nodeOrFields.col_offset
+            )
             endStr = ""
-            
-            if (
-                hasattr(nodeOrFields, "end_lineno") and
-                hasattr(nodeOrFields, "end_col_offset")
+
+            if hasattr(nodeOrFields, "end_lineno") and hasattr(
+                nodeOrFields, "end_col_offset"
             ):
                 itm.setData(0, self.EndLineRole, nodeOrFields.end_lineno)
-                itm.setData(0, self.EndIndexRole,
-                            nodeOrFields.end_col_offset)
+                itm.setData(0, self.EndIndexRole, nodeOrFields.end_col_offset)
                 endStr = self.tr("{0},{1}").format(
-                    nodeOrFields.end_lineno, nodeOrFields.end_col_offset)
+                    nodeOrFields.end_lineno, nodeOrFields.end_col_offset
+                )
             else:
                 itm.setData(0, self.EndLineRole, nodeOrFields.lineno)
-                itm.setData(0, self.EndIndexRole,
-                            nodeOrFields.col_offset + 1)
+                itm.setData(0, self.EndIndexRole, nodeOrFields.col_offset + 1)
             if endStr:
                 rangeStr = self.tr("{0}  -  {1}").format(startStr, endStr)
             else:
                 rangeStr = startStr
-            
+
             itm.setText(1, rangeStr)
-        
+
         for fieldName, fieldValue in fields:
             self.__populateNode(fieldName, fieldValue, itm)
-    
+
     def __markTextRanges(self, tree, source):
         """
         Private method to modify the AST nodes with end_lineno and
         end_col_offset information.
-        
+
         Note: The modifications are only done for nodes containing lineno and
         col_offset attributes.
-        
+
         @param tree reference to the AST to be modified
         @type ast.AST
         @param source source code the AST was created from
@@ -330,17 +326,17 @@
         """
         ASTTokens(source, tree=tree)
         for child in ast.walk(tree):
-            if hasattr(child, 'last_token'):
+            if hasattr(child, "last_token"):
                 child.end_lineno, child.end_col_offset = child.last_token.end
-                if hasattr(child, 'lineno'):
+                if hasattr(child, "lineno"):
                     # Fixes problems with some nodes like binop
                     child.lineno, child.col_offset = child.first_token.start
-    
+
     def __findClosestContainingNode(self, node, textRange):
         """
         Private method to search for the AST node that contains a range
         closest.
-        
+
         @param node AST node to start searching at
         @type ast.AST
         @param textRange tuple giving the start and end positions
@@ -351,27 +347,28 @@
         if textRange in [(-1, -1, -1, -1), (0, -1, 0, -1)]:
             # no valid range, i.e. no selection
             return None
-        
+
         # first look among children
         for child in ast.iter_child_nodes(node):
             result = self.__findClosestContainingNode(child, textRange)
             if result is not None:
                 return result
-        
+
         # no suitable child was found
         if hasattr(node, "lineno") and self.__rangeContainsSmaller(
-            (node.lineno, node.col_offset, node.end_lineno,
-             node.end_col_offset), textRange):
+            (node.lineno, node.col_offset, node.end_lineno, node.end_col_offset),
+            textRange,
+        ):
             return node
         else:
             # nope
             return None
-    
+
     def __findClosestContainingItem(self, itm, textRange):
         """
         Private method to search for the tree item that contains a range
         closest.
-        
+
         @param itm tree item to start searching at
         @type QTreeWidgetItem
         @param textRange tuple giving the start and end positions
@@ -382,59 +379,69 @@
         if textRange in [(-1, -1, -1, -1), (0, -1, 0, -1)]:
             # no valid range, i.e. no selection
             return None
-        
+
         lineno = itm.data(0, self.StartLineRole)
         if lineno is not None and not self.__rangeContainsSmallerOrEqual(
-           (itm.data(0, self.StartLineRole), itm.data(0, self.StartIndexRole),
-            itm.data(0, self.EndLineRole), itm.data(0, self.EndIndexRole)),
-           textRange):
+            (
+                itm.data(0, self.StartLineRole),
+                itm.data(0, self.StartIndexRole),
+                itm.data(0, self.EndLineRole),
+                itm.data(0, self.EndIndexRole),
+            ),
+            textRange,
+        ):
             return None
-        
+
         # first look among children
         for index in range(itm.childCount()):
             child = itm.child(index)
             result = self.__findClosestContainingItem(child, textRange)
             if result is not None:
                 return result
-        
+
         # no suitable child was found
         lineno = itm.data(0, self.StartLineRole)
         if lineno is not None and self.__rangeContainsSmallerOrEqual(
-           (itm.data(0, self.StartLineRole), itm.data(0, self.StartIndexRole),
-            itm.data(0, self.EndLineRole), itm.data(0, self.EndIndexRole)),
-           textRange):
+            (
+                itm.data(0, self.StartLineRole),
+                itm.data(0, self.StartIndexRole),
+                itm.data(0, self.EndLineRole),
+                itm.data(0, self.EndIndexRole),
+            ),
+            textRange,
+        ):
             return itm
         else:
             # nope
             return None
-    
+
     def __resizeColumns(self):
         """
         Private method to resize the columns to suitable values.
         """
         for col in range(self.__astWidget.columnCount()):
             self.__astWidget.resizeColumnToContents(col)
-        
+
         rangeSize = self.__astWidget.columnWidth(1) + 10
         # 10 px extra for the range
         nodeSize = max(400, self.__astWidget.viewport().width() - rangeSize)
         self.__astWidget.setColumnWidth(0, nodeSize)
         self.__astWidget.setColumnWidth(1, rangeSize)
-    
+
     def resizeEvent(self, evt):
         """
         Protected method to handle resize events.
-        
+
         @param evt resize event
         @type QResizeEvent
         """
         # just adjust the sizes of the columns
         self.__resizeColumns()
-    
+
     def __rangeContainsSmaller(self, first, second):
         """
         Private method to check, if second is contained in first.
-        
+
         @param first text range to check against
         @type tuple of (int, int, int, int)
         @param second text range to check for
@@ -448,15 +455,15 @@
         secondEnd = second[2:]
 
         return (
-            (firstStart < secondStart and firstEnd > secondEnd) or
-            (firstStart == secondStart and firstEnd > secondEnd) or
-            (firstStart < secondStart and firstEnd == secondEnd)
+            (firstStart < secondStart and firstEnd > secondEnd)
+            or (firstStart == secondStart and firstEnd > secondEnd)
+            or (firstStart < secondStart and firstEnd == secondEnd)
         )
-    
+
     def __rangeContainsSmallerOrEqual(self, first, second):
         """
         Private method to check, if second is contained in or equal to first.
-        
+
         @param first text range to check against
         @type tuple of (int, int, int, int)
         @param second text range to check for
@@ -465,69 +472,69 @@
         @rtype bool
         """
         return first == second or self.__rangeContainsSmaller(first, second)
-    
+
     def __clearSelection(self):
         """
         Private method to clear all selected items.
         """
         for itm in self.__astWidget.selectedItems():
             itm.setSelected(False)
-    
+
     def __selectItemForEditorSelection(self):
         """
         Private slot to select the item corresponding to an editor selection.
         """
         # step 1: clear all selected items
         self.__clearSelection()
-        
+
         # step 2: retrieve the editor selection
         selection = self.__editor.getSelection()
         # make the line numbers 1-based
-        selection = (selection[0] + 1, selection[1],
-                     selection[2] + 1, selection[3])
-        
+        selection = (selection[0] + 1, selection[1], selection[2] + 1, selection[3])
+
         # step 3: search the corresponding item, scroll to it and select it
         itm = self.__findClosestContainingItem(
-            self.__astWidget.topLevelItem(0), selection)
+            self.__astWidget.topLevelItem(0), selection
+        )
         if itm:
             self.__astWidget.scrollToItem(
-                itm, QAbstractItemView.ScrollHint.PositionAtCenter)
+                itm, QAbstractItemView.ScrollHint.PositionAtCenter
+            )
             itm.setSelected(True)
-    
+
     def __grabFocus(self):
         """
         Private method to grab the input focus.
         """
         self.__astWidget.setFocus(Qt.FocusReason.OtherFocusReason)
-    
+
     @pyqtSlot(QTreeWidgetItem, int)
     def __astItemClicked(self, itm, column):
         """
         Private slot handling a user click on an AST node item.
-        
+
         @param itm reference to the clicked item
         @type QTreeWidgetItem
         @param column column number of the click
         @type int
         """
         self.__editor.clearAllHighlights()
-        
+
         if itm is not None:
             startLine = itm.data(0, self.StartLineRole)
             if startLine is not None:
                 startIndex = itm.data(0, self.StartIndexRole)
                 endLine = itm.data(0, self.EndLineRole)
                 endIndex = itm.data(0, self.EndIndexRole)
-                
-                self.__editor.gotoLine(startLine, firstVisible=True,
-                                       expand=True)
-                self.__editor.setHighlight(startLine - 1, startIndex,
-                                           endLine - 1, endIndex)
-    
+
+                self.__editor.gotoLine(startLine, firstVisible=True, expand=True)
+                self.__editor.setHighlight(
+                    startLine - 1, startIndex, endLine - 1, endIndex
+                )
+
     @pyqtSlot()
     def preferencesChanged(self):
         """
         Public slot handling changes of the AST viewer settings.
         """
-        self.__errorColor = QBrush(
-            Preferences.getPython("ASTViewerErrorColor"))
+        self.__errorColor = QBrush(Preferences.getPython("ASTViewerErrorColor"))

eric ide

mercurial