1 ;;; bashdb.el --- BASH Debugger mode via GUD and bashdb
2 ;;; $Id: bashdb.el,v 1.22 2007/07/12 06:06:05 myamato Exp $
4 ;; Copyright (C) 2002, 2006, 2007 Rocky Bernstein (rockyb@users.sf.net)
5 ;; and Masatake YAMATO (jet@gyve.org)
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
25 ;; (autoload 'bashdb "bashdb" "BASH Debugger mode via GUD and bashdb" t)
27 ;; to your .emacs file.
31 ;; 3. Give a target script for debugging and arguments for it
32 ;; See `bashdb' of describe-function for more details.
37 ;; ======================================================================
40 ;;; History of argument lists passed to bashdb.
41 (defvar gud-bashdb-history nil
)
43 ;; The debugger outputs program-location lines that look like this:
44 ;; (/etc/init.d/network:14):
45 (defconst gud-bashdb-marker-regexp
46 "^(\\(\\(?:[a-zA-Z]:\\)?[-a-zA-Z0-9_/.\\\\ ]+\\):[ \t]?\\(.*\n\\)"
47 "Regular expression used to find a file location given by bashdb.
49 Program-location lines look like this:
50 (/etc/init.d/network:39):
52 (c:\\mydirectory\\gcd.sh:10):
54 (defconst gud-bashdb-marker-regexp-file-group
1
55 "Group position in `gud-bashdb-marker-regexp' that matches the file name.")
56 (defconst gud-bashdb-marker-regexp-line-group
2
57 "Group position in `gud-bashdb-marker-regexp' that matches the line number.")
59 ;; Convert a command line as would be typed normally to run a script
60 ;; into one that invokes an Emacs-enabled debugging session.
61 ;; "--debugger" in inserted as the first switch, unless the
62 ;; command is bashdb which doesn't need and can't parse --debugger.
63 ;; Note: bashdb will be fixed up so that it *does* bass --debugger
66 (defun gud-bashdb-massage-args (file args
&optional command-line
)
67 (let* ((new-args (list "--debugger"))
70 (setq new-args
(cons (car args
) new-args
))
71 (setq args
(cdr args
)))))
73 ; If we are invoking using the bashdb command, no need to add
74 ; --debugger. '^\S ' means non-whitespace at the beginning of a
75 ; line and '\s ' means "whitespace"
76 (if (and command-line
(string-match "^\\S *bashdb\\s " command-line
))
79 ;; Pass all switches and -e scripts through.
81 (string-match "^-" (car args
))
82 (not (equal "-" (car args
)))
83 (not (equal "--" (car args
))))
87 (string-match "^-" (car args
)))
88 (error "Can't use stdin as the script to debug"))
89 ;; This is the program name.
95 (nreverse new-args
))))
97 ;; There's no guarantee that Emacs will hand the filter the entire
98 ;; marker at once; it could be broken up across several strings. We
99 ;; might even receive a big chunk with several markers in it. If we
100 ;; receive a chunk of text which looks like it might contain the
101 ;; beginning of a marker, we save it here between calls to the
103 (defun gud-bashdb-marker-filter (string)
104 (setq gud-marker-acc
(concat gud-marker-acc string
))
107 ;; Process all the complete markers in this chunk.
108 (while (string-match gud-bashdb-marker-regexp gud-marker-acc
)
111 ;; Extract the frame position from the marker.
113 (cons (substring gud-marker-acc
114 (match-beginning gud-bashdb-marker-regexp-file-group
)
115 (match-end gud-bashdb-marker-regexp-file-group
))
117 (substring gud-marker-acc
118 (match-beginning gud-bashdb-marker-regexp-line-group
)
119 (match-end gud-bashdb-marker-regexp-line-group
))))
121 ;; Append any text before the marker to the output we're going
122 ;; to return - we don't include the marker in this text.
123 output
(concat output
124 (substring gud-marker-acc
0 (match-beginning 0)))
126 ;; Set the accumulator to the remaining text.
127 gud-marker-acc
(substring gud-marker-acc
(match-end 0))))
129 ;; Does the remaining text look like it might end with the
130 ;; beginning of another marker? If it does, then keep it in
131 ;; gud-marker-acc until we receive the rest of it. Since we
132 ;; know the full marker regexp above failed, it's pretty simple to
133 ;; test for marker starts.
134 (if (string-match "\032.*\\'" gud-marker-acc
)
136 ;; Everything before the potential marker start can be output.
137 (setq output
(concat output
(substring gud-marker-acc
138 0 (match-beginning 0))))
140 ;; Everything after, we save, to combine with later input.
142 (substring gud-marker-acc
(match-beginning 0))))
144 (setq output
(concat output gud-marker-acc
)
149 (defun gud-bashdb-find-file (f)
151 (let ((buf (find-file-noselect f
'nowarn
)))
155 (defcustom gud-bashdb-command-name
"bash"
156 "File name for executing bash debugger."
161 (defun bashdb (command-line)
162 "Run bashdb on program FILE in buffer *gud-FILE*.
163 The directory containing FILE becomes the initial working directory
164 and source-file directory for your debugger.
166 You can specify a target script and its arguments like:
168 bash YOUR-SCRIPT ARGUMENT...
172 bashdb YOUR-SCRIPT ARGUMENT...
174 Generally the former one works fine. The later one may be useful if
175 you have not installed bashdb yet or you have installed Bashdb to the
176 place where Bash doesn't expect."
179 (list (read-from-minibuffer "Run bashdb (like this): "
180 (if (consp gud-bashdb-history
)
181 (car gud-bashdb-history
)
182 (concat gud-bashdb-command-name
184 gud-minibuffer-local-map nil
185 '(gud-bashdb-history .
1))))
187 ;; `gud-bashdb-massage-args' needs whole `command-line'.
188 ;; command-line is refered through dyanmic scope.
189 (gud-common-init command-line
(lambda (file args
)
190 (gud-bashdb-massage-args file args command-line
))
191 'gud-bashdb-marker-filter
'gud-bashdb-find-file
)
193 (set (make-local-variable 'gud-minor-mode
) 'bashdb
)
195 (gud-def gud-args
"info args" "a"
196 "Show arguments of the current stack frame.")
197 (gud-def gud-break
"break %d%f:%l" "\C-b"
198 "Set breakpoint at the current line.")
199 (gud-def gud-cont
"continue" "\C-r"
200 "Continue with display.")
201 (gud-def gud-down
"down %p" ">"
202 "Down N stack frames (numeric arg).")
203 (gud-def gud-finish
"finish" "f\C-f"
204 "Finish executing current function.")
205 (gud-def gud-linetrace
"toggle" "t"
206 "Toggle line tracing.")
207 (gud-def gud-next
"next %p" "\C-n"
208 "Step one line (skip functions).")
209 (gud-def gud-print
"p %e" "\C-p"
210 "Evaluate bash expression at point.")
211 (gud-def gud-remove
"clear %d%f:%l" "\C-d"
212 "Remove breakpoint at current line")
213 (gud-def gud-run
"run" "R"
214 "Restart the Bash script.")
215 (gud-def gud-statement
"eval %e" "\C-e"
216 "Execute Bash statement at point.")
217 (gud-def gud-step
"step %p" "\C-s"
218 "Step one source line with display.")
219 (gud-def gud-tbreak
"tbreak %d%f:%l" "\C-t"
220 "Set temporary breakpoint at current line.")
221 (gud-def gud-up
"up %p"
222 "<" "Up N stack frames (numeric arg).")
223 (gud-def gud-where
"where"
224 "T" "Show stack trace.")
226 ;; Update GUD menu bar
227 (define-key gud-menu-map
[args] '("Show arguments of current stack" .
229 (define-key gud-menu-map [down] '("Down Stack" . gud-down))
230 (define-key gud-menu-map [eval] '("Execute Bash statement at point"
232 (define-key gud-menu-map [finish] '("Finish Function" . gud-finish))
233 (define-key gud-menu-map [linetrace] '("Toggle line tracing" .
235 (define-key gud-menu-map [run] '("Restart the Bash Script" .
237 (define-key gud-menu-map [stepi] nil)
238 (define-key gud-menu-map [tbreak] nil)
239 (define-key gud-menu-map [up] '("Up Stack" . gud-up))
240 (define-key gud-menu-map [where] '("Show stack trace" . gud-where))
242 (local-set-key "\C-i" 'gud-bash-complete-command)
244 (local-set-key [menu-bar debug tbreak]
245 '("Temporary Breakpoint" . gud-tbreak))
246 (local-set-key [menu-bar debug finish] '("Finish Function" . gud-finish))
247 (local-set-key [menu-bar debug up] '("Up Stack" . gud-up))
248 (local-set-key [menu-bar debug down] '("Down Stack" . gud-down))
250 (setq comint-prompt-regexp "^bashdb<+(*[0-9]*)*>+ ")
251 (setq paragraph-start comint-prompt-regexp)
252 (run-hooks 'bashdb-mode-hook)
255 (defun gud-bashdb-complete-command (&optional command a b)
256 "A wrapper for `gud-gdb-complete-command'"
257 (gud-gdb-complete-command command a b))
261 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
262 ;;; bashdbtrack --- tracking bashdb debugger in an Emacs shell window
263 ;;; Modified from python-mode in particular the part:
264 ;; pdbtrack support contributed by Ken Manheimer, April 2001.
274 (defgroup bashdbtrack nil
275 "Bashdb file tracking by watching the prompt."
276 :prefix "bashdb-bashdbtrack-"
280 ;; user definable variables
281 ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
283 (defcustom bashdb-bashdbtrack-do-tracking-p t
284 "*Controls whether the bashdbtrack feature is enabled or not.
285 When non-nil, bashdbtrack is enabled in all comint-based buffers,
286 e.g. shell buffers and the *Python* buffer. When using bashdb to debug a
287 Python program, bashdbtrack notices the bashdb prompt and displays the
288 source file and line that the program is stopped at, much the same way
289 as gud-mode does for debugging C programs with gdb."
292 (make-variable-buffer-local 'bashdb-bashdbtrack-do-tracking-p)
294 (defcustom bashdb-bashdbtrack-minor-mode-string " BASHDB"
295 "*String to use in the minor mode list when bashdbtrack is enabled."
299 (defcustom bashdb-temp-directory
300 (let ((ok '(lambda (x)
302 (setq x (expand-file-name x)) ; always true
306 (or (funcall ok (getenv "TMPDIR"))
307 (funcall ok "/usr/tmp")
309 (funcall ok "/var/tmp")
312 "Couldn't find a usable temp directory -- set `bashdb-temp-directory'")))
313 "*Directory used for temporary files created by a *Python* process.
314 By default, the first directory from this list that exists and that you
315 can write into: the value (if any) of the environment variable TMPDIR,
316 /usr/tmp, /tmp, /var/tmp, or the current directory."
321 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
322 ;; NO USER DEFINABLE VARIABLES BEYOND THIS POINT
324 ;; have to bind bashdb-file-queue before installing the kill-emacs-hook
325 (defvar bashdb-file-queue nil
326 "Queue of Makefile temp files awaiting execution.
327 Currently-active file is at the head of the list.")
329 (defvar bashdb-bashdbtrack-is-tracking-p nil)
334 (defconst bashdb-position-re
335 "\\(^\\|\n\\)(\\([^:]+\\):\\([0-9]*\\)).*\n"
336 "Regular expression for a bashdb position")
338 (defconst bashdb-marker-regexp-file-group 2
339 "Group position in bashdb-postiion-re that matches the file name.")
341 (defconst bashdb-marker-regexp-line-group 3
342 "Group position in bashdb-position-re that matches the line number.")
346 (defconst bashdb-traceback-line-re
347 "^#[0-9]+[ \t]+\\((\\([a-zA-Z-.]+\\) at (\\(\\([a-zA-Z]:\\)?[^:\n]*\\):\\([0-9]*\\)).*\n"
348 "Regular expression that describes tracebacks.")
350 ;; bashdbtrack contants
351 (defconst bashdb-bashdbtrack-stack-entry-regexp
352 "^=>#[0-9]+[ \t]+\\((\\([a-zA-Z-.]+\\) at (\\(\\([a-zA-Z]:\\)?[^:\n]*\\):\\([0-9]*\\)).*\n"
353 "Regular expression bashdbtrack uses to find a stack trace entry.")
355 (defconst bashdb-bashdbtrack-input-prompt "\nbashdb<+.*>+ "
356 "Regular expression bashdbtrack uses to recognize a bashdb prompt.")
358 (defconst bashdb-bashdbtrack-track-range 10000
359 "Max number of characters from end of buffer to search for stack entry.")
363 (defmacro bashdb-safe (&rest body)
364 "Safely execute BODY, return nil if an error occurred."
365 (` (condition-case nil
372 (defun bashdb-bashdbtrack-overlay-arrow (activation)
373 "Activate or de arrow at beginning-of-line in current buffer."
374 ;; This was derived/simplified from edebug-overlay-arrow
376 (setq overlay-arrow-position (make-marker))
377 (setq overlay-arrow-string "=>")
378 (set-marker overlay-arrow-position (point) (current-buffer))
379 (setq bashdb-bashdbtrack-is-tracking-p t))
380 (bashdb-bashdbtrack-is-tracking-p
381 (setq overlay-arrow-position nil)
382 (setq bashdb-bashdbtrack-is-tracking-p nil))
385 (defun bashdb-bashdbtrack-track-stack-file (text)
386 "Show the file indicated by the bashdb stack entry line, in a separate window.
387 Activity is disabled if the buffer-local variable
388 `bashdb-bashdbtrack-do-tracking-p' is nil.
390 We depend on the bashdb input prompt matching `bashdb-bashdbtrack-input-prompt'
391 at the beginning of the line.
393 ;; Instead of trying to piece things together from partial text
394 ;; (which can be almost useless depending on Emacs version), we
395 ;; monitor to the point where we have the next bashdb prompt, and then
396 ;; check all text from comint-last-input-end to process-mark.
398 ;; Also, we're very conservative about clearing the overlay arrow,
399 ;; to minimize residue. This means, for instance, that executing
400 ;; other bashdb commands wipe out the highlight. You can always do a
401 ;; 'where' (aka 'w') command to reveal the overlay arrow.
402 (let* ((origbuf (current-buffer))
403 (currproc (get-buffer-process origbuf)))
405 (if (not (and currproc bashdb-bashdbtrack-do-tracking-p))
406 (bashdb-bashdbtrack-overlay-arrow nil)
408 (let* ((procmark (process-mark currproc))
409 (block (buffer-substring (max comint-last-input-end
411 bashdb-bashdbtrack-track-range))
413 target target_fname target_lineno target_buffer)
415 (if (not (string-match (concat bashdb-bashdbtrack-input-prompt "$") block))
416 (bashdb-bashdbtrack-overlay-arrow nil)
418 (setq target (bashdb-bashdbtrack-get-source-buffer block))
421 (message "bashdbtrack: %s" target)
423 (setq target_lineno (car target))
424 (setq target_buffer (cadr target))
425 (setq target_fname (buffer-file-name target_buffer))
426 (switch-to-buffer-other-window target_buffer)
427 (goto-line target_lineno)
428 (message "bashdbtrack: line %s, file %s" target_lineno target_fname)
429 (bashdb-bashdbtrack-overlay-arrow t)
430 (pop-to-buffer origbuf t)
435 (defun bashdb-bashdbtrack-get-source-buffer (block)
436 "Return line number and buffer of code indicated by block's traceback text.
438 We look first to visit the file indicated in the trace.
440 Failing that, we look for the most recently visited python-mode buffer
441 with the same name or having
442 having the named function.
444 If we're unable find the source code we return a string describing the
445 problem as best as we can determine."
447 (if (not (string-match bashdb-position-re block))
449 "line number cue not found"
451 (let* ((filename (match-string bashdb-marker-regexp-file-group block))
452 (lineno (string-to-number
453 (match-string bashdb-marker-regexp-line-group block)))
456 (cond ((file-exists-p filename)
457 (list lineno (find-file-noselect filename)))
459 ((= (elt filename 0) ?\<)
460 (format "(Non-file source: '%s')" filename))
462 (t (format "Not found: %s" filename)))
468 ;;; Subprocess commands
472 ;; bashdbtrack functions
473 (defun bashdb-bashdbtrack-toggle-stack-tracking (arg)
475 (if (not (get-buffer-process (current-buffer)))
476 (error "No process associated with buffer '%s'" (current-buffer)))
477 ;; missing or 0 is toggle, >0 turn on, <0 turn off
479 (zerop (setq arg (prefix-numeric-value arg))))
480 (setq bashdb-bashdbtrack-do-tracking-p (not bashdb-bashdbtrack-do-tracking-p))
481 (setq bashdb-bashdbtrack-do-tracking-p (> arg 0)))
482 (message "%sabled bashdb's bashdbtrack"
483 (if bashdb-bashdbtrack-do-tracking-p "En" "Dis")))
485 (defun turn-on-bashdbtrack ()
487 (add-hook 'comint-output-filter-functions
488 'bashdb-bashdbtrack-track-stack-file)
489 (setq bashdb-bashdbtrack-is-tracking-p t)
490 (bashdb-bashdbtrack-toggle-stack-tracking 1))
492 (defun turn-off-bashdbtrack ()
494 (remove-hook 'comint-output-filter-functions
495 'bashdb-bashdbtrack-track-stack-file)
496 (setq bashdb-bashdbtrack-is-tracking-p nil)
497 (bashdb-bashdbtrack-toggle-stack-tracking 0))
499 ;; Add a designator to the minor mode strings
500 (or (assq 'bashdb-bashdbtrack-is-tracking-p minor-mode-alist)
501 (push '(bashdb-bashdbtrack-is-tracking-p
502 bashdb-bashdbtrack-minor-mode-string)
507 ;;; bashdbtrack.el ends here
509 ;;; bashdb.el ends here