Coverage for C:\leo.repo\leo-editor\leo\core\leoDebugger.py : 18%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20130302121602.10208: * @file leoDebugger.py
4#@@first
6# Disable all mypy errors.
7# type:ignore
9#@+<< leoDebugger.py docstring >>
10#@+node:ekr.20181006100710.1: ** << leoDebugger.py docstring >>
11"""
12Leo's integrated debugger supports the xdb and various db-* commands,
13corresponding to the pdb commands:
15**Commands**
17xdb: Start a Leo's integrated debugger.
18The presently-selected node should be within an @<file> tree.
20Now you are ready to execute all the db-* commands. You can execute these
21commands from the minibuffer, or from the the Debug pane. The following
22correspond to the pdb commands::
24 db-b: Set a breakpoint at the line shown in Leo. It should be an executable line.
25 db-c: Continue, that is, run until the next breakpoint.
26 db-h: Print the help message (in the console, for now)
27 db-l: List a few lines around present location.
28 db-n: Execute the next line.
29 db-q: End the debugger.
30 db-r: Return from the present function/method.
31 db-s: Step into the next line.
32 db-w: Print the stack.
34There are two additional commands::
36 db-again: Run the previous db-command.
37 db-input: Prompt for any pdb command, then execute it.
39The db-input command allows you can enter any pdb command at all. For
40example: "print c". But you don't have to run these commands from the
41minibuffer, as discussed next.
43**Setting breakpoints in the gutter**
45When @bool use_gutter = True, Leo shows a border in the body pane. By
46default, the line-numbering.py plugin is enabled, and if so, the gutter
47shows correct line number in the external file.
49After executing the xdb command, you can set a breakpoint on any executable
50line by clicking in the gutter to the right of the line number. You can
51also set a breakpoint any time the debugger is stopped.
53Using the gutter is optional. You can also set breakpoints with the db-b
54command or by typing "d line-number" or "d file-name:line-number" using the
55db-input command, or by using the Debug pane (see below)
57**The Debug pane**
59The xdb_pane.py plugin creates the Debug pane in the Log pane. The pane
60contains buttons for all the commands listed above. In addition, there is
61an input area in which you can enter pdb commands. This is a bit easier to
62use than the db-input command.
64**Summary**
66The xdb and db-* commands are always available. They correspond to pdb
67commands.
69When xdb is active you may set breakpoints by clicking in the gutter next
70to any executable line. The line_numbering plugin must be enabled and @bool
71use_gutter must be True.
73The xdb_pane plugin creates the Debug pane in the Log window.
74"""
75#@-<< leoDebugger.py docstring >>
76#@+<< leoDebugger.py imports >>
77#@+node:ekr.20181006100604.1: ** << leoDebugger.py imports >>
78import bdb
79import queue
80import os
81import pdb
82import re
83import sys
84import threading
85from leo.core import leoGlobals as g
86#@-<< leoDebugger.py imports >>
87#@+others
88#@+node:ekr.20180701050839.5: ** class Xdb (pdb.Pdb, threading.Thread)
89class Xdb(pdb.Pdb, threading.Thread):
90 """
91 An debugger, a subclass of Pdb, that runs in a separate thread without
92 hanging Leo. Only one debugger, g.app.xdb, can be active at any time.
94 A singleton listener method, g.app,xdb_timer, runs in the main Leo
95 thread. The listener runs until Leo exists.
97 Two Queues communicate between the threads:
99 - xdb.qc contains commands from the main thread to this thread.
100 All xdb/pdb input comes from this queue.
102 - xdb.qr contains requests from the xdb thread to the main thread.
103 All xdb/pdb output goes to this queue.
105 Settings
106 --------
108 - @bool use_xdb_pane_output_area: when True, all debugger output is sent
109 to an output area in the Debug pane.
111 @bool use_gutter: when True, line numbers appear to the left of
112 the body pane. Clicking to the left of the gutter toggles breakpoints
113 when xdb is active.
114 """
115 #@+others
116 #@+node:ekr.20180701050839.4: *3* class QueueStdin (obj)
117 class QueueStdin:
118 """
119 A replacement for Python's stdin class containing only readline().
120 """
122 def __init__(self, qc):
123 self.qc = qc
125 def readline(self):
126 """Return the next line from the qc channel."""
127 s = self.qc.get() # blocks
128 if 1:
129 # Just echo.
130 print(s.rstrip())
131 else:
132 # Use the output area.
133 xdb = getattr(g.app, 'xdb')
134 if xdb:
135 xdb.write(s)
136 else:
137 print(s)
138 return s
139 #@+node:ekr.20181003020344.1: *3* class QueueStdout (obj)
140 class QueueStdout:
141 """
142 A replacement for Python's stdout class containing only write().
143 """
145 def __init__(self, qr):
146 self.qr = qr
148 def flush(self):
149 pass
151 def write(self, s):
152 """Write s to the qr channel"""
153 self.qr.put(['put-stdout', s])
154 #@+node:ekr.20181006160108.1: *3* xdb.__init__
155 def __init__(self, path=None):
157 self.qc = queue.Queue() # The command queue.
158 self.qr = queue.Queue() # The request queue.
159 stdin_q = self.QueueStdin(qc=self.qc)
160 stdout_q = self.QueueStdout(qr=self.qr)
161 # Start the singleton listener, in the main Leo thread.
162 timer = getattr(g.app, 'xdb_timer', None)
163 if timer:
164 self.timer = timer
165 else:
166 self.timer = g.IdleTime(listener, delay=0)
167 self.timer.start()
168 # Init the base classes.
169 threading.Thread.__init__(self)
170 super().__init__(
171 stdin=stdin_q,
172 stdout=stdout_q,
173 readrc=False,
174 # Don't read a .rc file.
175 )
176 sys.stdout = stdout_q
177 self.daemon = True
178 self.path = path
179 self.prompt = '(xdb) '
180 self.saved_frame = None
181 self.saved_traceback = None
182 #@+node:ekr.20181002053718.1: *3* Overrides
183 #@+node:ekr.20190108040329.1: *4* xdb.checkline (overrides Pdb)
184 def checkline(self, path, n):
185 # pylint: disable=arguments-differ
186 # filename, lineno
187 try:
188 return pdb.Pdb.checkline(self, path, n)
189 except AttributeError:
190 return False
191 except Exception:
192 g.es_exception()
193 return False
194 #@+node:ekr.20181002061627.1: *4* xdb.cmdloop (overrides Cmd)
195 def cmdloop(self, intro=None):
196 """Override Cmd.cmdloop."""
197 assert not intro, repr(intro)
198 stop = None
199 while not stop:
200 if self.cmdqueue:
201 # Pdb.precmd sets cmdqueue.
202 line = self.cmdqueue.pop(0)
203 else:
204 self.stdout.write(self.prompt)
205 self.stdout.flush()
206 line = self.stdin.readline()
207 # QueueStdin.readline.
208 # Get the input from Leo's main thread.
209 line = line.rstrip('\r\n') if line else 'EOF'
210 line = self.precmd(line)
211 # Pdb.precmd.
212 stop = self.onecmd(line)
213 # Pdb.onecmd.
214 # Show the line in Leo.
215 if stop:
216 self.select_line(self.saved_frame, self.saved_traceback)
217 #@+node:ekr.20180701050839.6: *4* xdb.do_clear (overides Pdb)
218 def do_clear(self, arg=None):
219 """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
220 With a space separated list of breakpoint numbers, clear
221 those breakpoints. Without argument, clear all breaks (but
222 first ask confirmation). With a filename:lineno argument,
223 clear all breaks at that line in that file.
224 """
225 # Same as pdb.do_clear except uses self.stdin.readline (as it should).
226 if not arg:
227 bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp]
228 if bplist:
229 print('Clear all breakpoints?')
230 reply = self.stdin.readline().strip().lower()
231 if reply in ('y', 'yes'):
232 self.clear_all_breaks()
233 for bp in bplist:
234 self.message(f"Deleted {bp}")
235 return
236 if ':' in arg:
237 # Make sure it works for "clear C:\foo\bar.py:12"
238 i = arg.rfind(':')
239 filename = arg[:i]
240 arg = arg[i + 1 :]
241 try:
242 lineno = int(arg)
243 except ValueError:
244 err = f"Invalid line number ({arg})"
245 else:
246 bplist = self.get_breaks(filename, lineno)
247 err = self.clear_break(filename, lineno)
248 if err:
249 self.error(err)
250 else:
251 for bp in bplist:
252 self.message(f"Deleted {bp}")
253 return
254 numberlist = arg.split()
255 for i in numberlist:
256 try:
257 bp = self.get_bpbynumber(i)
258 except ValueError as err:
259 self.error(err)
260 else:
261 self.clear_bpbynumber(i)
262 self.message(f"Deleted {bp}")
264 do_cl = do_clear # 'c' is already an abbreviation for 'continue'
266 # complete_clear = self._complete_location
267 # complete_cl = self._complete_location
268 #@+node:ekr.20180701050839.7: *4* xdb.do_quit (overrides Pdb)
269 def do_quit(self, arg=None):
270 """q(uit)\nexit
271 Quit from the debugger. The program being executed is aborted.
272 """
273 self.write('End xdb\n')
274 self._user_requested_quit = True
275 self.set_quit()
276 self.qr.put(['stop-xdb'])
277 # Kill xdb *after* all other messages have been sent.
278 return 1
280 do_q = do_quit
281 do_exit = do_quit
282 #@+node:ekr.20180701050839.8: *4* xdb.interaction (overrides Pdb)
283 def interaction(self, frame, traceback):
284 """Override."""
285 self.saved_frame = frame
286 self.saved_traceback = traceback
287 self.select_line(frame, traceback)
288 pdb.Pdb.interaction(self, frame, traceback)
289 # Call the base class method.
290 #@+node:ekr.20180701050839.10: *4* xdb.set_continue (overrides Bdb)
291 def set_continue(self):
292 """ override Bdb.set_continue"""
293 # Don't stop except at breakpoints or when finished
294 self._set_stopinfo(self.botframe, None, -1)
295 if not self.breaks:
296 # no breakpoints; run without debugger overhead.
297 # Do *not call kill(): only db-kill and db-q do that.
298 # self.write('clearing sys.settrace\n')
299 sys.settrace(None)
300 frame = sys._getframe().f_back
301 while frame and frame is not self.botframe:
302 del frame.f_trace
303 frame = frame.f_back
304 #@+node:ekr.20181006052604.1: *3* xdb.has_breakpoint & has_breakpoints
305 def has_breakpoint(self, filename, lineno):
306 """Return True if there is a breakpoint at the given file and line."""
307 filename = self.canonic(filename)
308 aList = self.breaks.get(filename) or []
309 return lineno in aList
311 def has_breakpoints(self):
312 """Return True if there are any breakpoints."""
313 return self.breaks
314 #@+node:ekr.20181002094126.1: *3* xdb.run
315 def run(self):
316 """The thread's run method: called via start."""
317 # pylint: disable=arguments-differ
318 from leo.core.leoQt import QtCore
319 QtCore.pyqtRemoveInputHook() # From g.pdb
320 if self.path:
321 self.run_path(self.path)
322 else:
323 self.set_trace()
324 #@+node:ekr.20180701090439.1: *3* xdb.run_path
325 def run_path(self, path):
326 """Begin execution of the python file."""
327 source = g.readFileIntoUnicodeString(path)
328 fn = g.shortFileName(path)
329 try:
330 code = compile(source, fn, 'exec')
331 except Exception:
332 g.es_exception()
333 g.trace('can not compile', path)
334 return
335 self.reset()
336 sys.settrace(self.trace_dispatch)
337 try:
338 self.quitting = False
339 exec(code, {}, {})
340 except bdb.BdbQuit:
341 if not self.quitting:
342 self.do_quit()
343 finally:
344 self.quitting = True
345 sys.settrace(None)
346 #@+node:ekr.20180701151233.1: *3* xdb.select_line
347 def select_line(self, frame, traceback):
348 """Select the given line in Leo."""
349 stack, curindex = self.get_stack(frame, traceback)
350 frame, lineno = stack[curindex]
351 filename = frame.f_code.co_filename
352 self.qr.put(['select-line', lineno, filename])
353 # Select the line in the main thread.
354 # xdb.show_line finalizes the file name.
355 #@+node:ekr.20181007044254.1: *3* xdb.write
356 def write(self, s):
357 """Write s to the output stream."""
358 self.qr.put(['put-stdout', s])
359 #@-others
360#@+node:ekr.20181007063214.1: ** top-level functions
361# These functions run in Leo's main thread.
362#@+node:ekr.20181004120344.1: *3* function: get_gnx_from_file
363def get_gnx_from_file(file_s, p, path):
364 """Set p's gnx from the @file node in the derived file."""
365 pat = re.compile(r'^#@\+node:(.*): \*+ @file (.+)$')
366 for line in g.splitLines(file_s):
367 m = pat.match(line)
368 if m:
369 gnx, path2 = m.group(1), m.group(2)
370 path2 = path2.replace('\\', '/')
371 p.v.fileIndex = gnx
372 if path == path2:
373 return True
374 g.trace(f"Not found: @+node for {path}")
375 g.trace('Reverting to @auto')
376 return False
377#@+node:ekr.20180701050839.3: *3* function: listener
378def listener(timer):
379 """
380 Listen, at idle-time, in Leo's main thread, for data on the qr channel.
382 This is a singleton timer, created by the xdb command.
383 """
384 if g.app.killed:
385 return
386 xdb = getattr(g.app, 'xdb', None)
387 if not xdb:
388 return
389 c = g.app.log.c
390 xpd_pane = getattr(c, 'xpd_pane', None)
391 kill = False
392 while not xdb.qr.empty():
393 aList = xdb.qr.get() # blocks
394 kind = aList[0]
395 if kind == 'clear-stdout':
396 if xpd_pane:
397 xpd_pane.clear()
398 elif kind == 'put-stdout':
399 message = aList[1]
400 if xpd_pane:
401 xpd_pane.write(message)
402 else:
403 sys.stdout.write(message)
404 sys.stdout.flush()
405 elif kind == 'stop-xdb':
406 kill = True
407 elif kind == 'select-line':
408 line, fn = aList[1], aList[2]
409 show_line(line, fn)
410 else:
411 g.es('unknown qr message:', aList)
412 if kill:
413 g.app.xdb = None
414 sys.stdout = sys.__stdout__
415 # Never stop the singleton timer.
416 # self.timer.stop()
417#@+node:ekr.20181004060517.1: *3* function: make_at_file_node
418def make_at_file_node(line, path):
419 """
420 Make and populate an @auto node for the given path.
421 """
422 c = g.app.log.c
423 if not c:
424 return None
425 path = g.os_path_finalize(path).replace('\\', '/')
426 if not g.os_path_exists(path):
427 g.trace('Not found:', repr(path))
428 return None
429 # Create the new node.
430 p = c.lastTopLevel().insertAfter()
431 # Like c.looksLikeDerivedFile, but retaining the contents.
432 with open(path, 'r') as f:
433 file_s = f.read()
434 is_derived = file_s.find('@+leo-ver=') > -1
435 if is_derived:
436 # Set p.v.gnx from the derived file.
437 is_derived = get_gnx_from_file(file_s, p, path)
438 kind = '@file' if is_derived else '@auto'
439 p.h = f"{kind} {path}"
440 c.selectPosition(p)
441 c.refreshFromDisk()
442 return p
443#@+node:ekr.20180701061957.1: *3* function: show_line
444def show_line(line, fn):
445 """
446 Put the cursor on the requested line of the given file.
447 fn should be a full path to a file.
448 """
449 c = g.app.log.c
450 target = g.os_path_finalize(fn).replace('\\', '/')
451 if not g.os_path_exists(fn):
452 g.trace('===== Does not exist', fn)
453 return
454 for p in c.all_positions():
455 if p.isAnyAtFileNode():
456 path = g.fullPath(c, p).replace('\\', '/')
457 if target == path:
458 # Select the line.
459 junk_p, junk_offset, ok = c.gotoCommands.find_file_line(n=line, p=p)
460 if not ok:
461 g.trace('FAIL:', target)
462 c.bodyWantsFocusNow()
463 return
464 p = make_at_file_node(line, target)
465 junk_p, junk_offset, ok = c.gotoCommands.find_file_line(n=line, p=p)
466 if not ok:
467 g.trace('FAIL:', target)
468#@+node:ekr.20181001054314.1: ** top-level xdb commands
469#@+node:ekr.20181003015017.1: *3* db-again
470@g.command('db-again')
471def xdb_again(event):
472 """Repeat the previous xdb command."""
473 xdb = getattr(g.app, 'xdb', None)
474 if xdb:
475 xdb.qc.put(xdb.lastcmd)
476 else:
477 print('xdb not active')
478#@+node:ekr.20181003054157.1: *3* db-b
479@g.command('db-b')
480def xdb_breakpoint(event):
481 """Set the breakpoint at the presently select line in Leo."""
482 c = event.get('c')
483 if not c:
484 return
485 p = c.p
486 xdb = getattr(g.app, 'xdb', None)
487 if not xdb:
488 print('xdb not active')
489 return
490 w = c.frame.body.wrapper
491 if not w:
492 return
493 x = c.gotoCommands
494 root, fileName = x.find_root(p)
495 if not root:
496 g.trace('no root', p.h)
497 return
498 path = g.fullPath(c, root)
499 n0 = x.find_node_start(p=p)
500 if n0 is None:
501 g.trace('no n0')
502 return
503 c.bodyWantsFocusNow()
504 i = w.getInsertPoint()
505 s = w.getAllText()
506 row, col = g.convertPythonIndexToRowCol(s, i)
507 n = x.node_offset_to_file_line(row, p, root)
508 if n is not None:
509 xdb.qc.put(f"b {path}:{n + 1}")
510#@+node:ekr.20180702074705.1: *3* db-c/h/l/n/q/r/s/w
511@g.command('db-c')
512def xdb_c(event):
513 """execute the pdb 'continue' command."""
514 db_command(event, 'c')
516@g.command('db-h')
517def xdb_h(event):
518 """execute the pdb 'continue' command."""
519 db_command(event, 'h')
521@g.command('db-l')
522def xdb_l(event):
523 """execute the pdb 'list' command."""
524 db_command(event, 'l')
526@g.command('db-n')
527def xdb_n(event):
528 """execute the pdb 'next' command."""
529 db_command(event, 'n')
531@g.command('db-q')
532def xdb_q(event):
533 """execute the pdb 'quit' command."""
534 db_command(event, 'q')
536@g.command('db-r')
537def xdb_r(event):
538 """execute the pdb 'return' command."""
539 db_command(event, 'r')
541@g.command('db-s')
542def xdb_s(event):
543 """execute the pdb 'step' command."""
544 db_command(event, 's')
546@g.command('db-w')
547def xdb_w(event):
548 """execute the pdb 'where' command."""
549 db_command(event, 'w')
550#@+node:ekr.20180701050839.2: *3* db-input
551@g.command('db-input')
552def xdb_input(event):
553 """Prompt the user for a pdb command and execute it."""
554 c = event.get('c')
555 if not c:
556 g.trace('no c')
557 return
558 xdb = getattr(g.app, 'xdb', None)
559 if not xdb:
560 print('xdb not active')
561 return
563 def callback(args, c, event):
564 xdb = getattr(g.app, 'xdb', None)
565 if xdb:
566 command = args[0].strip()
567 if not command:
568 command = xdb.lastcmd
569 xdb.qc.put(command)
570 else:
571 g.trace('xdb not active')
573 c.interactive(callback, event, prompts=['Debugger command: '])
574#@+node:ekr.20181003015636.1: *3* db-status
575@g.command('db-status')
576def xdb_status(event):
577 """Print whether xdb is active."""
578 xdb = getattr(g.app, 'xdb', None)
579 print('active' if xdb else 'inactive')
580#@+node:ekr.20181006163454.1: *3* do_command
581def db_command(event, command):
583 xdb = getattr(g.app, 'xdb', None)
584 if xdb:
585 xdb.qc.put(command)
586 else:
587 print('xdb not active')
588#@+node:ekr.20180701050839.1: *3* xdb
589@g.command('xdb')
590def xdb_command(event):
591 """Start the external debugger on a toy test program."""
592 c = event.get('c')
593 if not c:
594 return
595 path = g.fullPath(c, c.p)
596 if not path:
597 g.trace('Not in an @<file> tree')
598 return
599 if not g.os_path_exists(path):
600 g.trace('not found', path)
601 return
602 os.chdir(g.os_path_dirname(path))
603 xdb = getattr(g.app, 'xdb', None)
604 if xdb:
605 # Just issue a message.
606 xdb.write('xdb active: use Quit button or db-q to terminate')
607 # Killing the previous debugger works,
608 # *provided* we don't try to restart xdb!
609 # That would create a race condition on g.app.xdb.
610 # xdb.do_quit()
611 else:
612 # Start the debugger in a separate thread.
613 g.app.xdb = xdb = Xdb(path)
614 xdb.start()
615 xdb.qr.put(['clear-stdout'])
616 # This is Threading.start().
617 # It runs the debugger in a separate thread.
618 # It also selects the start of the file.
619#@-others
620#@@language python
621#@@tabwidth -4
622#@-leo