eric6/UI/PythonDisViewer.py

changeset 7705
90a9aefd4253
parent 7704
9251c4dc4f7a
child 7707
6abcf4275d0e
equal deleted inserted replaced
7704:9251c4dc4f7a 7705:90a9aefd4253
13 13
14 from PyQt5.QtCore import pyqtSlot, Qt, QTimer 14 from PyQt5.QtCore import pyqtSlot, Qt, QTimer
15 from PyQt5.QtGui import QCursor, QBrush 15 from PyQt5.QtGui import QCursor, QBrush
16 from PyQt5.QtWidgets import ( 16 from PyQt5.QtWidgets import (
17 QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, 17 QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget,
18 QVBoxLayout, QLabel 18 QVBoxLayout, QLabel, QMenu
19 ) 19 )
20
21 import Preferences
20 22
21 23
22 class PythonDisViewer(QWidget): 24 class PythonDisViewer(QWidget):
23 """ 25 """
24 Class implementing a widget to visualize the Python Disassembly for some 26 Class implementing a widget to visualize the Python Disassembly for some
42 self.setLayout(self.__layout) 44 self.setLayout(self.__layout)
43 self.__disWidget = QTreeWidget(self) 45 self.__disWidget = QTreeWidget(self)
44 self.__layout.addWidget(self.__disWidget) 46 self.__layout.addWidget(self.__disWidget)
45 self.__layout.setContentsMargins(0, 0, 0, 0) 47 self.__layout.setContentsMargins(0, 0, 0, 0)
46 48
47 self.__infoLabel = QLabel(self.tr( 49 self.__currentInfoLabel = QLabel(self.tr(
48 "italic: current instruction\n" 50 "italic: current instruction"))
49 "bold: labelled instruction" 51 self.__labeledInfoLabel = QLabel(self.tr(
50 )) 52 "bold: labeled instruction"))
51 self.__layout.addWidget(self.__infoLabel) 53 self.__layout.addWidget(self.__currentInfoLabel)
54 self.__layout.addWidget(self.__labeledInfoLabel)
52 55
53 self.__vm = viewmanager 56 self.__vm = viewmanager
54 self.__vmConnected = False 57 self.__vmConnected = False
55 58
56 self.__editor = None 59 self.__editor = None
62 self.__disWidget.setSortingEnabled(False) 65 self.__disWidget.setSortingEnabled(False)
63 self.__disWidget.setSelectionBehavior(QAbstractItemView.SelectRows) 66 self.__disWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
64 self.__disWidget.setSelectionMode(QAbstractItemView.SingleSelection) 67 self.__disWidget.setSelectionMode(QAbstractItemView.SingleSelection)
65 self.__disWidget.setAlternatingRowColors(True) 68 self.__disWidget.setAlternatingRowColors(True)
66 69
70 self.__menu = QMenu(self.__disWidget)
71 self.__menu.addAction(self.tr('Expand All'), self.__expandAll)
72 self.__menu.addAction(self.tr('Collapse All'), self.__collapseAll)
73
74 self.__disWidget.setContextMenuPolicy(Qt.CustomContextMenu)
75 self.__disWidget.customContextMenuRequested.connect(
76 self.__contextMenuRequested)
77
78 self.__errorColor = QBrush(
79 Preferences.getPython("DisViewerErrorColor"))
80 self.__currentInstructionColor = QBrush(
81 Preferences.getPython("DisViewerCurrentColor"))
82 self.__jumpTargetColor = QBrush(
83 Preferences.getPython("DisViewerLabeledColor"))
84
67 self.__disWidget.itemClicked.connect(self.__disItemClicked) 85 self.__disWidget.itemClicked.connect(self.__disItemClicked)
86 self.__disWidget.itemCollapsed.connect(self.__resizeColumns)
87 self.__disWidget.itemExpanded.connect(self.__resizeColumns)
68 88
69 self.__vm.disViewerStateChanged.connect(self.__disViewerStateChanged) 89 self.__vm.disViewerStateChanged.connect(self.__disViewerStateChanged)
70 90
71 self.hide() 91 self.hide()
92
93 def __contextMenuRequested(self, coord):
94 """
95 Private slot to show the context menu.
96
97 @param coord position of the mouse pointer
98 @type QPoint
99 """
100 coord = self.__disWidget.mapToGlobal(coord)
101 self.__menu.popup(coord)
72 102
73 def __editorChanged(self, editor): 103 def __editorChanged(self, editor):
74 """ 104 """
75 Private slot to handle a change of the current editor. 105 Private slot to handle a change of the current editor.
76 106
93 @type Editor 123 @type Editor
94 """ 124 """
95 if editor and editor is self.__editor: 125 if editor and editor is self.__editor:
96 self.__loadDIS() 126 self.__loadDIS()
97 127
98 def __editorDoubleClicked(self, editor, pos, buttons): 128 def __editorLineChanged(self, editor, lineno):
99 """ 129 """
100 Private slot to handle a mouse button double click in the editor. 130 Private slot to handle a mouse button double click in the editor.
101 131
102 @param editor reference to the editor, that emitted the signal 132 @param editor reference to the editor, that emitted the signal
103 @type Editor 133 @type Editor
104 @param pos position of the double click 134 @param lineno line number of the editor's cursor (zero based)
105 @type QPoint 135 @type int
106 @param buttons mouse buttons that were double clicked 136 """
107 @type Qt.MouseButtons 137 if editor is self.__editor:
108 """
109 if editor is self.__editor and buttons == Qt.LeftButton:
110 if editor.isModified(): 138 if editor.isModified():
111 # reload the source 139 # reload the source
112 QTimer.singleShot(0, self.__loadDIS) 140 QTimer.singleShot(0, self.__loadDIS)
113 141
114 # highlight the corresponding entry 142 # highlight the corresponding entry
115 ## QTimer.singleShot(0, self.__selectItemForEditorSelection) 143 QTimer.singleShot(0, self.__selectItemForEditorLine)
116 QTimer.singleShot(0, self.__grabFocus)
117 144
118 def __lastEditorClosed(self): 145 def __lastEditorClosed(self):
119 """ 146 """
120 Private slot to handle the last editor closed signal of the view 147 Private slot to handle the last editor closed signal of the view
121 manager. 148 manager.
129 super(PythonDisViewer, self).show() 156 super(PythonDisViewer, self).show()
130 157
131 if not self.__vmConnected: 158 if not self.__vmConnected:
132 self.__vm.editorChangedEd.connect(self.__editorChanged) 159 self.__vm.editorChangedEd.connect(self.__editorChanged)
133 self.__vm.editorSavedEd.connect(self.__editorSaved) 160 self.__vm.editorSavedEd.connect(self.__editorSaved)
134 self.__vm.editorDoubleClickedEd.connect(self.__editorDoubleClicked) 161 self.__vm.editorLineChangedEd.connect(self.__editorLineChanged)
135 self.__vmConnected = True 162 self.__vmConnected = True
136 163
137 def hide(self): 164 def hide(self):
138 """ 165 """
139 Public slot to hide the DIS viewer. 166 Public slot to hide the DIS viewer.
144 self.__editor.clearAllHighlights() 171 self.__editor.clearAllHighlights()
145 172
146 if self.__vmConnected: 173 if self.__vmConnected:
147 self.__vm.editorChangedEd.disconnect(self.__editorChanged) 174 self.__vm.editorChangedEd.disconnect(self.__editorChanged)
148 self.__vm.editorSavedEd.disconnect(self.__editorSaved) 175 self.__vm.editorSavedEd.disconnect(self.__editorSaved)
149 self.__vm.editorDoubleClickedEd.disconnect( 176 self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged)
150 self.__editorDoubleClicked)
151 self.__vmConnected = False 177 self.__vmConnected = False
152 178
153 def shutdown(self): 179 def shutdown(self):
154 """ 180 """
155 Public method to perform shutdown actions. 181 Public method to perform shutdown actions.
171 self.__loadDIS() 197 self.__loadDIS()
172 else: 198 else:
173 self.hide() 199 self.hide()
174 self.__editor = None 200 self.__editor = None
175 201
202 def __expandAll(self):
203 """
204 Private slot to expand all items.
205 """
206 block = self.__disWidget.blockSignals(True)
207 self.__disWidget.expandAll()
208 self.__disWidget.blockSignals(block)
209 self.__resizeColumns()
210
211 def __collapseAll(self):
212 """
213 Private slot to collapse all items.
214 """
215 block = self.__disWidget.blockSignals(True)
216 self.__disWidget.collapseAll()
217 self.__disWidget.blockSignals(block)
218 self.__resizeColumns()
219
176 def __createErrorItem(self, error): 220 def __createErrorItem(self, error):
177 """ 221 """
178 Private method to create a top level error item. 222 Private method to create a top level error item.
179 223
180 @param error error message 224 @param error error message
182 @return generated item 226 @return generated item
183 @rtype QTreeWidgetItem 227 @rtype QTreeWidgetItem
184 """ 228 """
185 itm = QTreeWidgetItem(self.__disWidget, [error]) 229 itm = QTreeWidgetItem(self.__disWidget, [error])
186 itm.setFirstColumnSpanned(True) 230 itm.setFirstColumnSpanned(True)
187 itm.setForeground(0, QBrush(Qt.red)) 231 itm.setForeground(0, self.__errorColor)
188 return itm 232 return itm
189 233
190 def __createTitleItem(self, title, line): 234 def __createTitleItem(self, title, line, parentItem):
191 """ 235 """
192 Private method to create a title item. 236 Private method to create a title item.
193 237
194 @param title titel string for the item 238 @param title titel string for the item
195 @type str 239 @type str
196 @param line start line of the titled disassembly 240 @param line start line of the titled disassembly
197 @type int 241 @type int
242 @param parentItem reference to the parent item
243 @type QTreeWidget or QTreeWidgetItem
198 @return generated item 244 @return generated item
199 @rtype QTreeWidgetItem 245 @rtype QTreeWidgetItem
200 """ 246 """
201 itm = QTreeWidgetItem(self.__disWidget, [title]) 247 itm = QTreeWidgetItem(parentItem, [title])
202 itm.setFirstColumnSpanned(True) 248 itm.setFirstColumnSpanned(True)
203 itm.setExpanded(True) 249 itm.setExpanded(True)
204 250
205 itm.setData(0, self.StartLineRole, line) 251 itm.setData(0, self.StartLineRole, line)
206 itm.setData(0, self.EndLineRole, line) 252 itm.setData(0, self.EndLineRole, line)
239 fields.append('(' + instr.argrepr + ')') 285 fields.append('(' + instr.argrepr + ')')
240 286
241 itm = QTreeWidgetItem(parent, fields) 287 itm = QTreeWidgetItem(parent, fields)
242 for col in (0, 1, 3): 288 for col in (0, 1, 3):
243 itm.setTextAlignment(col, Qt.AlignRight) 289 itm.setTextAlignment(col, Qt.AlignRight)
290 # set font to indicate current instruction and jump target
244 font = itm.font(0) 291 font = itm.font(0)
245 if instr.offset == lasti: 292 if instr.offset == lasti:
246 font.setItalic(True) 293 font.setItalic(True)
247 if instr.is_jump_target: 294 if instr.is_jump_target:
248 font.setBold(True) 295 font.setBold(True)
249 for col in range(itm.columnCount()): 296 for col in range(itm.columnCount()):
250 itm.setFont(col, font) 297 itm.setFont(col, font)
298 # set color to indicate current instruction or jump target
299 if instr.offset == lasti:
300 foreground = self.__currentInstructionColor
301 elif instr.is_jump_target:
302 foreground = self.__jumpTargetColor
303 else:
304 foreground = None
305 if foreground:
306 for col in range(itm.columnCount()):
307 itm.setForeground(col, foreground)
251 308
252 itm.setExpanded(True) 309 itm.setExpanded(True)
253 310
254 if instr.starts_line: 311 if instr.starts_line:
255 itm.setData(0, self.StartLineRole, instr.starts_line) 312 itm.setData(0, self.StartLineRole, instr.starts_line)
298 if not source.strip(): 355 if not source.strip():
299 # empty editor or white space only 356 # empty editor or white space only
300 return 357 return
301 358
302 filename = self.__editor.getFileName() 359 filename = self.__editor.getFileName()
303 if not filename: 360 if filename:
361 filename = os.path.basename(filename)
362 else:
304 filename = "<dis>" 363 filename = "<dis>"
305 364
306 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) 365 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
307 try: 366 try:
308 codeObject = self.__tryCompile(source, filename) 367 codeObject = self.__tryCompile(source, filename)
310 codeObject = None 369 codeObject = None
311 self.__createErrorItem(str(exc)) 370 self.__createErrorItem(str(exc))
312 371
313 if codeObject: 372 if codeObject:
314 self.setUpdatesEnabled(False) 373 self.setUpdatesEnabled(False)
374 block = self.__disWidget.blockSignals(True)
315 375
316 self.__disassembleObject(codeObject, self.__disWidget, filename) 376 self.__disassembleObject(codeObject, self.__disWidget, filename)
317 QTimer.singleShot(0, self.__resizeColumns) 377 QTimer.singleShot(0, self.__resizeColumns)
318 378
379 self.__disWidget.blockSignals(block)
319 self.setUpdatesEnabled(True) 380 self.setUpdatesEnabled(True)
320 381
321 QApplication.restoreOverrideCursor() 382 QApplication.restoreOverrideCursor()
322 383
323 self.__grabFocus() 384 self.__grabFocus()
336 @param evt resize event 397 @param evt resize event
337 @type QResizeEvent 398 @type QResizeEvent
338 """ 399 """
339 # just adjust the sizes of the columns 400 # just adjust the sizes of the columns
340 self.__resizeColumns() 401 self.__resizeColumns()
402
403 def __clearSelection(self):
404 """
405 Private method to clear all selected items.
406 """
407 for itm in self.__disWidget.selectedItems():
408 itm.setSelected(False)
409
410 def __selectChildren(self, itm, lineno):
411 """
412 Private method to select children of the given item covering the given
413 line number.
414
415 @param itm reference to the item
416 @type QTreeWidgetItem
417 @param lineno line number to base the selection on
418 @type int
419 """
420 for index in range(itm.childCount()):
421 child = itm.child(index)
422 if (
423 child.data(0, self.StartLineRole) <= lineno <=
424 child.data(0, self.EndLineRole)
425 ):
426 child.setSelected(True)
427 self.__selectChildren(child, lineno)
428
429 if child.data(0, self.StartLineRole) == lineno:
430 self.__disWidget.scrollToItem(
431 child, QAbstractItemView.PositionAtCenter)
432
433 def __selectItemForEditorLine(self):
434 """
435 Private slot to select the items corresponding with the cursor line
436 of the current editor.
437 """
438 # step 1: clear all selected items
439 self.__clearSelection()
440
441 # step 2: retrieve the editor cursor line
442 cline, cindex = self.__editor.getCursorPosition()
443 # make the line numbers 1-based
444 cline += 1
445
446 for index in range(self.__disWidget.topLevelItemCount()):
447 itm = self.__disWidget.topLevelItem(index)
448 if (
449 itm.data(0, self.StartLineRole) <= cline <=
450 itm.data(0, self.EndLineRole)
451 ):
452 itm.setSelected(True)
453 self.__selectChildren(itm, cline)
341 454
342 def __grabFocus(self): 455 def __grabFocus(self):
343 """ 456 """
344 Private method to grab the input focus. 457 Private method to grab the input focus.
345 """ 458 """
381 c = compile(source, name, 'eval') 494 c = compile(source, name, 'eval')
382 except SyntaxError: 495 except SyntaxError:
383 c = compile(source, name, 'exec') 496 c = compile(source, name, 'exec')
384 return c 497 return c
385 498
386 def __disassembleObject(self, co, parentItem, name="", lasti=-1): 499 def __disassembleObject(self, co, parentItem, parentName="", lasti=-1):
387 """ 500 """
388 Private method to disassemble the given code object recursively. 501 Private method to disassemble the given code object recursively.
389 502
390 @param co code object to be disassembled 503 @param co code object to be disassembled
391 @type code object 504 @type code object
392 @param parentItem reference to the parent item 505 @param parentItem reference to the parent item
393 @type QTreeWidget or QTreeWidgetItem 506 @type QTreeWidget or QTreeWidgetItem
394 @param name name of the code object 507 @param parentName name of the parent code object
395 @type str 508 @type str
396 @param lasti index of the instruction of a traceback 509 @param lasti index of the instruction of a traceback
397 @type int 510 @type int
398 """ 511 """
399 if co.co_name == "<module>": 512 if co.co_name == "<module>":
400 title = ( 513 title = os.path.basename(co.co_filename)
401 self.tr("Disassembly of module '{0}'") 514 name = ""
402 .format(os.path.basename(co.co_filename)) 515 else:
403 ) 516 if parentName:
404 else: 517 name = "{0}.{1}".format(parentName, co.co_name)
405 title = ( 518 else:
406 self.tr("Disassembly of code object '{0}'") 519 name = co.co_name
407 .format(co.co_name) 520 title = self.tr("Code Object '{0}'").format(name)
408 ) 521 titleItem = self.__createTitleItem(title, co.co_firstlineno,
409 titleItem = self.__createTitleItem(title, co.co_firstlineno) 522 parentItem)
410 lastStartItem = None 523 lastStartItem = None
411 for instr in dis.get_instructions(co): 524 for instr in dis.get_instructions(co):
412 if instr.starts_line: 525 if instr.starts_line:
413 if lastStartItem: 526 if lastStartItem:
414 self.__updateItemEndLine(lastStartItem) 527 self.__updateItemEndLine(lastStartItem)
417 else: 530 else:
418 self.__createInstructionItem(instr, lastStartItem, lasti=lasti) 531 self.__createInstructionItem(instr, lastStartItem, lasti=lasti)
419 if lastStartItem: 532 if lastStartItem:
420 self.__updateItemEndLine(lastStartItem) 533 self.__updateItemEndLine(lastStartItem)
421 534
422 self.__updateItemEndLine(titleItem)
423
424 for x in co.co_consts: 535 for x in co.co_consts:
425 if hasattr(x, 'co_code'): 536 if hasattr(x, 'co_code'):
426 self.__disassembleObject(x, self.__disWidget, lasti=lasti) 537 self.__disassembleObject(x, titleItem, parentName=name,
538 lasti=lasti)
539
540 self.__updateItemEndLine(titleItem)
541
542 @pyqtSlot()
543 def preferencesChanged(self):
544 """
545 Public slot handling changes of the Disassembly viewer settings.
546 """
547 self.__errorColor = QBrush(
548 Preferences.getPython("DisViewerErrorColor"))
549 self.__currentInstructionColor = QBrush(
550 Preferences.getPython("DisViewerCurrentColor"))
551 self.__jumpTargetColor = QBrush(
552 Preferences.getPython("DisViewerLabeledColor"))
553
554 if self.isVisible():
555 self.__loadDIS()

eric ide

mercurial