├── .gitignore ├── sldb-source-eval.gif ├── sldb-source-eval.png ├── sldb-show-frame-local.gif ├── slime-breakpoints.lisp ├── README.md ├── sldb-show-frame-local.el ├── breakpoints.lisp ├── sldb-source-eval.el └── slime-breakpoints.el /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.fasl -------------------------------------------------------------------------------- /sldb-source-eval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontone/slime-breakpoints/HEAD/sldb-source-eval.gif -------------------------------------------------------------------------------- /sldb-source-eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontone/slime-breakpoints/HEAD/sldb-source-eval.png -------------------------------------------------------------------------------- /sldb-show-frame-local.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontone/slime-breakpoints/HEAD/sldb-show-frame-local.gif -------------------------------------------------------------------------------- /slime-breakpoints.lisp: -------------------------------------------------------------------------------- 1 | (require :breakpoints (merge-pathnames "breakpoints.lisp" *load-pathname*)) 2 | 3 | (defpackage :slime-breakpoints 4 | (:use :cl :breakpoints) 5 | (:export #:list-of-breakpoints)) 6 | 7 | (in-package :slime-breakpoints) 8 | 9 | (defun list-of-breakpoints () 10 | (loop for breakpoint-name being the hash-keys of breakpoints:*breakpoints* 11 | using (hash-value breakpoint) 12 | collect (list :name breakpoint-name 13 | :type (getf breakpoint :type) 14 | :enabled (breakpoints:breakpoint-installed-p breakpoint-name)))) 15 | 16 | (provide :slime-breakpoints) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SLIME-BREAKPOINTS 2 | 3 | SLIME extension for setting up breakpoints from Emacs. 4 | 5 | ## Install 6 | 7 | ℹ️ Please consider using [SLIME :star:](https://github.com/mmontone/slime-star), that comes with this extension preinstalled. 8 | 9 | Load `swank` and add this repository path to `swank::*load-path*`, in your Lisp compiler init file (~/.sbclrc if using SBCL): 10 | 11 | ```lisp 12 | (require :swank) 13 | (push #p"~/slime-breakpoints/" swank::*load-path*) 14 | ``` 15 | 16 | In Emacs, add this repository path to `load-path` and `slime-breakpoints` to `slime-contribs` in `~/.emacs` init file, like: 17 | 18 | ``` 19 | (push "~/slime-breakpoints" load-path) 20 | 21 | (setq slime-contribs '(slime-fancy slime-breakpoints)) 22 | 23 | (slime-setup) 24 | ``` 25 | 26 | ## Use 27 | 28 | ### Breakpoints 29 | 30 | - **slime-break-on-entry** *C-c b RET* - Install breakpoint on FUNCTION-NAME. 31 | - **slime-list-breakpoints** *C-c b l* - Open a buffer that list the current installed breakpoints. 32 | - **slime-remove-all-breakpoints** *C-c b q* - Remove all breakpoints. 33 | - **slime-remove-breakpoint** *C-c b * - Remove breakpoint on FUNCTION-NAME. 34 | - **slime-toggle-breakpoint** *C-c b SPC* - Toggle breakpoint on FUNCTION-NAME. 35 | - **slime-break-with-last-expression** - Compile function at point with a BREAK at last expression position. 36 | 37 | ### Tracing 38 | - **slime-trace-last-expression** - Compile function at point with a 'trace' expression at last expression position. 39 | 40 | ### Stepping 41 | 42 | - **slime-step-in-last-expression** - Compile function at point with a 'step' expression at last expression position. 43 | - **slime-step-in-next-expression** - Compile function at point with a 'step' expression at next expression position. 44 | 45 | ### Debug printing 46 | 47 | These commands require [cl-debug-print](https://github.com/masatoi/cl-debug-print) installed (it is available via Quicklisp). 48 | 49 | - **slime-debug-print-next-expression** - Instrument next expression to be debug printed when evaluated. 50 | - **slime-debug-print-last-expression** - Instrument last expression to be debug printed when evaluated. 51 | 52 | ## Common Lisp api 53 | 54 | You can also use from Common Lisp directly. The `breakpoints` package exports this functions: 55 | 56 | ### break-on-entry 57 | 58 | ```lisp 59 | (function-name) 60 | ``` 61 | 62 | Setup a breakpoint on entry on FUNCTION-NAME. 63 | 64 | ### breakpoint-installed-p 65 | 66 | ```lisp 67 | (function-name) 68 | ``` 69 | 70 | Wether a breakpoint is installed on FUNCTION-NAME. 71 | 72 | ### reinstall-all-breakpoints 73 | 74 | ```lisp 75 | nil 76 | ``` 77 | 78 | Reinstall all breakpoints. 79 | 80 | When a function is recompiled, the breakpoint is lost. A call to this function reintalls all breakpoints. 81 | 82 | ### reinstall-breakpoint 83 | 84 | ```lisp 85 | (function-name) 86 | ``` 87 | 88 | Reinstall breakpoint on FUNCTION-NAME. 89 | 90 | When a function is recompiled, the breakpoint is lost. A call to this function reinstalls the breakpoint. 91 | 92 | ### remove-all-breakpoints 93 | 94 | ```lisp 95 | nil 96 | ``` 97 | 98 | Remove all installed breakpoints. 99 | 100 | ### remove-breakpoint 101 | 102 | ```lisp 103 | (function-name) 104 | ``` 105 | 106 | Remove breakpoint on FUNCTION-NAME. 107 | 108 | ### toggle-breakpoint 109 | 110 | ```lisp 111 | (function-name) 112 | ``` 113 | 114 | Toggle breakpoint on FUNCTION-NAME. 115 | 116 | # SLDB-SOURCE-EVAL 117 | 118 | SLIME debugger (SLDB) extension that adds debugger context based evaluation directly from Lisp source buffers. 119 | 120 | ![sldb-source-eval](sldb-source-eval.png) 121 | ![sldb-source-eval](sldb-source-eval.gif) 122 | 123 | When SLIME debugger (SLDB) opens, move cursor to a backtrace frame, and press letter `v' for navigating to the frame source. 124 | Use C-x C-e to evaluate expressions in the source buffer using the backtrace frame as context. 125 | 126 | # SLDB-SHOW-FRAME-LOCAL 127 | 128 | This is a prototype. 129 | 130 | Show backtrace frames locals when the ursor over source code. 131 | 132 | ![sldb-show-frame-local](sldb-show-frame-local.gif) 133 | 134 | ## Setup 135 | 136 | ```lisp 137 | (require 'sldb-show-frame-local) 138 | (sldb-show-frame-local-on-cursor-move) 139 | ``` 140 | -------------------------------------------------------------------------------- /sldb-show-frame-local.el: -------------------------------------------------------------------------------- 1 | ;;; slime-show-frame-local.el --- Navigation to backtrace locals from source code in SLIME. -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2023 Mariano Montone 4 | 5 | ;; Author: Mariano Montone 6 | ;; Keywords: lisp, tools 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; Navigation to backtrace locals from source code in SLIME. 24 | 25 | ;;; Code: 26 | 27 | (require 'slime) 28 | (require 'hl-line) 29 | 30 | (defun slime-maybe-select-sldb-buffer () 31 | "Select the SLDB buffer to work with." 32 | (let ((sldb-buffers (sldb-buffers))) 33 | (cl-case (length sldb-buffers) 34 | (0 nil) 35 | (1 (car sldb-buffers)) 36 | (t (completing-read "SLDB buffer: " sldb-buffers))))) 37 | 38 | (defun slime-qualified-symbol-at-point () 39 | (interactive) 40 | (let ((symbol-at-point (slime-symbol-at-point))) 41 | (when symbol-at-point 42 | (slime-qualify-cl-symbol-name symbol-at-point)))) 43 | 44 | (defun sldb-show-frame-local (symbol) 45 | "Show backtrace local for SYMBOL. 46 | 47 | Look for the local in sldb error buffer backtrace frames. 48 | Then show the frame details and highlight the line with the local." 49 | (interactive (list (slime-read-symbol-name "Navigate to backtrace local: "))) 50 | (let ((current-buffer (current-buffer)) 51 | (sldb-buffer (slime-maybe-select-sldb-buffer)) 52 | (original-pos nil) 53 | (local-found-p nil)) 54 | (when sldb-buffer 55 | (switch-to-buffer-other-window sldb-buffer) 56 | (setq original-pos (point)) 57 | (hl-line-mode) 58 | (sldb-hide-all-frame-details) 59 | (sldb-beginning-of-backtrace) 60 | (cl-block loop 61 | (while (get-text-property (point) 'frame) 62 | ;; frame-details has format: (START END FRAME LOCALS CATCHES) 63 | (cl-destructuring-bind (_start _end _frame locals _catches) 64 | (sldb-frame-details) 65 | (let ((lines 2)) 66 | (dolist (local locals) 67 | (when (cl-equalp (cl-getf local :name) symbol) 68 | ;; Local found 69 | (setq local-found-p t) 70 | (let ((inhibit-read-only t) 71 | (inhibit-point-motion-hooks t)) 72 | ;; Show the frame details and goto the line of the local 73 | (sldb-show-frame-details) 74 | (forward-line lines) 75 | ;; Highlight the line 76 | (hl-line-highlight) 77 | ;; Echo the value of the local 78 | (message (cl-getf local :value)) 79 | ;; Try to prevent the message being overwritten by other 80 | ;; processes (eldoc) for 2 seconds. 81 | (sit-for 2) 82 | ;; Exit the loop 83 | (cl-return-from loop))) 84 | (cl-incf lines)))) 85 | ;; Try with next frame 86 | (sldb-forward-frame))) 87 | ;; Restore position in buffer unless local was found 88 | (unless local-found-p 89 | (goto-char original-pos)) 90 | (switch-to-buffer-other-window current-buffer)))) 91 | 92 | (defun sldb-hide-all-frame-details () 93 | "Hide details of all frames." 94 | (interactive) 95 | (let ((inhibit-read-only t) 96 | (inhibit-point-motion-hooks t)) 97 | (sldb-beginning-of-backtrace) 98 | (while (get-text-property (point) 'frame) 99 | (sldb-hide-frame-details) 100 | (sldb-forward-frame)))) 101 | 102 | (defun sldb-show-all-frames-details () 103 | "Show details of all frames." 104 | (interactive) 105 | (let ((inhibit-read-only t) 106 | (inhibit-point-motion-hooks t)) 107 | (sldb-beginning-of-backtrace) 108 | (while (get-text-property (point) 'frame) 109 | (sldb-show-frame-details) 110 | (sldb-forward-frame)))) 111 | 112 | ;; Hook for navigating to current symbol-at-point 113 | 114 | (defvar sldb-show-frame-local--last-symbol nil 115 | "The last symbol visited.") 116 | 117 | (defun sldb-show-frame-local--post-command () 118 | "Function to run as post-command, that shows local-at-point in sldb error buffer." 119 | (when (and slime-mode 120 | (slime-connected-p) 121 | (sldb-buffers)) 122 | (let ((symbol-at-point (slime-symbol-at-point))) 123 | (when (and symbol-at-point 124 | (not (string= symbol-at-point sldb-show-frame-local--last-symbol))) 125 | (setq sldb-show-frame-local--last-symbol symbol-at-point) 126 | (sldb-show-frame-local symbol-at-point))))) 127 | 128 | (defun sldb-show-frame-local-on-cursor-move () 129 | "Setup sldb-show-frame-local when cursor moves." 130 | (add-to-list 'slime-mode-hook 131 | (lambda () 132 | (add-to-list 'post-command-hook #'sldb-show-frame-local--post-command)))) 133 | 134 | (provide 'sldb-show-frame-local) 135 | ;;; sldb-show-frame-local.el ends here 136 | -------------------------------------------------------------------------------- /breakpoints.lisp: -------------------------------------------------------------------------------- 1 | (defpackage :breakpoints 2 | (:use :cl) 3 | (:export 4 | #:break-on-entry 5 | #:step-on-entry 6 | #:toggle-breakpoint 7 | #:remove-breakpoint 8 | #:remove-all-breakpoints 9 | #:reinstall-breakpoint 10 | #:reinstall-all-breakpoints 11 | #:disable-breakpoint 12 | #:disable-all-breakpoints 13 | #:breakpoint-installed-p 14 | #:find-breakpoint 15 | #:*breakpoints*)) 16 | 17 | (in-package :breakpoints) 18 | 19 | (defvar *breakpoints* (make-hash-table)) 20 | (defvar *save-definitions* t 21 | "When enabled, the original functions definitions is saved in the breakpoint, to give the SLIME interface the possibility to follow the correct source locations of the function with a breakpoint installed. 22 | Making this optional, since it can have a performance penalty when installing breakpionts.") 23 | 24 | (defun find-breakpoint (function-name) 25 | (check-type function-name symbol) 26 | (gethash function-name *breakpoints*)) 27 | 28 | (defun breakpoint-installed-p (function-name) 29 | "Wether a breakpoint is installed on FUNCTION-NAME." 30 | (check-type function-name symbol) 31 | (let ((breakpoint (gethash function-name *breakpoints*))) 32 | (when breakpoint 33 | (destructuring-bind (&key type replaced break definitions) breakpoint 34 | (declare (ignore type replaced definitions)) 35 | (when (eq (symbol-function function-name) break) 36 | (return-from breakpoint-installed-p t)))))) 37 | 38 | (defun break-on-entry (function-name) 39 | "Setup a breakpoint on entry on FUNCTION-NAME." 40 | (check-type function-name symbol) 41 | 42 | ;; First remove any breakpoints installed on FUNCTION-NAME, if any 43 | (remove-breakpoint function-name) 44 | 45 | (let* ((definitions (when *save-definitions* 46 | (swank:find-definitions-for-emacs (prin1-to-string function-name)))) 47 | (original-function (symbol-function function-name)) 48 | (function-with-break 49 | (lambda (&rest args) 50 | (break) 51 | (apply original-function args)))) 52 | (setf (symbol-function function-name) function-with-break) 53 | (setf (gethash function-name *breakpoints*) 54 | (list :type :break-on-entry 55 | :replaced original-function 56 | :break function-with-break 57 | :definitions definitions))) 58 | t) 59 | 60 | (defun step-on-entry (function-name) 61 | "Start stepping when function named FUNCTION-NAME is invoked." 62 | (check-type function-name symbol) 63 | 64 | ;; First remove any breakpoints installed on FUNCTION-NAME, if any 65 | (remove-breakpoint function-name) 66 | 67 | (let* ((definitions (when *save-definitions* 68 | (swank:find-definitions-for-emacs (prin1-to-string function-name)))) 69 | (original-function (symbol-function function-name)) 70 | (function-with-step 71 | (lambda (&rest args) 72 | (step 73 | (apply original-function args))))) 74 | (setf (symbol-function function-name) function-with-step) 75 | (setf (gethash function-name *breakpoints*) 76 | (list :type :step-on-entry 77 | :replaced original-function 78 | :break function-with-step 79 | :definitions definitions))) 80 | t) 81 | 82 | (defun remove-breakpoint (function-name) 83 | "Remove breakpoint on FUNCTION-NAME." 84 | (check-type function-name symbol) 85 | 86 | (when (not (breakpoint-installed-p function-name)) 87 | (return-from remove-breakpoint nil)) 88 | 89 | (let ((breakpoint (gethash function-name *breakpoints*))) 90 | (destructuring-bind (&key type replaced break definitions) breakpoint 91 | (declare (ignore type definitions)) 92 | (when (eq (symbol-function function-name) break) 93 | (setf (symbol-function function-name) replaced))) 94 | (remhash function-name *breakpoints*) 95 | t)) 96 | 97 | (defun disable-breakpoint (function-name) 98 | "Disable breakpoint on FUNCTION-NAME. 99 | The breakpoint remains in the list of breakpoints." 100 | (check-type function-name symbol) 101 | 102 | (when (not (breakpoint-installed-p function-name)) 103 | (return-from disable-breakpoint nil)) 104 | 105 | (let ((breakpoint (gethash function-name *breakpoints*))) 106 | (destructuring-bind (&key type replaced break definitions) breakpoint 107 | (declare (ignore type definitions)) 108 | (when (eq (symbol-function function-name) break) 109 | (setf (symbol-function function-name) replaced))) 110 | t)) 111 | 112 | (defun toggle-breakpoint (function-name) 113 | "Toggle breakpoint on FUNCTION-NAME." 114 | (check-type function-name symbol) 115 | (if (breakpoint-installed-p function-name) 116 | (progn 117 | (remove-breakpoint function-name) 118 | nil) 119 | (progn 120 | (break-on-entry function-name) 121 | t))) 122 | 123 | (defun remove-all-breakpoints () 124 | "Remove all installed breakpoints." 125 | (loop for k being each hash-key of *breakpoints* 126 | do (remove-breakpoint k)) 127 | (setf *breakpoints* (make-hash-table)) 128 | t) 129 | 130 | (defun disable-all-breakpoints () 131 | "Disable all installed breakpoints." 132 | (loop for k being each hash-key of *breakpoints* 133 | do (disable-breakpoint k)) 134 | t) 135 | 136 | (defun reinstall-breakpoint (function-name) 137 | "Reinstall breakpoint on FUNCTION-NAME. 138 | 139 | When a function is recompiled, the breakpoint is lost. A call to this function reinstalls the breakpoint." 140 | (let ((breakpoint (gethash function-name *breakpoints*))) 141 | (when breakpoint 142 | (let ((break (getf breakpoint :break))) 143 | (when (not (eq (symbol-function function-name) break)) 144 | (break-on-entry function-name)))))) 145 | 146 | (defun reinstall-all-breakpoints () 147 | "Reinstall all breakpoints. 148 | 149 | When a function is recompiled, the breakpoint is lost. A call to this function reintalls all breakpoints." 150 | (loop for k being each hash-key of *breakpoints* 151 | do (reinstall-breakpoint k))) 152 | 153 | (provide :breakpoints) 154 | -------------------------------------------------------------------------------- /sldb-source-eval.el: -------------------------------------------------------------------------------- 1 | ;;; sldb-source-eval.el --- SLIME debugger (SLDB) extension that adds debugger context based evaluation directly from Lisp source buffers. -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2023 Mariano Montone 4 | 5 | ;; Author: Mariano Montone 6 | ;; Version: 0.1 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; SLIME debugger (SLDB) extension that adds debugger context based evaluation 24 | ;; directly from Lisp source buffers. 25 | 26 | ;; Usage: 27 | 28 | ;; When SLIME debugger (SLDB) opens, move cursor to a backtrace frame, 29 | ;; and press letter `v' for navigating to the frame source. 30 | ;; Use C-x C-e to evaluate expressions in the source buffer using the backtrace frame as context. 31 | 32 | ;;; Code: 33 | 34 | (require 'slime) 35 | 36 | (defvar-local sldb-source-eval-frame-number nil 37 | "The SLDB frame number attached to the current buffer.") 38 | (defvar-local sldb-source-eval-mode-enabled nil 39 | "Use internally to indicate that SLDB-SOURCE-EVAL-MODE is enabled in the local buffer.") 40 | 41 | (defun sldb-source-eval-show-frame-source (frame-number) 42 | "Show source for FRAME-NUMBER." 43 | ;; keep slime-current-thread for later assignment 44 | (let ((current-thread slime-current-thread)) 45 | (slime-eval-async 46 | `(swank:frame-source-location ,frame-number) 47 | (lambda (source-location) 48 | (slime-dcase source-location 49 | ((:error message) 50 | (message "%s" message) 51 | (ding)) 52 | (t 53 | (slime-show-source-location source-location t nil) 54 | ;; slime-show-source-location sets the buffer with the source to current-buffer 55 | (setq-local sldb-source-eval-frame-number frame-number) 56 | ;; assign the current-thread to new buffer 57 | (setq-local slime-current-thread current-thread) 58 | ;; enable sldb-source-eval-mode 59 | (sldb-source-eval-mode 1))))))) 60 | 61 | (defun sldb-source-eval-goto-frame-source (frame-number) 62 | "Show source for FRAME-NUMBER." 63 | ;; keep slime-current-thread for later assignment 64 | (let ((current-thread slime-current-thread)) 65 | (slime-eval-async 66 | `(swank:frame-source-location ,frame-number) 67 | (lambda (source-location) 68 | (slime-dcase source-location 69 | ((:error message) 70 | (message "%s" message) 71 | (ding)) 72 | (t 73 | (slime-show-source-location source-location t nil) 74 | ;; slime-show-source-location sets the buffer with the source to current-buffer 75 | (setq-local sldb-source-eval-frame-number frame-number) 76 | ;; assign the current-thread to new buffer 77 | (setq-local slime-current-thread current-thread) 78 | ;; enabl sldb-source-eval-mode 79 | (sldb-source-eval-mode 1) 80 | ;; navigate to the buffer 81 | (switch-to-buffer-other-window (current-buffer)) 82 | )))))) 83 | 84 | (defun sldb-source-eval-eval-last-expression () 85 | "Prompt for an expression and evaluate it in the selected frame." 86 | (interactive) 87 | (unless (numberp slime-current-thread) 88 | (user-error "Run this command after selecting a frame from a backtrace, using `sldb-show-source' (bound to ?v by default).")) 89 | (let ((package (slime-current-package)) 90 | (expr (slime-last-expression)) 91 | (frame sldb-source-eval-frame-number)) 92 | (slime-eval-async `(swank:eval-string-in-frame ,expr ,frame ,(upcase package)) 93 | (if current-prefix-arg 94 | 'slime-write-string 95 | 'slime-display-eval-result)))) 96 | 97 | (defun sldb-source-eval-inspect (string) 98 | "Prompt for an expression and inspect it in the current debugger frame." 99 | (interactive (list (slime-read-from-minibuffer 100 | "Inspect in frame (evaluated): " 101 | (slime-sexp-at-point)))) 102 | (slime-eval-async `(swank:inspect-in-frame ,string ,sldb-source-eval-frame-number) 103 | 'slime-open-inspector)) 104 | 105 | (defun sldb-source-eval-show-source () 106 | "Highlight the frame at point's expression in a source code buffer." 107 | (interactive) 108 | (sldb-source-eval-show-frame-source (sldb-frame-number-at-point))) 109 | 110 | (defun sldb-source-eval-goto-source () 111 | "Highlight the frame at point's expression in a source code buffer." 112 | (interactive) 113 | (sldb-source-eval-goto-frame-source (sldb-frame-number-at-point))) 114 | 115 | (defvar sldb-source-eval-mode-map 116 | (let ((map (make-keymap))) 117 | (define-key map (kbd "C-x C-e") 'sldb-source-eval-eval-last-expression) 118 | (define-key map (kbd "C-c I") 'sldb-source-eval-inspect) 119 | (define-key map (kbd "C-c C-r") 'sldb-source-eval-eval-region) 120 | map)) 121 | 122 | (define-minor-mode sldb-source-eval-mode 123 | "SLDB evaluation minor mode." 124 | :lighter " sldb-source-eval" 125 | :keymap sldb-source-eval-mode-map 126 | (setq-local sldb-source-eval-mode-enabled t)) 127 | 128 | (defun sldb-source-eval-quit (&optional _args) 129 | "Disable sldb-source-eval mode in all buffers." 130 | (interactive) 131 | (dolist (buffer (buffer-list)) 132 | (with-current-buffer buffer 133 | (when sldb-source-eval-mode-enabled 134 | (sldb-source-eval-mode -1) 135 | (setq-local sldb-source-eval-mode-enabled nil) 136 | (setq-local slime-current-thread t))))) 137 | 138 | ;; When user quits sldb buffer, disable sldb-source-eval mode from all buffers. 139 | (advice-add 'sldb-quit :after #'sldb-source-eval-quit) 140 | (advice-add 'sldb-invoke-restart :after #'sldb-source-eval-quit) 141 | (advice-add 'sldb-abort :after #'sldb-source-eval-quit) 142 | (advice-add 'sldb-continue :after #'sldb-source-eval-quit) 143 | 144 | ;; Replace default sldb-mode show-source behaviour. 145 | ;; Redirect to a buffer with sldb-source-eval mode enabled. 146 | (advice-add 'sldb-show-source :override 'sldb-source-eval-show-source) 147 | 148 | (define-slime-contrib sldb-source-eval 149 | "SLIME debugger (SLDB) extension that adds debugger context based evaluation directly from Lisp source buffers." 150 | (:authors "Mariano Montone") 151 | (:license "GPL")) 152 | 153 | (provide 'sldb-source-eval) 154 | 155 | ;;; sldb-source-eval.el ends here 156 | -------------------------------------------------------------------------------- /slime-breakpoints.el: -------------------------------------------------------------------------------- 1 | ;;; slime-breakpoints.el --- Setup breaks on functions from Emacs/SLIME -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2022 Mariano Montone 4 | 5 | ;; Author: Mariano Montone 6 | ;; URL: https://github.com/mmontone/slime-breakpoints 7 | ;; Keywords: help, lisp, slime, common-lisp 8 | ;; Version: 0.1 9 | ;; Package-Requires: ((emacs "25")) 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; slime-breakpoints is a SLIME extension that lets you setup breaks on functions and manage them. 27 | 28 | ;;; Code: 29 | 30 | (require 'slime) 31 | 32 | (defgroup slime-breakpoints nil 33 | "SLIME breakpoints." 34 | :group 'slime) 35 | 36 | (defcustom slime-breakpoints-display-fringe-indicators nil 37 | "When enabled, display indicators for break on the Emacs fringe." 38 | :type 'boolean 39 | :group 'slime-breakpoints) 40 | 41 | (defvar slime-breakpoints--fringe-indicators (make-hash-table :test 'equal)) 42 | 43 | (defun slime-breakpoints--display-break-indicator (function-name) 44 | "Display indicator on the fringe for break on FUNCTION-NAME." 45 | (let ((break-indicator (make-overlay (point) (point)))) 46 | (overlay-put break-indicator 47 | 'before-string 48 | (propertize "x" 'display 49 | (list 'left-fringe 50 | 'filled-square 'error))) 51 | (puthash function-name break-indicator slime-breakpoints--fringe-indicators))) 52 | 53 | (defun slime-breakpoints--delete-break-indicator (function-name) 54 | "Remove display of break indicator on FUNCTION-NAME." 55 | (let ((break-indicator (gethash function-name slime-breakpoints--fringe-indicators))) 56 | (when break-indicator 57 | (remhash function-name slime-breakpoints--fringe-indicators) 58 | (delete-overlay break-indicator)))) 59 | 60 | (defun slime-breakpoints--set-fringe-indicator (function-name enable) 61 | "Toggle fringe indicator on FUNCTION-NAME following ENABLE." 62 | (when slime-breakpoints-display-fringe-indicators 63 | (slime-edit-definition function-name) 64 | (if enable 65 | (slime-breakpoints--display-break-indicator function-name) 66 | (slime-breakpoints--delete-break-indicator function-name)))) 67 | 68 | (defun slime-breakpoints--delete-all-fringe-indicators () 69 | "Delete all fringe indicators." 70 | (dolist (overlay (hash-table-values slime-breakpoints--fringe-indicators)) 71 | (delete-overlay overlay)) 72 | (setq slime-breakpoints--fringe-indicators (make-hash-table :test 'equal))) 73 | 74 | (defun slime-break-on-entry (function-name) 75 | "Install breakpoint on FUNCTION-NAME." 76 | (interactive (list (slime-read-symbol-name "Break on entry: "))) 77 | (when (not function-name) 78 | (error "No function name given")) 79 | (slime-remove-breakpoint function-name) 80 | (slime-breakpoints--set-fringe-indicator function-name t) 81 | (slime-eval `(breakpoints:break-on-entry (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 82 | (message "Breakpoint installed on %s entry" function-name) 83 | (slime-breakpoints--refresh-breakpoints-buffer)) 84 | 85 | (defun slime-step-on-entry (function-name) 86 | "Start stepping when FUNCTION-NAME is called." 87 | (interactive (list (slime-read-symbol-name "Step on entry: "))) 88 | (when (not function-name) 89 | (error "No function name given")) 90 | (slime-remove-breakpoint function-name) 91 | (slime-breakpoints--set-fringe-indicator function-name t) 92 | (slime-eval `(breakpoints:step-on-entry (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 93 | (message "Breakpoint installed on %s entry" function-name) 94 | (slime-breakpoints--refresh-breakpoints-buffer)) 95 | 96 | (defun slime-toggle-breakpoint (function-name) 97 | "Toggle breakpoint on FUNCTION-NAME." 98 | (interactive (list (slime-read-symbol-name "Toggle breakpoint: "))) 99 | (when (not function-name) 100 | (error "No function name given")) 101 | (let ((installed (slime-eval `(breakpoints:breakpoint-installed-p (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))))) 102 | (when (not installed) 103 | (slime-breakpoints--set-fringe-indicator function-name t)) 104 | (slime-eval `(breakpoints:toggle-breakpoint (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 105 | (when installed 106 | (slime-breakpoints--set-fringe-indicator function-name nil)) 107 | (if installed 108 | (message "Breakpoint removed from %s" function-name) 109 | (message "Breakpoint installed on %s entry" function-name)) 110 | (slime-breakpoints--refresh-breakpoints-buffer))) 111 | 112 | (defun slime-remove-breakpoint (function-name) 113 | "Remove breakpoint on FUNCTION-NAME." 114 | (interactive (list (slime-read-symbol-name "Remove breakpoint: "))) 115 | (when (not function-name) 116 | (error "No function name given")) 117 | (slime-eval `(breakpoints:remove-breakpoint (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 118 | (message "Breakpoint removed") 119 | (slime-breakpoints--delete-break-indicator function-name) 120 | (slime-breakpoints--refresh-breakpoints-buffer)) 121 | 122 | (defun slime-remove-all-breakpoints () 123 | "Remove all breakpoints." 124 | (interactive) 125 | (slime-eval '(breakpoints:remove-all-breakpoints)) 126 | (slime-breakpoints--delete-all-fringe-indicators) 127 | (message "All breakpoints removed.") 128 | (slime-breakpoints--refresh-breakpoints-buffer)) 129 | 130 | (defun slime-disable-breakpoint (function-name) 131 | "Disable breakpoint on FUNCTION-NAME. 132 | The breakpoint remains in the list of breakpoints." 133 | (interactive (list (slime-read-symbol-name "Disable breakpoint: "))) 134 | (when (not function-name) 135 | (error "No function name given")) 136 | (slime-eval `(breakpoints:disable-breakpoint (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 137 | (message "Breakpoint disabled") 138 | (slime-breakpoints--refresh-breakpoints-buffer)) 139 | 140 | (defun slime-disable-all-breakpoints () 141 | "Disable all breakpoints." 142 | (interactive) 143 | (slime-eval '(breakpoints:disable-all-breakpoints)) 144 | (message "All breakpoints disabled.") 145 | (slime-breakpoints--delete-all-fringe-indicators) 146 | (slime-breakpoints--refresh-breakpoints-buffer)) 147 | 148 | (defun slime-reinstall-breakpoint (function-name) 149 | "Reinstall breakpoint on FUNCTION-NAME." 150 | (interactive (list (slime-read-symbol-name "Reinstall breakpoint: "))) 151 | (when (not function-name) 152 | (error "No function name given")) 153 | (slime-eval `(breakpoints:reinstall-breakpoint (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name)))) 154 | (message "Breakpoint reinstalled") 155 | (slime-breakpoints--refresh-breakpoints-buffer)) 156 | 157 | (defun slime-reinstall-all-breakpoints () 158 | "Reinstall all breakpoints." 159 | (interactive) 160 | (slime-eval '(breakpoints:reinstall-all-breakpoints)) 161 | (message "All breakpoints reinstalled.") 162 | (slime-breakpoints--refresh-breakpoints-buffer)) 163 | 164 | (defface slime-breakpoints-button 165 | '((t (:box (:line-width 2 :color "dark grey") :background "light grey" :foreground "black"))) 166 | "Face for slime-breakpoints buttons" 167 | :group 'slime-breakpoints-faces) 168 | 169 | (defun slime-breakpoints--breakpoints-list () 170 | (slime-eval `(slime-breakpoints:list-of-breakpoints))) 171 | 172 | (cl-defun slime-breakpoints--update-breakpoints-buffer-contents () 173 | (let ((breakpoints-list (slime-breakpoints--breakpoints-list))) 174 | (when (zerop (length breakpoints-list)) 175 | (insert "There are no breakpoints installed.") 176 | (cl-return-from slime-breakpoints--update-breakpoints-buffer-contents)) 177 | (dolist (breakpoint breakpoints-list) 178 | (insert-button 179 | (symbol-name (cl-getf breakpoint :name)) 180 | 'action (lambda (_) 181 | (slime-edit-definition (slime-qualify-cl-symbol-name (cl-getf breakpoint :name)))) 182 | 'follow-link t) 183 | (indent-to-column 60) 184 | (widget-create 185 | 'toggle 186 | :on "[Enabled]" 187 | :off "[Disabled]" 188 | :notify (lambda (_ &rest _) 189 | (slime-toggle-breakpoint (slime-qualify-cl-symbol-name (cl-getf breakpoint :name)))) 190 | (cl-getf breakpoint :enabled))) 191 | (newline 2) 192 | (insert-button 193 | "Remove all" 194 | 'face 'slime-breakpoints-button 195 | 'action (lambda (_) 196 | (slime-remove-all-breakpoints)) 197 | 'follow-link t) 198 | (insert " ") 199 | (insert-button 200 | "Reinstall all" 201 | 'face 'slime-breakpoints-button 202 | 'action (lambda (_) 203 | (slime-reinstall-all-breakpoints)) 204 | 'follow-link t) 205 | (widget-setup) 206 | )) 207 | 208 | (defun slime-breakpoints--refresh-breakpoints-buffer () 209 | (let ((buffer (get-buffer "*slime-breakpoints*"))) 210 | (when buffer 211 | (with-current-buffer buffer 212 | (let ((buffer-read-only nil)) 213 | (erase-buffer) 214 | (slime-breakpoints--update-breakpoints-buffer-contents)))))) 215 | 216 | (defun slime-list-breakpoints () 217 | "Open a buffer that list the current installed breakpoints." 218 | (interactive) 219 | (if (get-buffer "*slime-breakpoints*") 220 | (switch-to-buffer "*slime-breakpoints*") 221 | (let ((buffer (get-buffer-create "*slime-breakpoints*"))) 222 | (with-current-buffer buffer 223 | (slime-breakpoints--update-breakpoints-buffer-contents) 224 | (setq buffer-read-only t) 225 | (use-local-map widget-keymap) 226 | (local-set-key (kbd "q") 'kill-buffer) 227 | (display-buffer buffer))))) 228 | 229 | ;; -- Navigation 230 | 231 | ;; This advices slime-list-definition function so that original source locations of breakpointed functions are followed correctly. 232 | 233 | (defun slime-breakpoints--find-definitions (function-name) 234 | (slime-eval `(cl:getf (breakpoints:find-breakpoint (cl:read-from-string ',(slime-qualify-cl-symbol-name function-name))) 235 | :definitions))) 236 | 237 | 238 | (defun slime-breakpoints--find-definitions-advice (find-defs name) 239 | (or (slime-breakpoints--find-definitions name) 240 | (funcall find-defs name))) 241 | 242 | (advice-add 'slime-find-definitions :around 'slime-breakpoints--find-definitions-advice) 243 | 244 | ;; -- Additional useful debugging commands 245 | 246 | (defun slime--last-expression-with-positions () 247 | "Return the last expression and start and end positions, in a list." 248 | (let (start 249 | (end (point))) 250 | (save-excursion 251 | (backward-sexp) 252 | (setq start (point))) 253 | (list 254 | (buffer-substring-no-properties start end) 255 | start end))) 256 | 257 | (defun slime--region-for-last-expression () 258 | "Return the region for last expression." 259 | (let (start 260 | (end (point))) 261 | (save-excursion 262 | (backward-sexp) 263 | (setq start (point))) 264 | (list start end))) 265 | 266 | (defun slime--expression-next-expression () 267 | "Return the next Lisp expression at point." 268 | (buffer-substring-no-properties 269 | (point) 270 | (save-excursion (forward-sexp) (point)))) 271 | 272 | (defun slime--region-for-next-expression () 273 | "Return the region for next expression." 274 | (let ((start (point)) 275 | end) 276 | (save-excursion 277 | (forward-sexp) 278 | (setq end (point))) 279 | (list start end))) 280 | 281 | (defun slime--next-expression-with-positions () 282 | "Return the next expression and start and end positions, in a list." 283 | (let ((start (point)) 284 | end) 285 | (save-excursion 286 | (forward-sexp) 287 | (setq end (point))) 288 | (list 289 | (buffer-substring-no-properties start end) 290 | start end))) 291 | 292 | (defun slime--wrap-last-expression (wrapper) 293 | "Wrap the expression at point. 294 | WRAPPER is a function that takes the expression at point string, 295 | and is expected to return a wrapped version." 296 | (cl-destructuring-bind (last-expression exp-start exp-end) 297 | (slime--last-expression-with-positions) 298 | (cl-destructuring-bind (defun-start defun-end) 299 | (slime-region-for-defun-at-point) 300 | ;; Remove the expression from the function source 301 | (let* ((start-region (buffer-substring-no-properties defun-start exp-start)) 302 | (end-region (buffer-substring-no-properties exp-end defun-end)) 303 | (wrapped-expression (funcall wrapper last-expression)) 304 | (wrapped-defun-source (concat start-region wrapped-expression end-region))) 305 | wrapped-defun-source)))) 306 | 307 | (defun slime--wrap-next-expression (wrapper) 308 | "Wrap the expression at point. 309 | WRAPPER is a function that takes the expression at point string, 310 | and is expected to return a wrapped version." 311 | (cl-destructuring-bind (next-expression exp-start exp-end) 312 | (slime--next-expression-with-positions) 313 | (cl-destructuring-bind (defun-start defun-end) 314 | (slime-region-for-defun-at-point) 315 | ;; Remove the expression from the function source 316 | (let* ((start-region (buffer-substring-no-properties defun-start exp-start)) 317 | (end-region (buffer-substring-no-properties exp-end defun-end)) 318 | (wrapped-expression (funcall wrapper next-expression)) 319 | (wrapped-defun-source (concat start-region wrapped-expression end-region))) 320 | wrapped-defun-source)))) 321 | 322 | (defun slime--async-compile-string (string start-offset &optional cont) 323 | (let* ((line (save-excursion 324 | (goto-char start-offset) 325 | (list (line-number-at-pos) (1+ (current-column))))) 326 | (position `((:position ,start-offset) (:line ,@line)))) 327 | (slime-eval-async 328 | `(swank:compile-string-for-emacs 329 | ,string 330 | ,(buffer-name) 331 | ',position 332 | ,(if (buffer-file-name) (slime-to-lisp-filename (buffer-file-name))) 333 | ',slime-compilation-policy) 334 | (or cont #'slime-compilation-finished)))) 335 | 336 | (defun slime-break-with-last-expression (datum) 337 | "Compile function at point with a BREAK at last expression position. 338 | DATUM is the string passed as first argument to CL:BREAK function. 339 | The CL:BREAK function is invoked with last expression value as second argument. 340 | The function at point is compiled with the extra debugging code. 341 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff." 342 | (interactive (list (read-string "Datum: " "~s"))) 343 | (let* (expr 344 | (source-with-break 345 | (slime--wrap-last-expression 346 | (lambda (exp) 347 | (let* ((inner-break-exp (format "(cl:break \"%s\" %s)" datum exp)) 348 | (break-exp (format "(cl:prog1 %s %s)" exp inner-break-exp))) 349 | (setq expr inner-break-exp) 350 | break-exp))))) 351 | (slime--async-compile-string 352 | source-with-break 0 353 | (lambda (_result) 354 | (display-message-or-buffer (format "Break installed with: %s" expr)))))) 355 | 356 | (defun slime-insert-break-at-point () 357 | "Compile function at point with a BREAK at cursor position. 358 | The function at point is compiled with the extra debugging code. 359 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff." 360 | (interactive) 361 | (cl-destructuring-bind (defun-start defun-end) 362 | (slime-region-for-defun-at-point) 363 | (let* ((start-region (buffer-substring-no-properties defun-start (point))) 364 | (end-region (buffer-substring-no-properties (point) defun-end)) 365 | (wrapped-defun-source (concat start-region " (CL:BREAK) " end-region))) 366 | (display-message-or-buffer (format "Break inserted: %s" wrapped-defun-source)) 367 | (view-echo-area-messages) 368 | (slime-compile-string wrapped-defun-source 0)))) 369 | 370 | (defun slime-trace-last-expression (datum) 371 | "Compile function at point with a 'trace' expression at last expression position. 372 | DATUM is the string passed to CL:FORMAT as control-string. 373 | The function at point is compiled with the extra debugging code. 374 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff." 375 | (interactive (list (read-string "Datum: " "~s~%"))) 376 | (let ((source-with-trace 377 | (slime--wrap-last-expression 378 | (lambda (exp) 379 | (format "(cl:prog1 %s (cl:format cl:t \"%s\" %s))" exp datum exp))))) 380 | (slime-compile-string source-with-trace 0))) 381 | 382 | (defun slime-step-in-last-expression () 383 | "Compile function at point with a 'step' expression at last expression position. 384 | The function at point is compiled with the extra debugging code. 385 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff." 386 | (interactive) 387 | (let ((source-with-step 388 | (slime--wrap-last-expression 389 | (lambda (exp) 390 | (format "(cl:step %s)" exp))))) 391 | (slime-compile-string source-with-step 0))) 392 | 393 | (defun slime-step-in-next-expression () 394 | "Compile function at point with a 'step' expression at next expression position. 395 | The function at point is compiled with the extra debugging code. 396 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff." 397 | (interactive) 398 | (let ((source-with-step 399 | (slime--wrap-next-expression 400 | (lambda (exp) 401 | (format "(cl:step %s)" exp))))) 402 | (slime-compile-string source-with-step 0))) 403 | 404 | (defun slime-debug-print-next-expression () 405 | "Instrument next expression to be debug printed when evaluated. 406 | The function at point is compiled with the extra debugging code. 407 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff. 408 | Requires cl-debug-print." 409 | (interactive) 410 | (slime-eval '(cl:require :cl-debug-print)) 411 | (let* (expr 412 | (source-with-print 413 | (slime--wrap-next-expression 414 | (lambda (exp) 415 | (setq expr exp) 416 | (format "(debug-print:debug-print '%s %s)" exp exp))))) 417 | (slime--async-compile-string 418 | source-with-print 0 419 | (lambda (_result) 420 | (display-message-or-buffer (format "Instrumented for debug printing: %s" expr)))))) 421 | 422 | (defun slime-debug-print-last-expression () 423 | "Instrument last expression to be debug printed when evaluated. 424 | The function at point is compiled with the extra debugging code. 425 | Use `slime-compile-defun' on the function source code to recompile without the debugging stuff. 426 | Requires cl-debug-print." 427 | (interactive) 428 | (slime-eval '(cl:require :cl-debug-print)) 429 | (let* (expr 430 | (source-with-print 431 | (slime--wrap-last-expression 432 | (lambda (exp) 433 | (setq expr exp) 434 | (format "(debug-print:debug-print '%s %s)" exp exp))))) 435 | (slime--async-compile-string 436 | source-with-print 0 437 | (lambda (_result) 438 | (display-message-or-buffer (format "Instrumented for debug printing: %s" expr)))))) 439 | 440 | (defvar slime-breakpoints-command-map 441 | (let ((map (make-sparse-keymap))) 442 | (define-key map (kbd "RET") #'slime-break-on-entry) 443 | (define-key map (kbd "SPC") #'slime-toggle-breakpoint) 444 | (define-key map (kbd "") #'slime-remove-breakpoint) 445 | (define-key map (kbd "s") #'slime-step-on-entry) 446 | (define-key map (kbd "q") #'slime-remove-all-breakpoints) 447 | (define-key map (kbd "l") #'slime-list-breakpoints) 448 | map)) 449 | 450 | (fset 'slime-breakpoints-command-map slime-breakpoints-command-map) 451 | 452 | (defun slime-breakpoints-setup-key-bindings () 453 | "Setup key bindings for `slime-breakpoints' package." 454 | (add-hook 'lisp-mode-hook 455 | (lambda () 456 | (local-set-key (kbd "C-c b") 'slime-breakpoints-command-map)))) 457 | 458 | (defun slime-breakpoints--extend-slime-menu () 459 | "Extend slime debugging menu." 460 | (easy-menu-add-item 'menubar-slime '("Debugging") "---") 461 | (easy-menu-add-item 'menubar-slime '("Debugging") 462 | ["Break on entry..." slime-break-on-entry]) 463 | (easy-menu-add-item 'menubar-slime '("Debugging") 464 | ["Break with last expression" slime-break-with-last-expression]) 465 | (easy-menu-add-item 'menubar-slime '("Debugging") 466 | ["Insert break at point" slime-insert-break-at-point]) 467 | (easy-menu-add-item 'menubar-slime '("Debugging") 468 | ["Toggle breakpoint at point" slime-toggle-breakpoint]) 469 | (easy-menu-add-item 'menubar-slime '("Debugging") 470 | ["Remove breakpoint at point" slime-remove-breakpoint]) 471 | (easy-menu-add-item 'menubar-slime '("Debugging") 472 | ["Disable breakpoint at point" slime-disable-breakpoint]) 473 | (easy-menu-add-item 'menubar-slime '("Debugging") 474 | ["Disable all breakpoints" slime-disable-all-breakpoints]) 475 | (easy-menu-add-item 'menubar-slime '("Debugging") 476 | ["Remove all breakpoints" slime-remove-all-breakpoints]) 477 | (easy-menu-add-item 'menubar-slime '("Debugging") 478 | ["List breakpoints" slime-list-breakpoints]) 479 | (easy-menu-add-item 'menubar-slime '("Debugging") "---") 480 | (easy-menu-add-item 'menubar-slime '("Debugging") 481 | ["Trace last expression" slime-trace-last-expression]) 482 | (easy-menu-add-item 'menubar-slime '("Debugging") 483 | ["Step on entry..." slime-step-on-entry]) 484 | (easy-menu-add-item 'menubar-slime '("Debugging") 485 | ["Step in last expression..." slime-step-in-last-expression]) 486 | (easy-menu-add-item 'menubar-slime '("Debugging") "---") 487 | (easy-menu-add-item 'menubar-slime '("Debugging") 488 | ["Debug print last expression" slime-debug-print-last-expression]) 489 | (easy-menu-add-item 'menubar-slime '("Debugging") 490 | ["Debug print next expression" slime-debug-print-next-expression])) 491 | 492 | (define-slime-contrib slime-breakpoints 493 | "Breakpoints management extension for SLIME." 494 | (:authors "Mariano Montone") 495 | (:license "GPL") 496 | (:swank-dependencies slime-breakpoints) 497 | (:on-load 498 | ;; setup key bindings 499 | (slime-breakpoints-setup-key-bindings) 500 | ;; add menu items 501 | (slime-breakpoints--extend-slime-menu))) 502 | 503 | 504 | (provide 'slime-breakpoints) 505 | 506 | ;;; slime-breakpoints.el ends here 507 | --------------------------------------------------------------------------------