For further details, please consult the manual:
<https://eshelyaron.com/sweep.html>.
+* Version 0.8.0 on 2022-10-22
+
+** New command ~sweeprolog-insert-term-dwim~ in ~sweeprolog-mode~ buffers
+
+This version introduces a new mechanism for context-based term
+insertion which revolves around a new command
+~sweeprolog-insert-term-dwim~, bound to ~C-M-m~. When invoked after a
+fullstop ending a predicate clause, this command inserts a new clause
+for the same predicate. When called with point over a call to an
+undefined predicate, this command insert a definition for that
+predicate after the current predicate definition.
+
+** New command ~sweeprolog-forward-hole~ in ~sweeprolog-mode~ buffers
+
+This command, bound to ~C-c C-i~ in ~sweeprolog-mode-map~, moves the
+ cursor and marks the next hole (placeholder variable) inserted by
+ ~sweeprolog-insert-term-dwim~ for the user to fill it.
+
+** Newly deprecated user option ~sweeprolog-init-on-load
* Version 0.7.2 on 2022-10-20
** ~sweep-module~ is now loaded on-demand
prompting for a predicate, invoke ~sweeprolog-export-predicate~ with a
prefix argument (~C-u C-c C-e~).
+** Context-Based Term Insertion
+:PROPERTIES:
+:CUSTOM_ID: insert-term-at-point
+:DESCRIPTION: Commands for smart insertion of Prolog terms based on the surrounding context
+:END:
+
+#+CINDEX: context-based term insertion
+#+CINDEX: term insertion at-point
+#+FINDEX: sweeprolog-insert-term-dwim
+#+KINDEX: M-RET
+#+KINDEX: C-M-m
+As a means of automating common Prolog code editing tasks, such as
+adding new clauses to an existing predicate, ~sweeprolog-mode~ provides
+the "do what I mean" command ~M-x sweeprolog-insert-term-dwim~, bound by
+default to ~C-M-m~ (or equivalently, ~M-RET~). This command inserts a new
+term at or after point according to the context in which
+~sweeprolog-insert-term-dwim~ is invoked.
+
+#+VINDEX: sweeprolog-insert-term-functions
+To determine which term to insert and exactly where, this command
+calls the functions in the list held by the variable
+~sweeprolog-insert-term-functions~ one after the other until one of the
+functions signal success by returning non-nil.
+
+By default, ~sweeprolog-insert-term-dwim~ tries the following insertion
+functions, in order:
+
+#+FINDEX: sweeprolog-maybe-insert-next-clause
+#+FINDEX: sweeprolog-maybe-define-predicate
+- ~sweeprolog-maybe-insert-next-clause~ :: If the last token before
+ point is a fullstop ending a predicate clause, insert a new clause
+ below it.
+- ~sweeprolog-maybe-define-predicate~ :: If point is over a call to an
+ undefined predicate, insert a definition for that predicate below
+ the last clause of the current predicate definition.
+
+*** Filling Holes
+:PROPERTIES:
+:CUSTOM_ID: filling-holes
+:DESCRIPTION: Commands for finding and filling holes for interactive term insertion
+:END:
+
+#+CINDEX: holes
+The default term insertion functions used by
+~sweeprolog-insert-term-dwim~ create a new clause in the buffer, with
+placeholders for the arguments of the head term (if any) and for the
+clause's body. These placeholders are simply anonymous variables (~_~),
+but they are annotated by the insertion functions with a special text
+property[fn:1] that allows ~sweeprolog-mode~ to recognize them as
+"holes" needed to be filled. After a term is inserted with
+~sweeprolog-insert-term-dwim~, the region is set to the first hole and
+the cursor left at the its end.
+
+#+FINDEX: sweeprolog-forward-hole
+#+KINDEX: C-c C-i
+#+KINDEX: C-c TAB
+#+KINDEX: C-- C-c C-i
+#+KINDEX: C-- C-c TAB
+To jump to the next hole in a ~sweeprolog-mode~ buffer, use the command
+~C-c C-i~ (~M-x sweeprolog-forward-hole~). This command sets up the
+region to cover the next hole after point leaving the cursor at right
+after the hole. To jump to the previous hole instead, call
+~sweeprolog-forward-hole~ with a negative prefix argument (~C-- C-c C-i~).
+
+To "fill" a hole marked by one of the aforementioned commands, type
+~C-w~ (~M-x kill-region~) to kill the region and remove the placeholder
+variable, then insert Prolog code as usual. As an alternative to
+manually killing the region with ~C-w~, with ~delete-selection-mode~
+enabled the placeholder is automatically deleted when the user inserts
+a character while the region is active (see also [[info:emacs#Using Region][Using Region in the
+Emacs manual]]).
+
+[fn:1] see [[info:elisp#Text Properties][Text Properties in the Elisp manual]]
+
* Prolog Help
:PROPERTIES:
:CUSTOM_ID: prolog-help
appropriate position, i.e. before the first predicate definition in
the buffer.
-- Add a command for inserting a new clause, similar to ~C-M-m~ in ~prolog-mode~ :: ~sweeprolog-mode~
- should provide a command for inserting a new clause for the
- predicate at or above point.
-
- Add a command for interactively inserting a new predicate :: ~sweeprolog-mode~
should provide a command for interactively inserting a new predicate
definition, ideally with optional =PlDoc= comments (see [[#sweeprolog-pldoc][Documenting
(should (equal (sweeprolog-next-solution) nil))
(should (equal (sweeprolog-cut-query) t)))
+
+(ert-deftest dwim-define-predicate ()
+ "Tests defining a new predicate with `sweeprolog-insert-term-dwim'."
+ (with-temp-buffer
+ (sweeprolog-mode)
+ (insert "
+foo :- bar.
+")
+ (backward-word)
+ (sweeprolog-insert-term-dwim)
+ (call-interactively #'kill-region)
+ (insert "foo")
+ (should (string= (buffer-string)
+ "
+foo :- bar.
+
+bar :- foo.
+"))))
+
(ert-deftest end-of-top-term-with-univ ()
"Tests detecting the fullstop in presence of `=..'."
(with-temp-buffer
(flymake-show-buffer-diagnostics))
(user-error "Flymake is not active in the current buffer")))
+(defun sweeprolog--forward-hole ()
+ (if-let ((prop (text-property-search-forward 'sweeprolog-hole)))
+ (progn
+ (push-mark (prop-match-beginning prop) t t))
+ (user-error "No holes following point")))
+
+(defun sweeprolog--backward-hole ()
+ (if-let ((prop (text-property-search-backward 'sweeprolog-hole))
+ (end (prop-match-end prop)))
+ (progn
+ (goto-char end)
+ (push-mark (1- end) t t))
+ (user-error "No holes before point")))
+
+(defun sweeprolog-forward-hole (&optional arg)
+ "Move point to the next hole in a `sweeprolog-mode' buffer.
+
+With negative prefix argument ARG, move to the previous hole
+instead."
+ (interactive "p" sweeprolog-mode)
+ (setq arg (or arg 1)
+ deactivate-mark nil)
+ (if (> 0 arg)
+ (sweeprolog--backward-hole)
+ (sweeprolog--forward-hole)))
+
+(defun sweeprolog--hole ()
+ (propertize "_"
+ 'sweeprolog-hole t
+ 'rear-sticky '(sweeprolog-hole)))
+
+(defun sweeprolog-insert-clause (functor arity)
+ (let ((point nil))
+ (combine-after-change-calls
+ (insert "\n" functor)
+ (setq point (point))
+ (when (< 0 arity)
+ (insert "(")
+ (dotimes (_ (1- arity))
+ (insert (sweeprolog--hole) ", "))
+ (insert (sweeprolog--hole) ")"))
+ (insert " :- " (sweeprolog--hole) ".\n"))
+ (goto-char point)
+ (sweeprolog-forward-hole)))
+
+(defun sweeprolog-maybe-insert-next-clause (point kind beg end)
+ (when-let ((current-predicate (and (eq kind 'operator)
+ (string= "." (buffer-substring-no-properties beg end))
+ (cdr (sweeprolog-definition-at-point point))))
+ (functor (car current-predicate))
+ (arity (cadr current-predicate)))
+ (goto-char end)
+ (end-of-line)
+ (sweeprolog-insert-clause functor arity)
+ t))
+
+(defun sweeprolog-maybe-define-predicate (point _kind _beg _end)
+ (when-let ((pred (sweeprolog-identifier-at-point point)))
+ (unless (sweeprolog-predicate-properties pred)
+ (push-mark)
+ (sweeprolog-end-of-predicate-definition)
+ (let ((functor-arity (sweeprolog--mfn-to-functor-arity pred)))
+ (sweeprolog-insert-clause (car functor-arity)
+ (cdr functor-arity)))
+ t)))
+
+(defvar sweeprolog-insert-term-functions
+ '(sweeprolog-maybe-insert-next-clause
+ sweeprolog-maybe-define-predicate)
+ "Hook of functions that insert a Prolog term in a certain context.
+
+Each hook function is called with four arguments describing the
+current context. The first argument, POINT, is the buffer
+position in which insertion should take place. The rest of the
+arguments, KIND, BEG and END, describe the previous non-comment
+Prolog token as returned from `sweeprolog-last-token-boundaries'.")
+
+(defun sweeprolog-insert-term-dwim (&optional point)
+ "Insert an appropriate Prolog term at POINT.
+
+This command calls the functions in
+`sweeprolog-insert-term-functions' one after the other until one
+of them signal success by returning non-nil."
+ (interactive "d" sweeprolog-mode)
+ (setq point (or point (point)))
+ (let* ((bounds (sweeprolog-last-token-boundaries))
+ (kind (car bounds))
+ (beg (cadr bounds))
+ (end (caddr bounds)))
+ (unless (run-hook-with-args-until-success
+ 'sweeprolog-insert-term-functions
+ point kind beg end)
+ (user-error "No term insertion function applies here"))))
+
(defvar sweeprolog-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?_ "_" table)
(define-key map (kbd "C-c C-o") #'sweeprolog-find-file-at-point)
(define-key map (kbd "C-c C-d") #'sweeprolog-document-predicate-at-point)
(define-key map (kbd "C-c C-e") #'sweeprolog-export-predicate)
+ (define-key map (kbd "C-c C-i") #'sweeprolog-forward-hole)
(define-key map (kbd "C-c C-`")
(if (fboundp 'flymake-show-buffer-diagnostics) ;; Flymake 1.2.1+
#'sweeprolog-show-diagnostics
#'flymake-show-diagnostics-buffer))
(define-key map (kbd "C-M-^") #'kill-backward-up-list)
+ (define-key map (kbd "C-M-m") #'sweeprolog-insert-term-dwim)
map)
"Keymap for `sweeprolog-mode'.")
summary))
(fill-paragraph))
+(defun sweeprolog-end-of-predicate-definition ()
+ "Move to the end of the predicate definition at point."
+ (when-let* ((def (sweeprolog-definition-at-point)))
+ (let ((point (point))
+ (fun (cadr def))
+ (ari (caddr def)))
+ (while (and point (not (eobp)))
+ (sweeprolog-end-of-top-term)
+ (if-let* ((ndef (sweeprolog-definition-at-point (point)))
+ (nfun (cadr ndef))
+ (nari (caddr ndef))
+ (same (and (string= fun nfun)
+ (= ari nari))))
+ (setq point (point))
+ (goto-char point)
+ (setq point nil))))))
+
(defun sweeprolog-beginning-of-predicate-at-point (&optional point)
"Find the beginning of the predicate definition at or above POINT.