Coverage for C:\Repos\leo-editor\leo\core\leoDebugger.py: 18%
347 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
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, # Don't read a .rc file.
174 )
175 sys.stdout = stdout_q
176 self.daemon = True
177 self.path = path
178 self.prompt = '(xdb) '
179 self.saved_frame = None
180 self.saved_traceback = None
181 #@+node:ekr.20181002053718.1: *3* Overrides
182 #@+node:ekr.20190108040329.1: *4* xdb.checkline (overrides Pdb)
183 def checkline(self, path, n):
184 # pylint: disable=arguments-differ
185 # filename, lineno
186 try:
187 return pdb.Pdb.checkline(self, path, n)
188 except AttributeError:
189 return False
190 except Exception:
191 g.es_exception()
192 return False
193 #@+node:ekr.20181002061627.1: *4* xdb.cmdloop (overrides Cmd)
194 def cmdloop(self, intro=None):
195 """Override Cmd.cmdloop."""
196 assert not intro, repr(intro)
197 stop = None
198 while not stop:
199 if self.cmdqueue:
200 # Pdb.precmd sets cmdqueue.
201 line = self.cmdqueue.pop(0)
202 else:
203 self.stdout.write(self.prompt)
204 self.stdout.flush()
205 # Get the input from Leo's main thread.
206 line = self.stdin.readline() # QueueStdin.readline:
207 line = line.rstrip('\r\n') if line else 'EOF'
208 line = self.precmd(line) # Pdb.precmd.
209 stop = self.onecmd(line) # Pdb.onecmd.
210 # Show the line in Leo.
211 if stop:
212 self.select_line(self.saved_frame, self.saved_traceback)
213 #@+node:ekr.20180701050839.6: *4* xdb.do_clear (overides Pdb)
214 def do_clear(self, arg=None):
215 """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
216 With a space separated list of breakpoint numbers, clear
217 those breakpoints. Without argument, clear all breaks (but
218 first ask confirmation). With a filename:lineno argument,
219 clear all breaks at that line in that file.
220 """
221 # Same as pdb.do_clear except uses self.stdin.readline (as it should).
222 if not arg:
223 bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp]
224 if bplist:
225 print('Clear all breakpoints?')
226 reply = self.stdin.readline().strip().lower()
227 if reply in ('y', 'yes'):
228 self.clear_all_breaks()
229 for bp in bplist:
230 self.message(f"Deleted {bp}")
231 return
232 if ':' in arg:
233 # Make sure it works for "clear C:\foo\bar.py:12"
234 i = arg.rfind(':')
235 filename = arg[:i]
236 arg = arg[i + 1 :]
237 try:
238 lineno = int(arg)
239 except ValueError:
240 err = f"Invalid line number ({arg})"
241 else:
242 bplist = self.get_breaks(filename, lineno)
243 err = self.clear_break(filename, lineno)
244 if err:
245 self.error(err)
246 else:
247 for bp in bplist:
248 self.message(f"Deleted {bp}")
249 return
250 numberlist = arg.split()
251 for i in numberlist:
252 try:
253 bp = self.get_bpbynumber(i)
254 except ValueError as err:
255 self.error(err)
256 else:
257 self.clear_bpbynumber(i)
258 self.message(f"Deleted {bp}")
260 do_cl = do_clear # 'c' is already an abbreviation for 'continue'
262 # complete_clear = self._complete_location
263 # complete_cl = self._complete_location
264 #@+node:ekr.20180701050839.7: *4* xdb.do_quit (overrides Pdb)
265 def do_quit(self, arg=None):
266 """q(uit)\nexit
267 Quit from the debugger. The program being executed is aborted.
268 """
269 self.write('End xdb\n')
270 self._user_requested_quit = True
271 self.set_quit()
272 # Kill xdb *after* all other messages have been sent.
273 self.qr.put(['stop-xdb'])
274 return 1
276 do_q = do_quit
277 do_exit = do_quit
278 #@+node:ekr.20180701050839.8: *4* xdb.interaction (overrides Pdb)
279 def interaction(self, frame, traceback):
280 """Override."""
281 self.saved_frame = frame
282 self.saved_traceback = traceback
283 self.select_line(frame, traceback)
284 # Call the base class method.
285 pdb.Pdb.interaction(self, frame, traceback)
286 #@+node:ekr.20180701050839.10: *4* xdb.set_continue (overrides Bdb)
287 def set_continue(self):
288 """ override Bdb.set_continue"""
289 # Don't stop except at breakpoints or when finished
290 self._set_stopinfo(self.botframe, None, -1)
291 if not self.breaks:
292 # no breakpoints; run without debugger overhead.
293 # Do *not call kill(): only db-kill and db-q do that.
294 # self.write('clearing sys.settrace\n')
295 sys.settrace(None)
296 frame = sys._getframe().f_back
297 while frame and frame is not self.botframe:
298 del frame.f_trace
299 frame = frame.f_back
300 #@+node:ekr.20181006052604.1: *3* xdb.has_breakpoint & has_breakpoints
301 def has_breakpoint(self, filename, lineno):
302 """Return True if there is a breakpoint at the given file and line."""
303 filename = self.canonic(filename)
304 aList = self.breaks.get(filename) or []
305 return lineno in aList
307 def has_breakpoints(self):
308 """Return True if there are any breakpoints."""
309 return self.breaks
310 #@+node:ekr.20181002094126.1: *3* xdb.run
311 def run(self):
312 """The thread's run method: called via start."""
313 # pylint: disable=arguments-differ
314 from leo.core.leoQt import QtCore
315 QtCore.pyqtRemoveInputHook() # From g.pdb
316 if self.path:
317 self.run_path(self.path)
318 else:
319 self.set_trace()
320 #@+node:ekr.20180701090439.1: *3* xdb.run_path
321 def run_path(self, path):
322 """Begin execution of the python file."""
323 source = g.readFileIntoUnicodeString(path)
324 fn = g.shortFileName(path)
325 try:
326 code = compile(source, fn, 'exec')
327 except Exception:
328 g.es_exception()
329 g.trace('can not compile', path)
330 return
331 self.reset()
332 sys.settrace(self.trace_dispatch)
333 try:
334 self.quitting = False
335 exec(code, {}, {})
336 except bdb.BdbQuit:
337 if not self.quitting:
338 self.do_quit()
339 finally:
340 self.quitting = True
341 sys.settrace(None)
342 #@+node:ekr.20180701151233.1: *3* xdb.select_line
343 def select_line(self, frame, traceback):
344 """Select the given line in Leo."""
345 stack, curindex = self.get_stack(frame, traceback)
346 frame, lineno = stack[curindex]
347 filename = frame.f_code.co_filename
348 # Select the line in the main thread.
349 # xdb.show_line finalizes the file name.
350 self.qr.put(['select-line', lineno, filename])
351 #@+node:ekr.20181007044254.1: *3* xdb.write
352 def write(self, s):
353 """Write s to the output stream."""
354 self.qr.put(['put-stdout', s])
355 #@-others
356#@+node:ekr.20181007063214.1: ** top-level functions
357# These functions run in Leo's main thread.
358#@+node:ekr.20181004120344.1: *3* function: get_gnx_from_file
359def get_gnx_from_file(file_s, p, path):
360 """Set p's gnx from the @file node in the derived file."""
361 pat = re.compile(r'^#@\+node:(.*): \*+ @file (.+)$')
362 for line in g.splitLines(file_s):
363 m = pat.match(line)
364 if m:
365 gnx, path2 = m.group(1), m.group(2)
366 path2 = path2.replace('\\', '/')
367 p.v.fileIndex = gnx
368 if path == path2:
369 return True
370 g.trace(f"Not found: @+node for {path}")
371 g.trace('Reverting to @auto')
372 return False
373#@+node:ekr.20180701050839.3: *3* function: listener
374def listener(timer):
375 """
376 Listen, at idle-time, in Leo's main thread, for data on the qr channel.
378 This is a singleton timer, created by the xdb command.
379 """
380 if g.app.killed:
381 return
382 xdb = getattr(g.app, 'xdb', None)
383 if not xdb:
384 return
385 c = g.app.log.c
386 xpd_pane = getattr(c, 'xpd_pane', None)
387 kill = False
388 while not xdb.qr.empty():
389 aList = xdb.qr.get() # blocks
390 kind = aList[0]
391 if kind == 'clear-stdout':
392 if xpd_pane:
393 xpd_pane.clear()
394 elif kind == 'put-stdout':
395 message = aList[1]
396 if xpd_pane:
397 xpd_pane.write(message)
398 else:
399 sys.stdout.write(message)
400 sys.stdout.flush()
401 elif kind == 'stop-xdb':
402 kill = True
403 elif kind == 'select-line':
404 line, fn = aList[1], aList[2]
405 show_line(line, fn)
406 else:
407 g.es('unknown qr message:', aList)
408 if kill:
409 g.app.xdb = None
410 sys.stdout = sys.__stdout__
411 # Never stop the singleton timer.
412 # self.timer.stop()
413#@+node:ekr.20181004060517.1: *3* function: make_at_file_node
414def make_at_file_node(line, path):
415 """
416 Make and populate an @auto node for the given path.
417 """
418 c = g.app.log.c
419 if not c:
420 return None
421 path = g.os_path_finalize(path).replace('\\', '/')
422 if not g.os_path_exists(path):
423 g.trace('Not found:', repr(path))
424 return None
425 # Create the new node.
426 p = c.lastTopLevel().insertAfter()
427 # Like c.looksLikeDerivedFile, but retaining the contents.
428 with open(path, 'r') as f:
429 file_s = f.read()
430 is_derived = file_s.find('@+leo-ver=') > -1
431 if is_derived:
432 # Set p.v.gnx from the derived file.
433 is_derived = get_gnx_from_file(file_s, p, path)
434 kind = '@file' if is_derived else '@auto'
435 p.h = f"{kind} {path}"
436 c.selectPosition(p)
437 c.refreshFromDisk()
438 return p
439#@+node:ekr.20180701061957.1: *3* function: show_line
440def show_line(line, fn):
441 """
442 Put the cursor on the requested line of the given file.
443 fn should be a full path to a file.
444 """
445 c = g.app.log.c
446 target = g.os_path_finalize(fn).replace('\\', '/')
447 if not g.os_path_exists(fn):
448 g.trace('===== Does not exist', fn)
449 return
450 for p in c.all_positions():
451 if p.isAnyAtFileNode():
452 path = g.fullPath(c, p).replace('\\', '/')
453 if target == path:
454 # Select the line.
455 junk_p, junk_offset, ok = c.gotoCommands.find_file_line(n=line, p=p)
456 if not ok:
457 g.trace('FAIL:', target)
458 c.bodyWantsFocusNow()
459 return
460 p = make_at_file_node(line, target)
461 junk_p, junk_offset, ok = c.gotoCommands.find_file_line(n=line, p=p)
462 if not ok:
463 g.trace('FAIL:', target)
464#@+node:ekr.20181001054314.1: ** top-level xdb commands
465#@+node:ekr.20181003015017.1: *3* db-again
466@g.command('db-again')
467def xdb_again(event):
468 """Repeat the previous xdb command."""
469 xdb = getattr(g.app, 'xdb', None)
470 if xdb:
471 xdb.qc.put(xdb.lastcmd)
472 else:
473 print('xdb not active')
474#@+node:ekr.20181003054157.1: *3* db-b
475@g.command('db-b')
476def xdb_breakpoint(event):
477 """Set the breakpoint at the presently select line in Leo."""
478 c = event.get('c')
479 if not c:
480 return
481 p = c.p
482 xdb = getattr(g.app, 'xdb', None)
483 if not xdb:
484 print('xdb not active')
485 return
486 w = c.frame.body.wrapper
487 if not w:
488 return
489 x = c.gotoCommands
490 root, fileName = x.find_root(p)
491 if not root:
492 g.trace('no root', p.h)
493 return
494 path = g.fullPath(c, root)
495 n0 = x.find_node_start(p=p)
496 if n0 is None:
497 g.trace('no n0')
498 return
499 c.bodyWantsFocusNow()
500 i = w.getInsertPoint()
501 s = w.getAllText()
502 row, col = g.convertPythonIndexToRowCol(s, i)
503 n = x.node_offset_to_file_line(row, p, root)
504 if n is not None:
505 xdb.qc.put(f"b {path}:{n + 1}")
506#@+node:ekr.20180702074705.1: *3* db-c/h/l/n/q/r/s/w
507@g.command('db-c')
508def xdb_c(event):
509 """execute the pdb 'continue' command."""
510 db_command(event, 'c')
512@g.command('db-h')
513def xdb_h(event):
514 """execute the pdb 'continue' command."""
515 db_command(event, 'h')
517@g.command('db-l')
518def xdb_l(event):
519 """execute the pdb 'list' command."""
520 db_command(event, 'l')
522@g.command('db-n')
523def xdb_n(event):
524 """execute the pdb 'next' command."""
525 db_command(event, 'n')
527@g.command('db-q')
528def xdb_q(event):
529 """execute the pdb 'quit' command."""
530 db_command(event, 'q')
532@g.command('db-r')
533def xdb_r(event):
534 """execute the pdb 'return' command."""
535 db_command(event, 'r')
537@g.command('db-s')
538def xdb_s(event):
539 """execute the pdb 'step' command."""
540 db_command(event, 's')
542@g.command('db-w')
543def xdb_w(event):
544 """execute the pdb 'where' command."""
545 db_command(event, 'w')
546#@+node:ekr.20180701050839.2: *3* db-input
547@g.command('db-input')
548def xdb_input(event):
549 """Prompt the user for a pdb command and execute it."""
550 c = event.get('c')
551 if not c:
552 g.trace('no c')
553 return
554 xdb = getattr(g.app, 'xdb', None)
555 if not xdb:
556 print('xdb not active')
557 return
559 def callback(args, c, event):
560 xdb = getattr(g.app, 'xdb', None)
561 if xdb:
562 command = args[0].strip()
563 if not command:
564 command = xdb.lastcmd
565 xdb.qc.put(command)
566 else:
567 g.trace('xdb not active')
569 c.interactive(callback, event, prompts=['Debugger command: '])
570#@+node:ekr.20181003015636.1: *3* db-status
571@g.command('db-status')
572def xdb_status(event):
573 """Print whether xdb is active."""
574 xdb = getattr(g.app, 'xdb', None)
575 print('active' if xdb else 'inactive')
576#@+node:ekr.20181006163454.1: *3* do_command
577def db_command(event, command):
579 xdb = getattr(g.app, 'xdb', None)
580 if xdb:
581 xdb.qc.put(command)
582 else:
583 print('xdb not active')
584#@+node:ekr.20180701050839.1: *3* xdb
585@g.command('xdb')
586def xdb_command(event):
587 """Start the external debugger on a toy test program."""
588 c = event.get('c')
589 if not c:
590 return
591 path = g.fullPath(c, c.p)
592 if not path:
593 g.trace('Not in an @<file> tree')
594 return
595 if not g.os_path_exists(path):
596 g.trace('not found', path)
597 return
598 os.chdir(g.os_path_dirname(path))
599 xdb = getattr(g.app, 'xdb', None)
600 if xdb:
601 # Just issue a message.
602 xdb.write('xdb active: use Quit button or db-q to terminate')
603 # Killing the previous debugger works,
604 # *provided* we don't try to restart xdb!
605 # That would create a race condition on g.app.xdb.
606 # xdb.do_quit()
607 else:
608 # Start the debugger in a separate thread.
609 g.app.xdb = xdb = Xdb(path)
610 xdb.start()
611 # This is Threading.start().
612 # It runs the debugger in a separate thread.
613 # It also selects the start of the file.
614 xdb.qr.put(['clear-stdout'])
615#@-others
616#@@language python
617#@@tabwidth -4
618#@-leo