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() |