[Python-Dev] Enhancement to pdb in gud.el

Nick Roberts nick at nick.uklinux.net
Sat Sep 20 11:20:50 EDT 2003

Kevin J. Butler writes:
 > ...
 > After my patch:
 > (Pdb) cl 3
 > Deleted breakpoint 3 at z:\work\wm\wm.py:136
 > (Pdb) cl wm.py:17
 > Deleted breakpoint 5 at z:\work\wm\wm.py:17

I've applied your patch manually to my files bdb.py and pdb.py (I have 2.2)
and it appears to work. If you load the file below in Emacs (M-x eval-buffer
or M-x load-file), then type M-x pdb, the debugger (with your patch) should
work with breakpoint icons in the display margin. Emacs must be a version from
the CVS repository to work straight away. This will also give you buttons for
debugging if you turn the toolbar on (M-x tool-bar-mode).

I've tested it on GNU/Linux but it should work on Windows too if I've defined
the regexp correctly.

I've just noticed that you can enable/disable breakpoints in python. In my
mode for gdb, the breakpoints are greyed out when the breakpoints become
disabled. I could do this for python too if these commands were changed to
emit a message.

If your patches are accepted (by SF?) then I will integrate my changes into
gud.el in the Emacs CVS repository.



(require 'gud)

(defun gud-sentinel (proc msg)
  (cond ((null (buffer-name (process-buffer proc)))
	 ;; buffer killed
	 ;; Stop displaying an arrow in a source file.
	 (setq overlay-arrow-position nil)
	 (set-process-buffer proc nil)
	 (if (memq gud-minor-mode-type '(gdba pdb))
	((memq (process-status proc) '(signal exit))
	 ;; Stop displaying an arrow in a source file.
	 (setq overlay-arrow-position nil)
	 (with-current-buffer gud-comint-buffer
	   (if (memq gud-minor-mode '(gdba pdb))
	 (let* ((obuf (current-buffer)))
	   ;; save-excursion isn't the right thing if
	   ;;  process-buffer is current-buffer
		 ;; Write something in *compilation* and hack its mode line,
		 (set-buffer (process-buffer proc))
		 ;; Fix the mode line.
		 (setq mode-line-process
		       (concat ":"
			       (symbol-name (process-status proc))))
		 (if (eobp)
		     (insert ?\n mode-name " " msg)
		     (goto-char (point-max))
		     (insert ?\n mode-name " " msg)))
		 ;; If buffer and mode line will show that the process
		 ;; is dead, we can delete it now.  Otherwise it
		 ;; will stay around until M-x list-processes.
		 (delete-process proc))
	     ;; Restore old buffer, but don't restore old point
	     ;; if obuf is the gud buffer.
	     (set-buffer obuf))))))

(defun gdb-reset ()
  "Exit a debugging session cleanly by killing the gdb buffers and resetting
 the source buffers."
  (dolist (buffer (buffer-list))
    (if (not (eq buffer gud-comint-buffer))
	(with-current-buffer buffer
	  (if (memq gud-minor-mode '(gdba pdb))
	      (if (string-match "^\*.+*$" (buffer-name))
		  (kill-buffer nil)
		(if (display-images-p)
		    (remove-images (point-min) (point-max))
		  (gdb-remove-strings (point-min) (point-max)))
		(setq left-margin-width 0)
		(setq gud-minor-mode nil)
		(kill-local-variable 'tool-bar-map)
		(setq gud-running nil)
		(if (get-buffer-window (current-buffer))
		    (set-window-margins (get-buffer-window

(defun gdb-put-string (putstring pos)
  "Put string PUTSTRING in front of POS in the current buffer.
PUTSTRING is displayed by putting an overlay into the current buffer with a
`before-string' STRING that has a `display' property whose value is
  (let ((gdb-string "x")
	(buffer (current-buffer)))
    (let ((overlay (make-overlay pos pos buffer))
	  (prop (list (list 'margin 'left-margin) putstring)))
      (put-text-property 0 (length gdb-string) 'display prop gdb-string)
      (overlay-put overlay 'put-break t)
      (overlay-put overlay 'before-string gdb-string))))

(defun gdb-remove-strings (start end &optional buffer)
  "Remove strings between START and END in BUFFER.
Remove only strings that were put in BUFFER with calls to `put-string'.
BUFFER nil or omitted means use the current buffer."
  (unless buffer
    (setq buffer (current-buffer)))
  (let ((overlays (overlays-in start end)))
    (while overlays
      (let ((overlay (car overlays)))
	(when (overlay-get overlay 'put-break)
	  (delete-overlay overlay)))
      (setq overlays (cdr overlays)))))

(defconst breakpoint-xpm-data "/* XPM */
static char *magick[] = {
/* columns rows colors chars-per-pixel */
\"12 12 2 1\",
\"  c red\",
\"+ c None\",
/* pixels */
\"+++      +++\",
\"++        ++\",
\"+          +\",
\"+          +\",
\"+          +\",
\"+          +\",
\"+          +\",
\"+          +\",
\"++        ++\",
\"+++      +++\",
  "XPM data used for breakpoint icon.")

(defconst breakpoint-enabled-pbm-data
12 12\",
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 1 1 0 0 0
0 0 1 1 1 1 1 1 1 1 0 0
0 1 1 1 1 1 1 1 1 1 1 0
0 1 1 1 1 1 1 1 1 1 1 0
0 1 1 1 1 1 1 1 1 1 1 0
0 1 1 1 1 1 1 1 1 1 1 0
0 1 1 1 1 1 1 1 1 1 1 0
0 1 1 1 1 1 1 1 1 1 1 0
0 0 1 1 1 1 1 1 1 1 0 0
0 0 0 1 1 1 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0"
  "PBM data used for enabled breakpoint icon.")

(defvar breakpoint-enabled-icon
  (find-image `((:type xpm :data ,breakpoint-xpm-data)
		(:type pbm :data ,breakpoint-enabled-pbm-data)))
  "Icon for enabled breakpoint in display margin")

;; ======================================================================
;; pdb (Python debugger) functions

;; History of argument lists passed to pdb.
(defvar gud-pdb-history nil)

;; Last group is for return value, e.g. "> test.py(2)foo()->None"
;; Either file or function name may be omitted: "> <string>(0)?()"
(defvar gud-pdb-marker-regexp
  "^> \\([-a-zA-Z0-9_/.:\\]*\\|<string>\\)(\\([0-9]+\\))\\([a-zA-Z0-9_]*\\|\\?\\)()\\(->[^\n]*\\)?\n")
(defvar gud-pdb-marker-regexp-file-group 1)
(defvar gud-pdb-marker-regexp-line-group 2)
(defvar gud-pdb-marker-regexp-fnname-group 3)

(defvar gud-pdb-marker-regexp-start "^> ")

(defvar gud-pdb-marker-regexp-breakpoint 
  "reakpoint [0-9]+ at \\(\\([a-zA-Z]:\\)?[^:\n]*\\):\\([0-9]*\\)\n")

;; There's no guarantee that Emacs will hand the filter the entire
;; marker at once; it could be broken up across several strings.  We
;; might even receive a big chunk with several markers in it.  If we
;; receive a chunk of text which looks like it might contain the
;; beginning of a marker, we save it here between calls to the
;; filter.
(defun gud-pdb-marker-filter (string)
  (setq gud-marker-acc (concat gud-marker-acc string))
  (let ((output ""))

    ;; Process all the complete markers in this chunk.
    (while (string-match gud-pdb-marker-regexp gud-marker-acc)

       ;; Extract the frame position from the marker.
       (let ((file (match-string gud-pdb-marker-regexp-file-group
	     (line (string-to-int
		    (match-string gud-pdb-marker-regexp-line-group
	 (if (string-equal file "<string>")
	   (cons file line)))

       ;; Output everything instead of the below
       output (concat output (substring gud-marker-acc 0 (match-end 0)))
;;	  ;; Append any text before the marker to the output we're going
;;	  ;; to return - we don't include the marker in this text.
;;	  output (concat output
;;		      (substring gud-marker-acc 0 (match-beginning 0)))
       ;; Set the accumulator to the remaining text.
       gud-marker-acc (substring gud-marker-acc (match-end 0))))

    (if (string-match (concat "B" gud-pdb-marker-regexp-breakpoint)
	(let ((file (match-string 1 gud-marker-acc))
	      (line (match-string 3 gud-marker-acc)))
	  (gud-pdb-insert-breakpoint file line)))	 
    (if (string-match (concat "Deleted b" gud-pdb-marker-regexp-breakpoint)
	(let ((file (match-string 1 gud-marker-acc))
	      (line (match-string 3 gud-marker-acc)))
	  (gud-pdb-remove-breakpoint file line)))	 

    ;; Does the remaining text look like it might end with the
    ;; beginning of another marker?  If it does, then keep it in
    ;; gud-marker-acc until we receive the rest of it.	Since we
    ;; know the full marker regexp above failed, it's pretty simple to
    ;; test for marker starts.
    (if (string-match gud-pdb-marker-regexp-start gud-marker-acc)
	  ;; Everything before the potential marker start can be output.
	  (setq output (concat output (substring gud-marker-acc
						 0 (match-beginning 0))))

	  ;; Everything after, we save, to combine with later input.
	  (setq gud-marker-acc
		(substring gud-marker-acc (match-beginning 0))))

      (setq output (concat output gud-marker-acc)
	    gud-marker-acc ""))


(defun gud-pdb-insert-breakpoint (file line)
  (with-current-buffer (find-file-noselect file)
      (set (make-local-variable 'gud-minor-mode) 'pdb)
      (set (make-local-variable 'tool-bar-map) gud-tool-bar-map)
      (setq left-margin-width 2)
      (if (get-buffer-window (current-buffer))
	  (set-window-margins (get-buffer-window (current-buffer))
			      left-margin-width right-margin-width)))
      (goto-line (string-to-number line))
      (let ((start (progn (beginning-of-line)
			  (- (point) 1)))
	    (end (progn (end-of-line) (+ (point) 1))))
	(if (display-images-p)
	      (remove-images start end)
	      (put-image breakpoint-enabled-icon
			 (+ start 1)
			 "breakpoint icon enabled"
	  (gdb-remove-strings start end)
	      (gdb-put-string "B" (+ start 1)))))))

(defun gud-pdb-remove-breakpoint (file line)
  (with-current-buffer (find-file-noselect file)
      (goto-line (string-to-number line))
      (let ((start (progn (beginning-of-line)
			  (- (point) 1)))
	    (end (progn (end-of-line) (+ (point) 1))))
	(if (display-images-p)
	    (remove-images start end)
	  (gdb-remove-strings start end))))))

(defcustom gud-pdb-command-name "pdb"
  "File name for executing the Python debugger.
This should be an executable on your path, or an absolute file name."
  :type 'string
  :group 'gud)

(defun pdb (command-line)
  "Run pdb on program FILE in buffer `*gud-FILE*'.
The directory containing FILE becomes the initial working directory
and source-file directory for your debugger."
   (list (gud-query-cmdline 'pdb)))

  (gud-common-init command-line nil 'gud-pdb-marker-filter)
  (set (make-local-variable 'gud-minor-mode) 'pdb)

  (gud-def gud-break  "break %l"     "\C-b" "Set breakpoint at current line.")
  (gud-def gud-remove "clear %f:%l"  "\C-d" "Remove breakpoint at current line")
  (gud-def gud-step   "step"         "\C-s" "Step one source line with display.")
  (gud-def gud-next   "next"         "\C-n" "Step one line (skip functions).")
  (gud-def gud-cont   "continue"     "\C-r" "Continue with display.")
  (gud-def gud-finish "return"       "\C-f" "Finish executing current function.")
  (gud-def gud-up     "up"           "<" "Up one stack frame.")
  (gud-def gud-down   "down"         ">" "Down one stack frame.")
  (gud-def gud-print  "p %e"         "\C-p" "Evaluate Python expression at point.")
  ;; Is this right?
  (gud-def gud-statement "! %e"      "\C-e" "Execute Python statement at point.")

  ;; (setq comint-prompt-regexp "^(.*pdb[+]?) *")
  (setq comint-prompt-regexp "^(Pdb) *")
  (setq paragraph-start comint-prompt-regexp)
  (run-hooks 'pdb-mode-hook))

More information about the Python-Dev mailing list