|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a widget containing various debug related views. |
|
8 |
|
9 The views avaliable are: |
|
10 <ul> |
|
11 <li>variables viewer for global variables</li> |
|
12 <li>variables viewer for local variables</li> |
|
13 <li>call trace viewer</li> |
|
14 <li>viewer for breakpoints</li> |
|
15 <li>viewer for watch expressions</li> |
|
16 <li>viewer for exceptions</li> |
|
17 <li>viewer for threads</li> |
|
18 <li>a file browser (optional)</li> |
|
19 <li>an interpreter shell (optional)</li> |
|
20 </ul> |
|
21 """ |
|
22 |
|
23 from __future__ import unicode_literals |
|
24 |
|
25 import os |
|
26 |
|
27 from PyQt5.QtCore import pyqtSignal |
|
28 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, \ |
|
29 QSizePolicy, QPushButton, QComboBox, QLabel, QTreeWidget, \ |
|
30 QTreeWidgetItem, QHeaderView |
|
31 |
|
32 import UI.PixmapCache |
|
33 import Preferences |
|
34 |
|
35 from E5Gui.E5TabWidget import E5TabWidget |
|
36 |
|
37 |
|
38 class DebugViewer(QWidget): |
|
39 """ |
|
40 Class implementing a widget containing various debug related views. |
|
41 |
|
42 The individual tabs contain the interpreter shell (optional), |
|
43 the filesystem browser (optional), the two variables viewers |
|
44 (global and local), a breakpoint viewer, a watch expression viewer and |
|
45 the exception logger. Additionally a list of all threads is shown. |
|
46 |
|
47 @signal sourceFile(string, int) emitted to open a source file at a line |
|
48 """ |
|
49 sourceFile = pyqtSignal(str, int) |
|
50 |
|
51 def __init__(self, debugServer, docked, vm, parent=None): |
|
52 """ |
|
53 Constructor |
|
54 |
|
55 @param debugServer reference to the debug server object (DebugServer) |
|
56 @param docked flag indicating a dock window |
|
57 @param vm reference to the viewmanager object |
|
58 @param parent parent widget (QWidget) |
|
59 """ |
|
60 super(DebugViewer, self).__init__(parent) |
|
61 |
|
62 self.debugServer = debugServer |
|
63 self.debugUI = None |
|
64 |
|
65 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) |
|
66 |
|
67 self.__mainLayout = QVBoxLayout() |
|
68 self.__mainLayout.setContentsMargins(0, 0, 0, 0) |
|
69 self.setLayout(self.__mainLayout) |
|
70 |
|
71 self.__tabWidget = E5TabWidget() |
|
72 self.__mainLayout.addWidget(self.__tabWidget) |
|
73 |
|
74 from .VariablesViewer import VariablesViewer |
|
75 # add the global variables viewer |
|
76 self.glvWidget = QWidget() |
|
77 self.glvWidgetVLayout = QVBoxLayout(self.glvWidget) |
|
78 self.glvWidgetVLayout.setContentsMargins(0, 0, 0, 0) |
|
79 self.glvWidgetVLayout.setSpacing(3) |
|
80 self.glvWidget.setLayout(self.glvWidgetVLayout) |
|
81 |
|
82 self.globalsViewer = VariablesViewer(self, True, self.glvWidget) |
|
83 self.glvWidgetVLayout.addWidget(self.globalsViewer) |
|
84 |
|
85 self.glvWidgetHLayout = QHBoxLayout() |
|
86 self.glvWidgetHLayout.setContentsMargins(3, 3, 3, 3) |
|
87 |
|
88 self.globalsFilterEdit = QLineEdit(self.glvWidget) |
|
89 self.globalsFilterEdit.setSizePolicy( |
|
90 QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
91 self.glvWidgetHLayout.addWidget(self.globalsFilterEdit) |
|
92 self.globalsFilterEdit.setToolTip( |
|
93 self.tr("Enter regular expression patterns separated by ';'" |
|
94 " to define variable filters. ")) |
|
95 self.globalsFilterEdit.setWhatsThis( |
|
96 self.tr("Enter regular expression patterns separated by ';'" |
|
97 " to define variable filters. All variables and" |
|
98 " class attributes matched by one of the expressions" |
|
99 " are not shown in the list above.")) |
|
100 |
|
101 self.setGlobalsFilterButton = QPushButton( |
|
102 self.tr('Set'), self.glvWidget) |
|
103 self.glvWidgetHLayout.addWidget(self.setGlobalsFilterButton) |
|
104 self.glvWidgetVLayout.addLayout(self.glvWidgetHLayout) |
|
105 |
|
106 index = self.__tabWidget.addTab( |
|
107 self.glvWidget, |
|
108 UI.PixmapCache.getIcon("globalVariables.png"), '') |
|
109 self.__tabWidget.setTabToolTip(index, self.globalsViewer.windowTitle()) |
|
110 |
|
111 self.setGlobalsFilterButton.clicked.connect( |
|
112 self.setGlobalsFilter) |
|
113 self.globalsFilterEdit.returnPressed.connect(self.setGlobalsFilter) |
|
114 |
|
115 # add the local variables viewer |
|
116 self.lvWidget = QWidget() |
|
117 self.lvWidgetVLayout = QVBoxLayout(self.lvWidget) |
|
118 self.lvWidgetVLayout.setContentsMargins(0, 0, 0, 0) |
|
119 self.lvWidgetVLayout.setSpacing(3) |
|
120 self.lvWidget.setLayout(self.lvWidgetVLayout) |
|
121 |
|
122 self.lvWidgetHLayout1 = QHBoxLayout() |
|
123 self.lvWidgetHLayout1.setContentsMargins(3, 3, 3, 3) |
|
124 |
|
125 self.stackComboBox = QComboBox(self.lvWidget) |
|
126 self.stackComboBox.setSizePolicy( |
|
127 QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
128 self.lvWidgetHLayout1.addWidget(self.stackComboBox) |
|
129 |
|
130 self.sourceButton = QPushButton(self.tr('Source'), self.lvWidget) |
|
131 self.lvWidgetHLayout1.addWidget(self.sourceButton) |
|
132 self.sourceButton.setEnabled(False) |
|
133 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout1) |
|
134 |
|
135 self.localsViewer = VariablesViewer(self, False, self.lvWidget) |
|
136 self.lvWidgetVLayout.addWidget(self.localsViewer) |
|
137 |
|
138 self.lvWidgetHLayout2 = QHBoxLayout() |
|
139 self.lvWidgetHLayout2.setContentsMargins(3, 3, 3, 3) |
|
140 |
|
141 self.localsFilterEdit = QLineEdit(self.lvWidget) |
|
142 self.localsFilterEdit.setSizePolicy( |
|
143 QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
144 self.lvWidgetHLayout2.addWidget(self.localsFilterEdit) |
|
145 self.localsFilterEdit.setToolTip( |
|
146 self.tr( |
|
147 "Enter regular expression patterns separated by ';' to define " |
|
148 "variable filters. ")) |
|
149 self.localsFilterEdit.setWhatsThis( |
|
150 self.tr( |
|
151 "Enter regular expression patterns separated by ';' to define " |
|
152 "variable filters. All variables and class attributes matched" |
|
153 " by one of the expressions are not shown in the list above.")) |
|
154 |
|
155 self.setLocalsFilterButton = QPushButton( |
|
156 self.tr('Set'), self.lvWidget) |
|
157 self.lvWidgetHLayout2.addWidget(self.setLocalsFilterButton) |
|
158 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout2) |
|
159 |
|
160 index = self.__tabWidget.addTab( |
|
161 self.lvWidget, |
|
162 UI.PixmapCache.getIcon("localVariables.png"), '') |
|
163 self.__tabWidget.setTabToolTip(index, self.localsViewer.windowTitle()) |
|
164 |
|
165 self.sourceButton.clicked.connect(self.__showSource) |
|
166 self.stackComboBox.currentIndexChanged[int].connect( |
|
167 self.__frameSelected) |
|
168 self.setLocalsFilterButton.clicked.connect(self.setLocalsFilter) |
|
169 self.localsFilterEdit.returnPressed.connect(self.setLocalsFilter) |
|
170 |
|
171 from .CallStackViewer import CallStackViewer |
|
172 # add the call stack viewer |
|
173 self.callStackViewer = CallStackViewer(self.debugServer) |
|
174 index = self.__tabWidget.addTab( |
|
175 self.callStackViewer, |
|
176 UI.PixmapCache.getIcon("step.png"), "") |
|
177 self.__tabWidget.setTabToolTip( |
|
178 index, self.callStackViewer.windowTitle()) |
|
179 self.callStackViewer.sourceFile.connect(self.sourceFile) |
|
180 self.callStackViewer.frameSelected.connect( |
|
181 self.__callStackFrameSelected) |
|
182 |
|
183 from .CallTraceViewer import CallTraceViewer |
|
184 # add the call trace viewer |
|
185 self.callTraceViewer = CallTraceViewer(self.debugServer) |
|
186 index = self.__tabWidget.addTab( |
|
187 self.callTraceViewer, |
|
188 UI.PixmapCache.getIcon("callTrace.png"), "") |
|
189 self.__tabWidget.setTabToolTip( |
|
190 index, self.callTraceViewer.windowTitle()) |
|
191 self.callTraceViewer.sourceFile.connect(self.sourceFile) |
|
192 |
|
193 from .BreakPointViewer import BreakPointViewer |
|
194 # add the breakpoint viewer |
|
195 self.breakpointViewer = BreakPointViewer() |
|
196 self.breakpointViewer.setModel(self.debugServer.getBreakPointModel()) |
|
197 index = self.__tabWidget.addTab( |
|
198 self.breakpointViewer, |
|
199 UI.PixmapCache.getIcon("breakpoints.png"), '') |
|
200 self.__tabWidget.setTabToolTip( |
|
201 index, self.breakpointViewer.windowTitle()) |
|
202 self.breakpointViewer.sourceFile.connect(self.sourceFile) |
|
203 |
|
204 from .WatchPointViewer import WatchPointViewer |
|
205 # add the watch expression viewer |
|
206 self.watchpointViewer = WatchPointViewer() |
|
207 self.watchpointViewer.setModel(self.debugServer.getWatchPointModel()) |
|
208 index = self.__tabWidget.addTab( |
|
209 self.watchpointViewer, |
|
210 UI.PixmapCache.getIcon("watchpoints.png"), '') |
|
211 self.__tabWidget.setTabToolTip( |
|
212 index, self.watchpointViewer.windowTitle()) |
|
213 |
|
214 from .ExceptionLogger import ExceptionLogger |
|
215 # add the exception logger |
|
216 self.exceptionLogger = ExceptionLogger() |
|
217 index = self.__tabWidget.addTab( |
|
218 self.exceptionLogger, |
|
219 UI.PixmapCache.getIcon("exceptions.png"), '') |
|
220 self.__tabWidget.setTabToolTip( |
|
221 index, self.exceptionLogger.windowTitle()) |
|
222 |
|
223 self.__tabWidget.setCurrentWidget(self.glvWidget) |
|
224 |
|
225 # add the threads viewer |
|
226 self.__mainLayout.addWidget(QLabel(self.tr("Threads:"))) |
|
227 self.__threadList = QTreeWidget() |
|
228 self.__threadList.setHeaderLabels( |
|
229 [self.tr("ID"), self.tr("Name"), |
|
230 self.tr("State"), ""]) |
|
231 self.__threadList.setSortingEnabled(True) |
|
232 self.__mainLayout.addWidget(self.__threadList) |
|
233 |
|
234 self.__doThreadListUpdate = True |
|
235 |
|
236 self.__threadList.currentItemChanged.connect(self.__threadSelected) |
|
237 |
|
238 self.__mainLayout.setStretchFactor(self.__tabWidget, 5) |
|
239 self.__mainLayout.setStretchFactor(self.__threadList, 1) |
|
240 |
|
241 self.currentStack = None |
|
242 self.framenr = 0 |
|
243 |
|
244 self.debugServer.clientStack.connect(self.handleClientStack) |
|
245 |
|
246 self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode") |
|
247 self.sourceButton.setVisible(not self.__autoViewSource) |
|
248 |
|
249 def preferencesChanged(self): |
|
250 """ |
|
251 Public slot to handle the preferencesChanged signal. |
|
252 """ |
|
253 self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode") |
|
254 self.sourceButton.setVisible(not self.__autoViewSource) |
|
255 |
|
256 def setDebugger(self, debugUI): |
|
257 """ |
|
258 Public method to set a reference to the Debug UI. |
|
259 |
|
260 @param debugUI reference to the DebugUI object (DebugUI) |
|
261 """ |
|
262 self.debugUI = debugUI |
|
263 self.debugUI.clientStack.connect(self.handleClientStack) |
|
264 self.callStackViewer.setDebugger(debugUI) |
|
265 |
|
266 def handleResetUI(self): |
|
267 """ |
|
268 Public method to reset the SBVviewer. |
|
269 """ |
|
270 self.globalsViewer.handleResetUI() |
|
271 self.localsViewer.handleResetUI() |
|
272 self.setGlobalsFilter() |
|
273 self.setLocalsFilter() |
|
274 self.sourceButton.setEnabled(False) |
|
275 self.currentStack = None |
|
276 self.stackComboBox.clear() |
|
277 self.__threadList.clear() |
|
278 self.__tabWidget.setCurrentWidget(self.glvWidget) |
|
279 self.breakpointViewer.handleResetUI() |
|
280 |
|
281 def initCallStackViewer(self, projectMode): |
|
282 """ |
|
283 Public method to initialize the call stack viewer. |
|
284 |
|
285 @param projectMode flag indicating to enable the project mode (boolean) |
|
286 """ |
|
287 self.callStackViewer.clear() |
|
288 self.callStackViewer.setProjectMode(projectMode) |
|
289 |
|
290 def isCallTraceEnabled(self): |
|
291 """ |
|
292 Public method to get the state of the call trace function. |
|
293 |
|
294 @return flag indicating the state of the call trace function (boolean) |
|
295 """ |
|
296 return self.callTraceViewer.isCallTraceEnabled() |
|
297 |
|
298 def clearCallTrace(self): |
|
299 """ |
|
300 Public method to clear the recorded call trace. |
|
301 """ |
|
302 self.callTraceViewer.clear() |
|
303 |
|
304 def setCallTraceToProjectMode(self, enabled): |
|
305 """ |
|
306 Public slot to set the call trace viewer to project mode. |
|
307 |
|
308 In project mode the call trace info is shown with project relative |
|
309 path names. |
|
310 |
|
311 @param enabled flag indicating to enable the project mode (boolean) |
|
312 """ |
|
313 self.callTraceViewer.setProjectMode(enabled) |
|
314 |
|
315 def showVariables(self, vlist, showGlobals): |
|
316 """ |
|
317 Public method to show the variables in the respective window. |
|
318 |
|
319 @param vlist list of variables to display |
|
320 @param showGlobals flag indicating global/local state |
|
321 """ |
|
322 if showGlobals: |
|
323 self.globalsViewer.showVariables(vlist, self.framenr) |
|
324 else: |
|
325 self.localsViewer.showVariables(vlist, self.framenr) |
|
326 |
|
327 def showVariable(self, vlist, showGlobals): |
|
328 """ |
|
329 Public method to show the variables in the respective window. |
|
330 |
|
331 @param vlist list of variables to display |
|
332 @param showGlobals flag indicating global/local state |
|
333 """ |
|
334 if showGlobals: |
|
335 self.globalsViewer.showVariable(vlist) |
|
336 else: |
|
337 self.localsViewer.showVariable(vlist) |
|
338 |
|
339 def showVariablesTab(self, showGlobals): |
|
340 """ |
|
341 Public method to make a variables tab visible. |
|
342 |
|
343 @param showGlobals flag indicating global/local state |
|
344 """ |
|
345 if showGlobals: |
|
346 self.__tabWidget.setCurrentWidget(self.glvWidget) |
|
347 else: |
|
348 self.__tabWidget.setCurrentWidget(self.lvWidget) |
|
349 |
|
350 def handleClientStack(self, stack): |
|
351 """ |
|
352 Public slot to show the call stack of the program being debugged. |
|
353 |
|
354 @param stack list of tuples with call stack data (file name, |
|
355 line number, function name, formatted argument/values list) |
|
356 """ |
|
357 block = self.stackComboBox.blockSignals(True) |
|
358 self.framenr = 0 |
|
359 self.stackComboBox.clear() |
|
360 self.currentStack = stack |
|
361 self.sourceButton.setEnabled(len(stack) > 0) |
|
362 for s in stack: |
|
363 # just show base filename to make it readable |
|
364 s = (os.path.basename(s[0]), s[1], s[2]) |
|
365 self.stackComboBox.addItem('{0}:{1}:{2}'.format(*s)) |
|
366 self.stackComboBox.blockSignals(block) |
|
367 |
|
368 def setVariablesFilter(self, globalsFilter, localsFilter): |
|
369 """ |
|
370 Public slot to set the local variables filter. |
|
371 |
|
372 @param globalsFilter filter list for global variable types |
|
373 (list of int) |
|
374 @param localsFilter filter list for local variable types (list of int) |
|
375 """ |
|
376 self.globalsFilter = globalsFilter |
|
377 self.localsFilter = localsFilter |
|
378 |
|
379 def __showSource(self): |
|
380 """ |
|
381 Private slot to handle the source button press to show the selected |
|
382 file. |
|
383 """ |
|
384 index = self.stackComboBox.currentIndex() |
|
385 if index > -1 and self.currentStack: |
|
386 s = self.currentStack[index] |
|
387 self.sourceFile.emit(s[0], int(s[1])) |
|
388 |
|
389 def __frameSelected(self, frmnr): |
|
390 """ |
|
391 Private slot to handle the selection of a new stack frame number. |
|
392 |
|
393 @param frmnr frame number (0 is the current frame) (int) |
|
394 """ |
|
395 self.framenr = frmnr |
|
396 if self.debugServer.isDebugging(): |
|
397 self.debugServer.remoteClientVariables(0, self.localsFilter, frmnr) |
|
398 |
|
399 if self.__autoViewSource: |
|
400 self.__showSource() |
|
401 |
|
402 def setGlobalsFilter(self): |
|
403 """ |
|
404 Public slot to set the global variable filter. |
|
405 """ |
|
406 if self.debugServer.isDebugging(): |
|
407 filterStr = self.globalsFilterEdit.text() |
|
408 self.debugServer.remoteClientSetFilter(1, filterStr) |
|
409 self.debugServer.remoteClientVariables(2, self.globalsFilter) |
|
410 |
|
411 def setLocalsFilter(self): |
|
412 """ |
|
413 Public slot to set the local variable filter. |
|
414 """ |
|
415 if self.debugServer.isDebugging(): |
|
416 filterStr = self.localsFilterEdit.text() |
|
417 self.debugServer.remoteClientSetFilter(0, filterStr) |
|
418 if self.currentStack: |
|
419 self.debugServer.remoteClientVariables( |
|
420 0, self.localsFilter, self.framenr) |
|
421 |
|
422 def handleDebuggingStarted(self): |
|
423 """ |
|
424 Public slot to handle the start of a debugging session. |
|
425 |
|
426 This slot sets the variables filter expressions. |
|
427 """ |
|
428 self.setGlobalsFilter() |
|
429 self.setLocalsFilter() |
|
430 self.showVariablesTab(False) |
|
431 |
|
432 def currentWidget(self): |
|
433 """ |
|
434 Public method to get a reference to the current widget. |
|
435 |
|
436 @return reference to the current widget (QWidget) |
|
437 """ |
|
438 return self.__tabWidget.currentWidget() |
|
439 |
|
440 def setCurrentWidget(self, widget): |
|
441 """ |
|
442 Public slot to set the current page based on the given widget. |
|
443 |
|
444 @param widget reference to the widget (QWidget) |
|
445 """ |
|
446 self.__tabWidget.setCurrentWidget(widget) |
|
447 |
|
448 def showThreadList(self, currentID, threadList): |
|
449 """ |
|
450 Public method to show the thread list. |
|
451 |
|
452 @param currentID id of the current thread (integer) |
|
453 @param threadList list of dictionaries containing the thread data |
|
454 """ |
|
455 citm = None |
|
456 |
|
457 self.__threadList.clear() |
|
458 for thread in threadList: |
|
459 if thread['broken']: |
|
460 state = self.tr("waiting at breakpoint") |
|
461 else: |
|
462 state = self.tr("running") |
|
463 itm = QTreeWidgetItem(self.__threadList, |
|
464 ["{0:d}".format(thread['id']), |
|
465 thread['name'], state]) |
|
466 if thread['id'] == currentID: |
|
467 citm = itm |
|
468 |
|
469 self.__threadList.header().resizeSections(QHeaderView.ResizeToContents) |
|
470 self.__threadList.header().setStretchLastSection(True) |
|
471 |
|
472 if citm: |
|
473 self.__doThreadListUpdate = False |
|
474 self.__threadList.setCurrentItem(citm) |
|
475 self.__doThreadListUpdate = True |
|
476 |
|
477 def __threadSelected(self, current, previous): |
|
478 """ |
|
479 Private slot to handle the selection of a thread in the thread list. |
|
480 |
|
481 @param current reference to the new current item (QTreeWidgetItem) |
|
482 @param previous reference to the previous current item |
|
483 (QTreeWidgetItem) |
|
484 """ |
|
485 if current is not None and self.__doThreadListUpdate: |
|
486 tid = int(current.text(0)) |
|
487 self.debugServer.remoteSetThread(tid) |
|
488 |
|
489 def __callStackFrameSelected(self, frameNo): |
|
490 """ |
|
491 Private slot to handle the selection of a call stack entry of the |
|
492 call stack viewer. |
|
493 |
|
494 @param frameNo frame number (index) of the selected entry (integer) |
|
495 """ |
|
496 if frameNo >= 0: |
|
497 self.stackComboBox.setCurrentIndex(frameNo) |