From: Eshel Yaron Date: Thu, 16 Feb 2023 12:54:44 +0000 (+0200) Subject: ADDED: Support setting breakpoints in sweeprolog-mode X-Git-Tag: V9.1.6-sweep-0.17.0~2 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=5fbcbae8539f74324153eb61e294488c8fe61f4e;p=dict.git ADDED: Support setting breakpoints in sweeprolog-mode * sweep.pl (sweep_set_breakpoint/2) (sweep_delete_breakpoint/2) (sweep_set_breakpoint_condition/2): new predicates. * sweeprolog.el (sweeprolog-dependency-directive): update package-version field. (sweeprolog-set-breakpoint) (sweeprolog-set-breakpoint-condition) (sweeprolog-delete-breakpoint) (sweeprolog-list-breakpoints): new commands. (sweeprolog-highlight-breakpoints): new user option. (sweeprolog-mode-map): bind sweeprolog-set-breakpoint. * README.org (Setting Breakpoints): new manual section. --- diff --git a/README.org b/README.org index 8765342..31b8fe1 100644 --- a/README.org +++ b/README.org @@ -1316,6 +1316,71 @@ information about the mode line. More relevant information about loading code in SWI-Prolog can be found in [[https://www.swi-prolog.org/pldoc/man?section=consulting][Loading Prolog source files]] in the SWI-Prolog manual. +** Setting Breakpoints +:PROPERTIES: +:CUSTOM_ID: breakpoints +:DESCRIPTION: Commands for setting breakpoints in Prolog buffers +:ALT_TITLE: Setting Breakpoints +:END: + +#+CINDEX: breakpoints +You can set /breakpoints/ in ~sweeprolog-mode~ buffers to have SWI-Prolog +break before specific goals in the code (see [[https://www.swi-prolog.org/pldoc/man?section=trace-breakpoints][Breakpoints]] in the +SWI-Prolog manual). + +#+FINDEX: sweeprolog-set-breakpoint +- Key: C-c C-b (sweeprolog-set-breakpoint) :: Set a breakpoint. +#+VINDEX: sweeprolog-highlight-breakpoints +- User Option: sweeprolog-highlight-breakpoints :: If non-nil, + highlight breakpoints in ~sweeprolog-mode~ buffers. Defaults to ~t~. + +The command ~sweeprolog-set-breakpoint~, bound to ~C-c C-b~, sets a +breakpoint at the position of the cursor. If you call it with a +positive prefix argument (e.g. ~C-u C-c C-b~), it creates a conditional +breakpoint with a condition goal that you insert in the minibuffer. +If you call it with a non-positive prefix argument (e.g. ~C-0 C-c C-b~), +it deletes the breakpoint at point instead. + +When Context Menu mode is enabled, you can also create and delete +breakpoints in ~sweeprolog-mode~ buffers through right-click context +menus (see [[#context-menu][Context Menu]]). + +By default, Sweep highlights terms with active breakpoints in +~sweeprolog-mode~ buffers. To inhibit breakpoint highlighting, +customize the user option ~sweeprolog-highlight-breakpoints~ to ~nil~. + +*** Breakpoint Menu +:PROPERTIES: +:CUSTOM_ID: breakpoint-menu +:DESCRIPTION: Special mode for managing breakpoints +:ALT_TITLE: Breakpoint Menu +:END: + +Sweep provides a /breakpoint menu/ that lets you manage breakpoints +across your codebase. + +#+FINDEX: sweeprolog-list-breakpoints +- Command: sweeprolog-list-breakpoints :: Display a list of active + breakpoints. + +To open the breakpoint menu, type ~M-x sweeprolog-list-breakpoints~. +This command opens the breakpoint menu in the =*Sweep Breakpoints*= +buffer. The major mode of this buffer is Sweep Breakpoint Menu, +which is a special mode that includes useful commands for managing +Prolog breakpoints: + +#+FINDEX: sweeprolog-breakpoint-menu-find +- Key: RET (sweeprolog-breakpoint-menu-find) :: Go to the position of + the breakpoint corresponding to the breakpoint menu entry at point. +#+FINDEX: sweeprolog-breakpoint-menu-find-other-window +- Key: o (sweeprolog-breakpoint-menu-find-other-window) :: Show the + position of the breakpoint corresponding to the breakpoint menu + entry at point, in another window. +#+FINDEX: sweeprolog-breakpoint-menu-set-condition +- Key: c (sweeprolog-breakpoint-menu-set-condition) :: Set the + condition goal for the breakpoint corresponding to the breakpoint + menu entry at point. + ** Creating New Modules :PROPERTIES: :CUSTOM_ID: creating-new-modules @@ -2336,6 +2401,7 @@ The full list of keybindings in ~sweeprolog-prefix-map~ is given below: | Key | Command | Documentation | |-----+--------------------------------------+-----------------------------------| +| ~B~ | ~sweeprolog-list-breakpoints~ | [[#breakpoint-menu][Breakpoint Menu]] | | ~F~ | ~sweeprolog-set-prolog-flag~ | [[*Setting Prolog flags][Setting Prolog Flags]] | | ~P~ | ~sweeprolog-pack-install~ | [[*Installing Prolog packages][Installing Prolog packages]] | | ~R~ | ~sweeprolog-restart~ | [[#prolog-init][Prolog Initialization and Cleanup]] | diff --git a/sweep.pl b/sweep.pl index 046e7eb..71c63fe 100644 --- a/sweep.pl +++ b/sweep.pl @@ -79,7 +79,14 @@ sweep_predicate_dependencies/2, sweep_async_goal/2, sweep_interrupt_async_goal/2, - sweep_source_file_load_time/2 + sweep_source_file_load_time/2, + sweep_set_breakpoint/2, + sweep_set_breakpoint_condition/2, + sweep_delete_breakpoint/2, + sweep_current_breakpoints/2, + sweep_current_breakpoints_in_region/2, + sweep_breakpoint_range/2, + sweep_breakpoint_file/2 ]). :- use_module(library(pldoc)). @@ -1234,3 +1241,55 @@ sweep_start_async_goal(Caller, Cookie, Goal, FD) :- sweep_interrupt_async_goal(TId, TId) :- thread_signal(TId, throw(interrupted)). + +sweep_set_breakpoint([File0,Line,Char], Id) :- + atom_string(File, File0), + set_breakpoint(File, Line, Char, Id). + +sweep_set_breakpoint_condition([Id|Cond], _) :- + set_breakpoint_condition(Id, Cond). + +sweep_delete_breakpoint(Id, _) :- + delete_breakpoint(Id). + +sweep_current_breakpoints(_, BPs) :- + findall(BP-Claue, + breakpoint_property(BP, clause(Claue)), + BPs0), + maplist(format_breakpoint, BPs0, BPs). + +format_breakpoint(Id-Clause, [["id"|Id],["predicate"|Pred],["clause"|ClauseNum]|BP]) :- + clause_property(Clause, predicate(Pred0)), + term_string(Pred0, Pred), + pi_head(Pred0, Head), + nth_clause(Head, ClauseNum, Clause), + findall(Prop, breakpoint_property(Id, Prop), Props), + convlist(format_breakpoint_property, Props, BP). + +format_breakpoint_property(file(File0), ["file"|File]) :- + atom_string(File0, File). +format_breakpoint_property(line_count(Line), ["line"|Line]). +format_breakpoint_property(character_range(Start0, Len), ["range",Start,End]) :- + Start is Start0 + 1, End is Start + Len. +format_breakpoint_property(condition(Cond), ["condition"|Cond]). + +sweep_current_breakpoints_in_region([Path0, Beg, End], BPs) :- + atom_string(Path, Path0), + findall([BPBeg|BPEnd], + ( breakpoint_property(BPId, file(Path)), + breakpoint_property(BPId, character_range(BPBeg0, Len)), + BPBeg is BPBeg0 + 1, + Beg =< BPBeg, + BPBeg =< End, + BPEnd is BPBeg + Len + ), + BPs). + +sweep_breakpoint_range(Id, [Beg|End]) :- + breakpoint_property(Id, character_range(Beg0, Len)), + Beg is Beg0 + 1, + End is Beg + Len. + +sweep_breakpoint_file(Id, File) :- + breakpoint_property(Id, file(File0)), + atom_string(File0, File). diff --git a/sweeprolog.el b/sweeprolog.el index fd5dc50..6bf7e5a 100644 --- a/sweeprolog.el +++ b/sweeprolog.el @@ -411,17 +411,23 @@ directives. If set to the symbol `infer', then unless the buffer already contains dependency directives and all of them are `use_module/2' directives. Any other values means to use `autoload/2' for all added directives." - :package-version '((sweeprolog "0.16.1")) + :package-version '((sweeprolog "0.17.0")) :type '(choice (const :tag "Prefer use_module/2" use-module) (const :tag "Prefer autoload/2" autoload) (const :tag "Infer" infer)) :group 'sweeprolog) +(defcustom sweeprolog-highlight-breakpoints t + "If non-nil, highlight breakpoints with a dedicated face." + :package-version '((sweeprolog "0.17.0")) + :type 'boolean + :group 'sweeprolog) ;;;; Keymaps (defvar sweeprolog-mode-map (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-b") #'sweeprolog-set-breakpoint) (define-key map (kbd "C-c C-c") #'sweeprolog-analyze-buffer) (define-key map (kbd "C-c C-d") #'sweeprolog-document-predicate-at-point) (define-key map (kbd "C-c C-e") #'sweeprolog-export-predicate) @@ -489,6 +495,7 @@ use `autoload/2' for all added directives." ;;;###autoload (defvar sweeprolog-prefix-map (let ((map (make-sparse-keymap))) + (define-key map "B" #'sweeprolog-list-breakpoints) (define-key map "F" #'sweeprolog-set-prolog-flag) (define-key map "P" #'sweeprolog-pack-install) (define-key map "R" #'sweeprolog-restart) @@ -546,6 +553,12 @@ use `autoload/2' for all added directives." [ "Set Prolog Flag" sweeprolog-set-prolog-flag t ] [ "Install Prolog Package" sweeprolog-pack-install t ] "--" + [ "Set Breakpoint" sweeprolog-set-breakpoint + (derived-mode-p 'sweeprolog-mode) ] + [ "Delete Breakpoint" sweeprolog-delete-breakpoint + (sweeprolog-current-breakpoints) ] + [ "List Breakpoints" sweeprolog-list-breakpoints t ] + "--" [ "Open Top-level" sweeprolog-top-level t ] [ "Signal Top-level" sweeprolog-top-level-signal @@ -2327,7 +2340,12 @@ resulting list even when found in the current clause." (current-buffer) nil) (goto-char hend) - (setq hole (sweeprolog--next-hole)))))))))) + (setq hole (sweeprolog--next-hole))))))))) + (when (and sweeprolog-highlight-breakpoints + (sweeprolog-buffer-loaded-since-last-modification-p)) + (with-silent-modifications + (dolist (bp (sweeprolog-current-breakpoints-in-region beg end)) + (sweeprolog--highlight-breakpoint (car bp) (cdr bp)))))) (defun sweeprolog--help-echo-for-comment (kind) (pcase kind @@ -5438,6 +5456,9 @@ GOAL." (defvar sweeprolog-context-menu-variable-at-click nil "Prolog variable at mouse click.") +(defvar sweeprolog-context-menu-breakpoints-at-click nil + "Prolog breakpoints at mouse click.") + (defun sweeprolog-context-menu-find-module () "Find Prolog module at mouse click." (interactive) @@ -5492,6 +5513,27 @@ GOAL." sweeprolog-context-menu-point-at-click sweeprolog-context-menu-variable-at-click)) +(defun sweeprolog-breakpoint-context-menu-set () + "Set breakpoint at click." + (interactive) + (sweeprolog-set-breakpoint sweeprolog-context-menu-point-at-click)) + +(defun sweeprolog-breakpoint-context-menu-delete () + "Delete breakpoints at click." + (interactive) + (dolist (id sweeprolog-context-menu-breakpoints-at-click) + (sweeprolog-delete-breakpoint id)) + (let ((n (length sweeprolog-context-menu-breakpoints-at-click))) + (message "Deleted %d %s" n + (ngettext "breakpoint" "breakpoints" n)))) + +(defun sweeprolog-breakpoint-context-menu-set-condition () + "Set condition goal for the breakpoint at click." + (interactive) + (let ((id (car sweeprolog-context-menu-breakpoints-at-click)) + (cond (sweeprolog-read-breakpoint-condition))) + (sweeprolog-set-breakpoint-condition id cond))) + (defun sweeprolog-context-menu-for-predicate (menu tok _beg _end _point) "Extend MENU with predicate-related commands if TOK describes one." (pcase tok @@ -5581,8 +5623,43 @@ POINT is the buffer position of the mouse click." sweeprolog-context-menu-variable-at-click)) :keys "\\[sweeprolog-rename-variable]"))))) +(defun sweeprolog-context-menu-for-clause (menu tok _beg _end point) + "Extend MENU with clause-related commands if TOK specifies one. +POINT is the buffer position of the mouse click." + (pcase tok + ((or "clause" + "grammar_rule") + (when-let ((file (buffer-file-name)) + (submenu (make-sparse-keymap (propertize "Breakpoint")))) + (if-let ((bps-at-point + (sweeprolog-breakpoints-at-point file point)) + (ids (mapcar (lambda (bp) + (alist-get "id" bp nil nil #'string=)) + bps-at-point))) + (progn + (setq sweeprolog-context-menu-breakpoints-at-click ids) + (define-key-after submenu [sweeprolog-delete-breakpoint] + `(menu-item "Delete" + sweeprolog-breakpoint-context-menu-delete + :help "Delete this breakpoint" + :keys "\\[negative-argument] \\[sweeprolog-set-breakpoint]")) + (define-key-after submenu [sweeprolog-set-breakpoint-condition] + `(menu-item "Set condition" + sweeprolog-breakpoint-context-menu-set-condition + :help "Set condition goal for this breakpoint" + :keys "\\[universal-argument] \\[sweeprolog-set-breakpoint]"))) + (setq sweeprolog-context-menu-point-at-click point) + (define-key-after submenu [sweeprolog-set-breakpoint] + `(menu-item "Set" + sweeprolog-breakpoint-context-menu-set + :help "Set breakpoint" + :keys "\\[sweeprolog-set-breakpoint]"))) + (define-key-after menu [sweeprolog-breakpoint] + `(menu-item "Breakpoint" ,submenu)))))) + (defvar sweeprolog-context-menu-functions - '(sweeprolog-context-menu-for-file + '(sweeprolog-context-menu-for-clause + sweeprolog-context-menu-for-file sweeprolog-context-menu-for-module sweeprolog-context-menu-for-predicate sweeprolog-context-menu-for-variable) @@ -6022,6 +6099,287 @@ numeric prefix argument (1 without prefix argument)." 1 (point) sweeprolog-increment-numbered-variables-last-result)) +;;;; Breakpoints + +(defun sweeprolog-current-breakpoints () + "Return the list for current Prolog breakpoints. +Each breakpoint is represented as an alist with string keys." + (sweeprolog--query-once "sweep" "sweep_current_breakpoints" nil)) + +(defun sweeprolog-current-breakpoints-in-region (beg end &optional buf) + "Return breakpoints that start between BEG an END in BUF. +If BUF is nil, it defaults to the current buffer. + +The return value is an alist of elements (BPBEG . BPEND) where +BPBEG is the start position of the breakpoint and BPEND is its +end. list for current Prolog breakpoints." + (setq buf (or buf (current-buffer))) + (with-current-buffer buf + (sweeprolog--query-once "sweep" "sweep_current_breakpoints_in_region" + (list (or (buffer-file-name) + (expand-file-name (buffer-name))) + beg end)))) + +(defun sweeprolog-read-breakpoint (&optional prompt) + "Read a Prolog breakpoint id in the minibuffer, with completion. +If PROMPT is non-nil, use it as the minibuffer prompt, otherwise +prompt with \"Breakpoint: \"." + (let* ((bps (sweeprolog-current-breakpoints)) + (col (mapcar (lambda (bp) + (let ((id (alist-get "id" bp nil nil #'string=)) + (file (alist-get "file" bp nil nil #'string=)) + (line (alist-get "line" bp nil nil #'string=))) + (cons (number-to-string id) + (format "%s%s:%d" + (make-string (- 4 (floor (log id 10))) ? ) + file line)))) + bps)) + (current-file (buffer-file-name)) + (current-line (line-number-at-pos (point))) + (def + (mapcar (lambda (bp) + (number-to-string (alist-get "id" bp nil nil #'string=))) + (seq-filter (lambda (bp) + (and (string= (expand-file-name (alist-get "file" bp nil nil #'string=)) + current-file) + (= (alist-get "line" bp nil nil #'string=) + current-line))) + bps))) + (completion-extra-properties + (list :annotation-function + (lambda (key) + (alist-get key col nil nil #'string=))))) + (string-to-number + (completing-read (concat (or prompt "Breakpoint") + (when def (concat " (default " (car def) ")")) + ": ") + col nil t nil nil def)))) + +(defun sweeprolog-read-breakpoint-condition () + "Read a Prolog breakpoint condition in the minibuffer." + (sweeprolog-read-goal "[breakpoint condition] ?- ")) + +(defun sweeprolog-set-breakpoint-condition (id cond) + "Attach condition goal COND to the breakpoint with id ID." + (interactive (list (sweeprolog-read-breakpoint "Set condition for breakpoint") + (sweeprolog-read-breakpoint-condition))) + (sweeprolog--query-once "sweep" "sweep_set_breakpoint_condition" + (cons id cond))) + +(defun sweeprolog-delete-breakpoint (id) + "Delete the breakpoint with id ID." + (interactive (list (sweeprolog-read-breakpoint "Delete breakpoint"))) + (let* ((file (sweeprolog--query-once "sweep" "sweep_breakpoint_file" id)) + (buf (find-buffer-visiting file (lambda (b) + (with-current-buffer b + (and (derived-mode-p 'sweeprolog-mode) + sweeprolog-highlight-breakpoints))))) + (range (sweeprolog--query-once "sweep" "sweep_breakpoint_range" id))) + (sweeprolog--query-once "sweep" "sweep_delete_breakpoint" id) + (message "Deleted breakpoint (id %d)" id) + (when (and buf range) + (with-current-buffer buf + (sweeprolog-analyze-term (car range)))))) + +(defface sweeprolog-breakpoint-face + '((((background light)) :background "lightgreen") + (t :background "darkgreen")) + "Face used to highlight Prolog breakpoints." + :group 'sweeprolog-faces) + +(defvar sweeprolog-breakpoint-face 'sweeprolog-breakpoint-face + "Face to use for highlighting Prolog breakpoints.") + +(defun sweeprolog--highlight-breakpoint (beg end) + (font-lock--add-text-property beg end + 'font-lock-face + sweeprolog-breakpoint-face + (current-buffer) + nil)) + +(defun sweeprolog-highlight-breakpoint (id) + (when sweeprolog-highlight-breakpoints + (when-let + ((range + (sweeprolog--query-once "sweep" "sweep_breakpoint_range" + id))) + (with-silent-modifications + (sweeprolog--highlight-breakpoint (car range) + (cdr range)))))) + +(defun sweeprolog-breakpoints-at-point (file point) + (seq-filter (lambda (bp) + (and (string= (expand-file-name (alist-get "file" bp nil nil #'string=)) + file) + (when-let ((range (alist-get "range" bp nil nil #'string=))) + (<= (car range) point (cadr range))))) + (sweeprolog-current-breakpoints))) + +(defun sweeprolog-read-breakpoint-at-point (point &optional prompt) + "Prompt with PROMPT for a breakpoint at POINT, with completion. +If there only one breakpoint at POINT, return it without prompting." + (let* ((bps (sweeprolog-breakpoints-at-point (buffer-file-name) + point)) + (col (mapcar (lambda (bp) + (let ((id (alist-get "id" bp nil nil #'string=)) + (file (alist-get "file" bp nil nil #'string=)) + (line (alist-get "line" bp nil nil #'string=))) + (cons (number-to-string id) + (format "%s%s:%d" + (make-string (- 4 (floor (log id 10))) ? ) + file line)))) + bps))) + (when col + (string-to-number + (if (= (length col) 1) + (caar col) + (let ((completion-extra-properties + (list :annotation-function + (lambda (key) + (alist-get key col nil nil #'string=))))) + (completing-read (or prompt "Breakpoint at point: ") col nil t))))))) + +(defun sweeprolog-delete-breakpoint-at-point (point) + "Delete breakpoint at POINT." + (interactive "d" sweeprolog-mode) + (if-let ((id (sweeprolog-read-breakpoint-at-point + point "Delete breakpoint at point: "))) + (sweeprolog-delete-breakpoint id) + (user-error "No breakpoints here!"))) + +(defun sweeprolog-set-breakpoint (point &optional cond delete) + "Set breakpoint at POINT with condition COND. +If DELETE is non-nil, delete the breakpoint at POINT instead. + +Interactively, POINT is point. If called without a prefix +argument, COND and DELETE are nil. If called with a positive +prefix argument, prompt for COND. Otherwise, if called with a +zero or negative prefix argument, delete the breakpoint at POINT +instead." + (interactive + (cons (point) + (cond ((< (prefix-numeric-value current-prefix-arg) 1) + (list nil t)) + (current-prefix-arg + (list (sweeprolog-read-breakpoint-condition))))) + sweeprolog-mode) + (if delete + (sweeprolog-delete-breakpoint-at-point point) + (if (or (sweeprolog-buffer-loaded-since-last-modification-p) + (and (y-or-n-p (concat (if (sweeprolog-buffer-load-time) + "Buffer modified since it was last loaded, re" + "Buffer isn't loaded, ") + "load before setting breakpoint?")) + (sweeprolog-load-buffer (current-buffer)))) + (if-let ((bp (sweeprolog--query-once "sweep" "sweep_set_breakpoint" + (list (buffer-file-name) + (line-number-at-pos point) + (1- point))))) + (progn + (if cond + (if (sweeprolog-set-breakpoint-condition bp cond) + (message "Created conditional breakpoint (id %d)." bp) + (sweeprolog-delete-breakpoint bp) + (user-error "Failed to set breakpoint condition")) + (message "Created breakpoint (id %d)." bp)) + (sweeprolog-highlight-breakpoint bp)) + (user-error "Failed to create breakpoint")) + (user-error "Cannot set breakpoint in buffer without loading it")))) + +(defun sweeprolog-breakpoint-menu-mode--entries () + (mapcar (lambda (bp) + (let ((id (alist-get "id" bp nil nil #'string=)) + (file (alist-get "file" bp nil nil #'string=)) + (line (alist-get "line" bp nil nil #'string=)) + (pred (alist-get "predicate" bp nil nil #'string=)) + (clause (alist-get "clause" bp nil nil #'string=)) + (cond (alist-get "condition" bp nil nil #'string=))) + (list id (vector (number-to-string id) + (if file file "") + (if line (number-to-string line) "") + (propertize pred + 'face + (sweeprolog-predicate-indicator-face)) + (number-to-string clause) + (or cond ""))))) + (sweeprolog-current-breakpoints))) + +(defun sweeprolog-breakpoint-menu-mode--refresh () + (tabulated-list-init-header) + (setq tabulated-list-entries (sweeprolog-breakpoint-menu-mode--entries))) + +(defun sweeprolog-breakpoint-menu-find (&optional other-window) + "Go to the source position of the breakpoint at point. +If OTHER-WINDOW is non-nil, find it in another window." + (interactive "" sweeprolog-breakpoint-menu-mode) + (if-let ((vec (tabulated-list-get-entry))) + (let* ((file (seq-elt vec 1)) + (line (seq-elt vec 2))) + (if other-window + (find-file-other-window file) + (find-file file)) + (goto-char (point-min)) + (forward-line (1- (string-to-number line)))) + (user-error "No breakpoint menu entry here"))) + +(defun sweeprolog-breakpoint-menu-find-other-window () + "Find the position of the breakpoint at point in another window." + (interactive "" sweeprolog-breakpoint-menu-mode) + (sweeprolog-breakpoint-menu-find t)) + +(defun sweeprolog-breakpoint-menu-delete () + "Delete the breakpoint at point." + (interactive "" sweeprolog-breakpoint-menu-mode) + (if-let ((id (car (tabulated-list-delete-entry)))) + (sweeprolog-delete-breakpoint id) + (user-error "No breakpoint menu entry here"))) + +(defun sweeprolog-breakpoint-menu-set-condition (bp cond) + "Attach condition goal COND to the breakpoint BP at point." + (interactive (list (tabulated-list-get-id) + (sweeprolog-read-breakpoint-condition)) + sweeprolog-breakpoint-menu-mode) + (if bp + (if (sweeprolog-set-breakpoint-condition bp cond) + (tabulated-list-revert) + (user-error "Failed to set breakpoint condition")) + (user-error "No breakpoint menu entry here"))) + +(defvar sweeprolog-breakpoint-menu-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") #'sweeprolog-breakpoint-menu-find) + (define-key map (kbd "o") #'sweeprolog-breakpoint-menu-find-other-window) + (define-key map (kbd "C") #'sweeprolog-breakpoint-menu-set-condition) + (define-key map (kbd "D") #'sweeprolog-breakpoint-menu-delete) + map) + "Local keymap for `sweeprolog-breakpoint-menu-mode' buffers.") + +(define-derived-mode sweeprolog-breakpoint-menu-mode + tabulated-list-mode "Sweep Breakpoint Menu" + "Major mode for browsing the list of current Prolog breakpoints." + (setq tabulated-list-format [("ID" 8 t) + ("File" 40 t) + ("Line" 8 t) + ("Predicate" 32 t) + ("Clause" 8 t) + ("Condition" 20 t)]) + (setq tabulated-list-padding 2) + (setq tabulated-list-sort-key (cons "ID" nil)) + (add-hook 'tabulated-list-revert-hook + #'sweeprolog-breakpoint-menu-mode--refresh nil t) + (tabulated-list-init-header)) + +(defun sweeprolog-list-breakpoints () + "Display a list of Prolog breakpoints." + (interactive) + (let ((buf (get-buffer-create "*Sweep Breakpoints*"))) + (with-current-buffer buf + (sweeprolog-breakpoint-menu-mode) + (sweeprolog-breakpoint-menu-mode--refresh) + (tabulated-list-print)) + (pop-to-buffer buf))) + + ;;;; Footer (provide 'sweeprolog)