]> git.eshelyaron.com Git - sweep.git/commitdiff
ADDED: context-based command for inserting new clauses or terms
authorEshel Yaron <me@eshelyaron.com>
Sat, 22 Oct 2022 16:10:08 +0000 (19:10 +0300)
committerEshel Yaron <me@eshelyaron.com>
Sat, 22 Oct 2022 16:10:08 +0000 (19:10 +0300)
* sweeprolog.el (sweeprolog--forward-hole, sweeprolog--backward-hole):
new functions.
(sweeprolog-forward-hole): new command.
(sweeprolog-end-of-predicate-definition,sweeprolog-insert-clause,
sweeprolog-maybe-insert-next-clause,
sweeprolog-maybe-define-predicate): new functions.
(sweeprolog-insert-term-functions): new hook.
(sweeprolog-insert-term-dwim): new command.
(sweeprolog-mode-map): bind new commands.
* sweeprolog-tests.el (dwim-define-predicate): new test case.
* README.org (Context-Based Term Insertion): new section.

NEWS.org
README.org
sweeprolog-tests.el
sweeprolog.el

index 0284cfd43e3bf2ed5977c2fcb443851a48faac22..67adb807e97dbb78034979d8f8d9b5b2c3bca5bd 100644 (file)
--- a/NEWS.org
+++ b/NEWS.org
@@ -11,6 +11,25 @@ SWI-Prolog in Emacs.
 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
index d5b65968f7a4ed1e3009e0cc1e56f106c462952c..567a34a66942f5cd22e9de36f15f496ec41728a0 100644 (file)
@@ -895,6 +895,80 @@ the non-exported predicates defined in the current buffer.  To force
 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
@@ -1344,10 +1418,6 @@ there some further improvements that we want to pursue:
   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
index a51afcc3b4fc342c16fc8a0e16024015ff1a8e8d..6c872629ceeb03d3e035709bd2b27723c824a44b 100644 (file)
   (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
index be81643c93fcd2e14df00bfecff34691a61a6e4f..5d611afa9c858741c3a2dce58c3453a0973564d3 100644 (file)
@@ -2014,6 +2014,100 @@ Interactively, PROJ is the prefix argument."
         (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)
@@ -2039,11 +2133,13 @@ Interactively, PROJ is the prefix argument."
     (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'.")
 
@@ -2580,6 +2676,23 @@ Interactively, PROJ is the prefix argument."
                   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.