eric6/UI/PythonDisViewer.py

changeset 7707
6abcf4275d0e
parent 7705
90a9aefd4253
child 7710
9aad21c7765d
equal deleted inserted replaced
7706:0c6d32ec64f1 7707:6abcf4275d0e
8 Python sources. 8 Python sources.
9 """ 9 """
10 10
11 import os 11 import os
12 import dis 12 import dis
13
14 import enum
15
13 16
14 from PyQt5.QtCore import pyqtSlot, Qt, QTimer 17 from PyQt5.QtCore import pyqtSlot, Qt, QTimer
15 from PyQt5.QtGui import QCursor, QBrush 18 from PyQt5.QtGui import QCursor, QBrush
16 from PyQt5.QtWidgets import ( 19 from PyQt5.QtWidgets import (
17 QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, 20 QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget,
18 QVBoxLayout, QLabel, QMenu 21 QVBoxLayout, QLabel, QMenu
19 ) 22 )
20 23
21 import Preferences 24 import Preferences
25
26
27 class PythonDisViewerModes(enum.Enum):
28 """
29 Class implementing the disassembly viewer operation modes.
30 """
31 SourceDisassemblyMode = 0
32 TracebackMode = 1
22 33
23 34
24 class PythonDisViewer(QWidget): 35 class PythonDisViewer(QWidget):
25 """ 36 """
26 Class implementing a widget to visualize the Python Disassembly for some 37 Class implementing a widget to visualize the Python Disassembly for some
27 Python sources. 38 Python sources.
28 """ 39 """
29 StartLineRole = Qt.UserRole 40 StartLineRole = Qt.UserRole
30 EndLineRole = Qt.UserRole + 1 41 EndLineRole = Qt.UserRole + 1
31 42
32 def __init__(self, viewmanager, parent=None): 43 def __init__(self, viewmanager,
44 mode=PythonDisViewerModes.SourceDisassemblyMode,
45 parent=None):
33 """ 46 """
34 Constructor 47 Constructor
35 48
36 @param viewmanager reference to the viewmanager object 49 @param viewmanager reference to the viewmanager object
37 @type ViewManager 50 @type ViewManager
51 @param mode operation mode of the viewer
52 @type int
38 @param parent reference to the parent widget 53 @param parent reference to the parent widget
39 @type QWidget 54 @type QWidget
40 """ 55 """
41 super(PythonDisViewer, self).__init__(parent) 56 super(PythonDisViewer, self).__init__(parent)
42 57
51 self.__labeledInfoLabel = QLabel(self.tr( 66 self.__labeledInfoLabel = QLabel(self.tr(
52 "bold: labeled instruction")) 67 "bold: labeled instruction"))
53 self.__layout.addWidget(self.__currentInfoLabel) 68 self.__layout.addWidget(self.__currentInfoLabel)
54 self.__layout.addWidget(self.__labeledInfoLabel) 69 self.__layout.addWidget(self.__labeledInfoLabel)
55 70
71 self.setWindowTitle(self.tr("Disassembly"))
72
56 self.__vm = viewmanager 73 self.__vm = viewmanager
57 self.__vmConnected = False 74 self.__vmConnected = False
75
76 self.__mode = mode
58 77
59 self.__editor = None 78 self.__editor = None
60 self.__source = "" 79 self.__source = ""
61 80
62 self.__disWidget.setHeaderLabels( 81 self.__disWidget.setHeaderLabels(
84 103
85 self.__disWidget.itemClicked.connect(self.__disItemClicked) 104 self.__disWidget.itemClicked.connect(self.__disItemClicked)
86 self.__disWidget.itemCollapsed.connect(self.__resizeColumns) 105 self.__disWidget.itemCollapsed.connect(self.__resizeColumns)
87 self.__disWidget.itemExpanded.connect(self.__resizeColumns) 106 self.__disWidget.itemExpanded.connect(self.__resizeColumns)
88 107
89 self.__vm.disViewerStateChanged.connect(self.__disViewerStateChanged) 108 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
90 109 self.__vm.disViewerStateChanged.connect(
91 self.hide() 110 self.__disViewerStateChanged)
111
112 self.hide()
113 elif self.__mode == PythonDisViewerModes.TracebackMode:
114 self.__styleLabels()
92 115
93 def __contextMenuRequested(self, coord): 116 def __contextMenuRequested(self, coord):
94 """ 117 """
95 Private slot to show the context menu. 118 Private slot to show the context menu.
96 119
140 QTimer.singleShot(0, self.__loadDIS) 163 QTimer.singleShot(0, self.__loadDIS)
141 164
142 # highlight the corresponding entry 165 # highlight the corresponding entry
143 QTimer.singleShot(0, self.__selectItemForEditorLine) 166 QTimer.singleShot(0, self.__selectItemForEditorLine)
144 167
168 def __editorLanguageChanged(self, editor):
169 """
170 Private slot to handle a change of the editor language.
171
172 @param editor reference to the editor which changed language
173 @type Editor
174 """
175 if editor is self.__editor:
176 QTimer.singleShot(0, self.__loadDIS)
177
145 def __lastEditorClosed(self): 178 def __lastEditorClosed(self):
146 """ 179 """
147 Private slot to handle the last editor closed signal of the view 180 Private slot to handle the last editor closed signal of the view
148 manager. 181 manager.
149 """ 182 """
153 """ 186 """
154 Public slot to show the DIS viewer. 187 Public slot to show the DIS viewer.
155 """ 188 """
156 super(PythonDisViewer, self).show() 189 super(PythonDisViewer, self).show()
157 190
158 if not self.__vmConnected: 191 if (
192 self.__mode == PythonDisViewerModes.SourceDisassemblyMode and
193 not self.__vmConnected
194 ):
159 self.__vm.editorChangedEd.connect(self.__editorChanged) 195 self.__vm.editorChangedEd.connect(self.__editorChanged)
160 self.__vm.editorSavedEd.connect(self.__editorSaved) 196 self.__vm.editorSavedEd.connect(self.__editorSaved)
161 self.__vm.editorLineChangedEd.connect(self.__editorLineChanged) 197 self.__vm.editorLineChangedEd.connect(self.__editorLineChanged)
198 self.__vm.editorLanguageChanged.connect(
199 self.__editorLanguageChanged)
162 self.__vmConnected = True 200 self.__vmConnected = True
201
202 self.__styleLabels()
163 203
164 def hide(self): 204 def hide(self):
165 """ 205 """
166 Public slot to hide the DIS viewer. 206 Public slot to hide the DIS viewer.
167 """ 207 """
168 super(PythonDisViewer, self).hide() 208 super(PythonDisViewer, self).hide()
169 209
170 if self.__editor: 210 if self.__editor:
171 self.__editor.clearAllHighlights() 211 self.__editor.clearAllHighlights()
172 212
173 if self.__vmConnected: 213 if (
214 self.__mode == PythonDisViewerModes.SourceDisassemblyMode and
215 self.__vmConnected
216 ):
174 self.__vm.editorChangedEd.disconnect(self.__editorChanged) 217 self.__vm.editorChangedEd.disconnect(self.__editorChanged)
175 self.__vm.editorSavedEd.disconnect(self.__editorSaved) 218 self.__vm.editorSavedEd.disconnect(self.__editorSaved)
176 self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged) 219 self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged)
220 self.__vm.editorLanguageChanged.disconnect(
221 self.__editorLanguageChanged)
177 self.__vmConnected = False 222 self.__vmConnected = False
178 223
179 def shutdown(self): 224 def shutdown(self):
180 """ 225 """
181 Public method to perform shutdown actions. 226 Public method to perform shutdown actions.
187 Private slot to toggle the display of the Disassembly viewer. 232 Private slot to toggle the display of the Disassembly viewer.
188 233
189 @param on flag indicating to show the Disassembly 234 @param on flag indicating to show the Disassembly
190 @type bool 235 @type bool
191 """ 236 """
192 editor = self.__vm.activeWindow() 237 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode:
193 if on and editor and editor.isPyFile(): 238 editor = self.__vm.activeWindow()
194 if editor is not self.__editor: 239 if on:
195 self.__editor = editor 240 if editor is not self.__editor:
196 self.show() 241 self.__editor = editor
197 self.__loadDIS() 242 self.show()
198 else: 243 self.__loadDIS()
199 self.hide() 244 else:
200 self.__editor = None 245 self.hide()
246 self.__editor = None
201 247
202 def __expandAll(self): 248 def __expandAll(self):
203 """ 249 """
204 Private slot to expand all items. 250 Private slot to expand all items.
205 """ 251 """
337 def __loadDIS(self): 383 def __loadDIS(self):
338 """ 384 """
339 Private method to generate the Disassembly from the source of the 385 Private method to generate the Disassembly from the source of the
340 current editor and visualize it. 386 current editor and visualize it.
341 """ 387 """
388 if self.__mode != PythonDisViewerModes.SourceDisassemblyMode:
389 # wrong mode, just return
390 return
391
342 if not self.__editor: 392 if not self.__editor:
393 self.__createErrorItem(self.tr(
394 "No editor has been opened."
395 ))
343 return 396 return
344 397
345 self.__disWidget.clear() 398 self.__disWidget.clear()
346 self.__editor.clearAllHighlights() 399 self.__editor.clearAllHighlights()
347
348 if not self.__editor.isPyFile():
349 self.__createErrorItem(self.tr(
350 "The current editor text does not contain Python source."
351 ))
352 return
353 400
354 source = self.__editor.text() 401 source = self.__editor.text()
355 if not source.strip(): 402 if not source.strip():
356 # empty editor or white space only 403 # empty editor or white space only
404 self.__createErrorItem(self.tr(
405 "The current editor does not contain any source code."
406 ))
407 return
408
409 if not self.__editor.isPyFile():
410 self.__createErrorItem(self.tr(
411 "The current editor does not contain Python source code."
412 ))
357 return 413 return
358 414
359 filename = self.__editor.getFileName() 415 filename = self.__editor.getFileName()
360 if filename: 416 if filename:
361 filename = os.path.basename(filename) 417 filename = os.path.basename(filename)
378 434
379 self.__disWidget.blockSignals(block) 435 self.__disWidget.blockSignals(block)
380 self.setUpdatesEnabled(True) 436 self.setUpdatesEnabled(True)
381 437
382 QApplication.restoreOverrideCursor() 438 QApplication.restoreOverrideCursor()
383 439
384 self.__grabFocus() 440 @pyqtSlot(dict)
441 def showDisassembly(self, disassembly):
442 """
443 Public slot to receive a code disassembly from the debug client.
444
445 @param disassembly dictionary containing the disassembly information
446 @type dict
447 """
448 if self.__mode == PythonDisViewerModes.TracebackMode:
449 if (
450 disassembly and
451 "instructions" in disassembly and
452 disassembly["instructions"]
453 ):
454 self.__disWidget.clear()
455
456 self.setUpdatesEnabled(False)
457 block = self.__disWidget.blockSignals(True)
458
459 titleItem = self.__createTitleItem(
460 self.tr("Disassembly of last traceback"),
461 disassembly["firstlineno"],
462 self.__disWidget
463 )
464
465 lasti = disassembly["lasti"]
466 lastStartItem = None
467 for instrDict in disassembly["instructions"]:
468 instr = dis.Instruction(
469 instrDict["opname"],
470 0, # dummy value
471 instrDict["arg"],
472 "", # dummy value
473 instrDict["argrepr"],
474 instrDict["offset"],
475 instrDict["lineno"],
476 instrDict["isJumpTarget"],
477 )
478 if instrDict["lineno"] > 0:
479 if lastStartItem:
480 self.__updateItemEndLine(lastStartItem)
481 lastStartItem = self.__createInstructionItem(
482 instr, titleItem, lasti=lasti)
483 else:
484 self.__createInstructionItem(
485 instr, lastStartItem, lasti=lasti)
486 if lastStartItem:
487 self.__updateItemEndLine(lastStartItem)
488
489 QTimer.singleShot(0, self.__resizeColumns)
490
491 self.__disWidget.blockSignals(block)
492 self.setUpdatesEnabled(True)
493
494 if lasti:
495 lastInstructions = self.__disWidget.findItems(
496 "{0:d}".format(lasti),
497 Qt.MatchFixedString | Qt.MatchRecursive, 1)
498 if lastInstructions:
499 self.__disWidget.scrollToItem(
500 lastInstructions[0],
501 QAbstractItemView.PositionAtCenter)
385 502
386 def __resizeColumns(self): 503 def __resizeColumns(self):
387 """ 504 """
388 Private method to resize the columns to suitable values. 505 Private method to resize the columns to suitable values.
389 """ 506 """
450 itm.data(0, self.EndLineRole) 567 itm.data(0, self.EndLineRole)
451 ): 568 ):
452 itm.setSelected(True) 569 itm.setSelected(True)
453 self.__selectChildren(itm, cline) 570 self.__selectChildren(itm, cline)
454 571
455 def __grabFocus(self):
456 """
457 Private method to grab the input focus.
458 """
459 self.__disWidget.setFocus(Qt.OtherFocusReason)
460
461 @pyqtSlot(QTreeWidgetItem, int) 572 @pyqtSlot(QTreeWidgetItem, int)
462 def __disItemClicked(self, itm, column): 573 def __disItemClicked(self, itm, column):
463 """ 574 """
464 Private slot handling a user click on a Disassembly node item. 575 Private slot handling a user click on a Disassembly node item.
465 576
466 @param itm reference to the clicked item 577 @param itm reference to the clicked item
467 @type QTreeWidgetItem 578 @type QTreeWidgetItem
468 @param column column number of the click 579 @param column column number of the click
469 @type int 580 @type int
470 """ 581 """
582 # TODO: add code to deal with Traceback mode
471 self.__editor.clearAllHighlights() 583 self.__editor.clearAllHighlights()
472 584
473 if itm is not None: 585 if itm is not None:
474 startLine = itm.data(0, self.StartLineRole) 586 startLine = itm.data(0, self.StartLineRole)
475 endLine = itm.data(0, self.EndLineRole) 587 endLine = itm.data(0, self.EndLineRole)
551 self.__jumpTargetColor = QBrush( 663 self.__jumpTargetColor = QBrush(
552 Preferences.getPython("DisViewerLabeledColor")) 664 Preferences.getPython("DisViewerLabeledColor"))
553 665
554 if self.isVisible(): 666 if self.isVisible():
555 self.__loadDIS() 667 self.__loadDIS()
668
669 self.__styleLabels()
670
671 def __styleLabels(self):
672 """
673 Private method to style the info labels iaw. selected colors.
674 """
675 # current instruction
676 self.__currentInfoLabel.setStyleSheet(
677 "QLabel {{ color : {0}; }}".format(
678 self.__currentInstructionColor.color().name()
679 )
680 )
681 font = self.__currentInfoLabel.font()
682 font.setItalic(True)
683 self.__currentInfoLabel.setFont(font)
684
685 # labeled instruction
686 self.__labeledInfoLabel.setStyleSheet(
687 "QLabel {{ color : {0}; }}".format(
688 self.__jumpTargetColor.color().name()
689 )
690 )
691 font = self.__labeledInfoLabel.font()
692 font.setBold(True)
693 self.__labeledInfoLabel.setFont(font)
694
695 @pyqtSlot()
696 def clear(self):
697 """
698 Public method to clear the display.
699 """
700 self.__disWidget.clear()

eric ide

mercurial