]> git.eshelyaron.com Git - sweep.git/commitdiff
ADDED: New command sweeprolog-insert-term-with-holes
authorEshel Yaron <me@eshelyaron.com>
Thu, 22 Dec 2022 14:51:47 +0000 (16:51 +0200)
committerEshel Yaron <me@eshelyaron.com>
Sun, 25 Dec 2022 17:16:42 +0000 (19:16 +0200)
* sweep.pl (sweep_format_term/2, sweep_current_functors/2): new
predicates.
* sweeprolog.el (sweeprolog-read-functor-history): new variable,
history list for...
(sweeprolog-read-functor): new function.
(sweeprolog-forward-hole-repeat-mode): rename to...
(sweeprolog-forward-hole-repeat-map): corrected name for keymap.
(sweeprolog-analyze-end-font-lock): don't rely on
sweeprolog--next-hole to move point.
(sweeprolog-at-hole-p, sweeprolog-beginning-of-hole)
(sweeprolog-end-of-hole): new functions, used by...
(sweeprolog--previous-hole, sweeprolog--next-hole): revise.
(sweeprolog--precedence-at-point): new function, used by...
(sweeprolog-insert-term-with-holes): new command.
(sweeprolog-mode-map): bind it to C-c C-m
* sweeprolog-tests: test it.
* README.org ("Filling Holes"): move to top and rename to...
("Holes"): document sweeprolog-insert-term-with-holes.

README.org
sweep.pl
sweeprolog-tests.el
sweeprolog.el

index 7515e0727301f5289970861a46fa6c631af6cc43..634a90aaf800d051a2af32bbb9ca348307d4d3c1 100644 (file)
@@ -781,6 +781,105 @@ directly:
       goal2.
 #+end_src
 
+** Holes
+:PROPERTIES:
+:CUSTOM_ID: holes
+:DESCRIPTION: Commands for finding and filling holes for interactive term insertion
+:ALT_TITLE: Holes
+:END:
+
+#+CINDEX: holes
+When writing Prolog code in the usual way of typing in one character
+at a time, the buffer text is often found in a syntactically incorrect
+state while you edit it.  This happens for example right after you
+insert an infix operator, before typing its expected right-hand side
+argument.  ~sweep~ provides an alternative method for inserting Prolog
+terms in a way that maintains the syntactic correctness of the buffer
+text while allowing the user to incrementally refine it by using
+placeholder terms, called simply "holes".  Holes indicate the location
+of missing terms that the user can later fill in, essentially they
+represent source-level unknown terms and their presence satisfies the
+Prolog parser.  Holes are written in the buffer as regular Prolog
+variables, but they are annotated with a special text property[fn:2]
+that allows ~sweep~ to recognize them as holes needed to be filled.
+
+#+FINDEX: sweeprolog-insert-term-with-holes
+#+KINDEX: C-c C-m
+#+KINDEX: C-c RET
+The main command for inserting terms with holes is ~M-x
+sweeprolog-insert-term-with-holes~.  This command, bound by default to
+~C-c C-m~ (or ~C-c RET~) in ~sweeprolog-mode~ buffers, prompts for a functor
+and an arity and inserts a corresponding term with holes in place of
+the term's arguments.  It leaves point right after the first hole,
+sets the mark to its start and activates region such that the hole is
+marked.  Call ~sweeprolog-insert-term-with-holes~ again to replace the
+active region which now covers the first hole with another term, that
+may again contain further holes.  That way you can incrementally write
+down a Prolog term, including whole clauses, by working down the
+syntactic structure of the term and maintaining its all the while.
+Without a prefix argument, ~sweeprolog-insert-term-with-holes~ prompts
+for the functor and the arity to use.  A non-negative prefix argument,
+e.g. ~C-2 C-c C-m~ or ~C-u C-c C-m~, is taken as to be the inserted term's
+arity and in this case ~sweeprolog-insert-term-with-holes~ only prompts
+for the functor to insert.  A negative prefix argument, ~C-- C-c C-m~,
+inserts only a single hole without prompting for a functor.  To
+further help with keeping the buffer syntactically correct, this
+command adds a comma (~,~) before or after the inserted term when needed
+according to the surrounding tokens.  If you call it at the end of a
+term that doesn't have a closing fullstop, it adds the fullstop after
+the inserted term.
+
+Several other ~sweep~ commands insert holes in place of unknown terms,
+including ~C-M-i~ (see [[#code-completion][Code Completion]]), ~C-M-m~ (see [[#insert-term-at-point][Context-Based Term
+Insertion]]) and ~M-x sweeprolog-plunit-testset-skeleton~ (see [[#writing-tests][Writing
+Tests]]).
+
+#+VINDEX: sweeprolog-highlight-holes
+When the user option ~sweeprolog-highlight-holes~ is set to non-nil,
+holes in Prolog buffers are highlighted with a dedicated face, making
+them easily distinguishable from regular Prolog variables.  Hole
+highlighting is enabled by default, to disable it customize
+~sweeprolog-highlight-holes~ to nil.
+
+#+FINDEX: sweeprolog-backward-hole
+#+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
+~M-x sweeprolog-forward-hole~, bound by default to ~C-c TAB~ (or ~C-c C-i~).
+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, use ~sweeprolog-backward-hole~ or call
+~sweeprolog-forward-hole~ with a negative prefix argument (~C-- C-c TAB~).
+
+#+FINDEX: sweeprolog-forward-hole-on-tab-mode
+#+KINDEX: TAB (sweeprolog-forward-hole-on-tab-mode)
+#+KINDEX: C-i (sweeprolog-forward-hole-on-tab-mode)
+When the minor mode ~sweeprolog-forward-hole-on-tab-mode~ is enabled,
+the ~TAB~ key is bound to a command moves to the next hole when called
+in a properly indented line (otherwise it indents the line).  This
+makes moving between holes in the buffer easier since ~TAB~ can be used
+instead of ~C-c TAB~ in most cases.  To enable this mode in a Prolog
+buffer, type ~M-x sweeprolog-forward-hole-on-tab-mode-map~.  This step
+can be automated by adding ~sweeprolog-forward-hole-on-tab-mode~ to
+~sweeprolog-mode-hook~:
+
+#+begin_src emacs-lisp
+  (add-hook 'sweeprolog-mode-hook #'sweeprolog-forward-hole-on-tab-mode)
+#+end_src
+
+To "fill" a hole marked by one of the aforementioned commands, either
+use ~C-c C-m~ as described above or type ~C-w~ (~M-x kill-region~) to kill
+the region and remove the placeholder variable, and 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 you insert a character while the region is
+active (see also [[info:emacs#Using Region][Using Region in the Emacs manual]]).
+
+[fn:2] see [[info:elisp#Text Properties][Text Properties in the Elisp manual]]
+
 ** Definitions and references
 :PROPERTIES:
 :CUSTOM_ID: sweeprolog-xref
@@ -1096,7 +1195,7 @@ In ~sweeprolog-mode~ buffers, the following enhancements are provided:
   ~completion-at-point~ suggests matching predicates as completion
   candidates.  Predicate calls are inserted as complete term.  If the
   chosen predicate takes arguments, holes are inserted in their places
-  (see [[#filling-holes][Filling Holes]]).
+  (see [[#holes][Holes]]).
 - Atom completion :: If point is at a non-callable,
   ~completion-at-point~ suggests matching atoms as completion
   candidates.
@@ -1142,70 +1241,8 @@ functions, in order:
   ~sweeprolog-new-predicate-location-function~ can be customized to
   control where this function inserts new predicate definitions.
 
-*** Filling Holes
-:PROPERTIES:
-:CUSTOM_ID: filling-holes
-:DESCRIPTION: Commands for finding and filling holes for interactive term insertion
-:ALT_TITLE: Filling Holes
-: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, called simply "holes", represent
-the Prolog terms that remain to be given by the user.  Holes are
-written in the buffer as regular Prolog variables, but they are
-annotated with a special text property[fn:2] 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.
-
-#+VINDEX: sweeprolog-highlight-holes
-When the user option ~sweeprolog-highlight-holes~ is set to non-nil,
-holes in Prolog buffers are highlighted with a dedicated face, making
-them easily distinguishable from regular Prolog variables.  Hole
-highlighting is enabled by default, to disable it customize
-~sweeprolog-highlight-holes~ to nil.
-
-#+FINDEX: sweeprolog-backward-hole
-#+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
-~M-x sweeprolog-forward-hole~, bound by default to ~C-c TAB~ (or ~C-c C-i~).
-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, use ~sweeprolog-backward-hole~ or call
-~sweeprolog-forward-hole~ with a negative prefix argument (~C-- C-c TAB~).
-
-#+FINDEX: sweeprolog-forward-hole-on-tab-mode
-#+KINDEX: TAB (sweeprolog-forward-hole-on-tab-mode)
-#+KINDEX: C-i (sweeprolog-forward-hole-on-tab-mode)
-When the minor mode ~sweeprolog-forward-hole-on-tab-mode~ is enabled,
-the ~TAB~ key is bound to a command moves to the next hole when called
-in a properly indented line (otherwise it indents the line).  This
-makes moving between holes in the buffer easier since ~TAB~ can be used
-instead of ~C-c TAB~ in most cases.  To enable this mode in a Prolog
-buffer, type ~M-x sweeprolog-forward-hole-on-tab-mode-map~.  This step
-can be automated by adding ~sweeprolog-forward-hole-on-tab-mode~ to
-~sweeprolog-mode-hook~:
-
-#+begin_src emacs-lisp
-  (add-hook 'sweeprolog-mode-hook #'sweeprolog-forward-hole-on-tab-mode)
-#+end_src
-
-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:2] see [[info:elisp#Text Properties][Text Properties in the Elisp manual]]
+This command inserts holes as placeholders for the body term and the
+head's arguments, if any.  See also [[#holes][Holes]].
 
 ** Writing Tests
 :PROPERTIES:
@@ -1234,9 +1271,9 @@ test() :- TestBody.
 #+end_src
 
 The cursor is left between the parentheses of the ~test()~ head term,
-and the ~TestBody~ variable is marked as a hole (see [[#filling-holes][Filling Holes]]).  To
-insert another unit test, place point after a complete test case and
-type ~C-M-m~ or ~M-RET~ to invoke ~sweeprolog-insert-term-dwim~ (see
+and the ~TestBody~ variable is marked as a hole (see [[#holes][Holes]]).  To insert
+another unit test, place point after a complete test case and type
+~C-M-m~ or ~M-RET~ to invoke ~sweeprolog-insert-term-dwim~ (see
 [[#insert-term-at-point][Context-Based Term Insertion]]).
 
 [fn:3] See [[https://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/plunit.html%27)][Prolog Unit Tests in the SWI-Prolog manual]].
index 91a4e69c5e2c999416479b40afd6e591600ac4fd..2c1d338e0f47be1399b44f917e1b5b44104570cc 100644 (file)
--- a/sweep.pl
+++ b/sweep.pl
@@ -71,7 +71,9 @@
             sweep_string_to_atom/2,
             sweep_file_path_in_library/2,
             sweep_file_missing_dependencies/2,
-            sweep_format_head/2
+            sweep_format_head/2,
+            sweep_format_term/2,
+            sweep_current_functors/2
           ]).
 
 :- use_module(library(pldoc)).
@@ -945,3 +947,28 @@ sweep_format_head([F0|A], R) :-
     pi_head(F/A, H),
     sweep_current_module(M),
     sweep_format_predicate(M, 0, H, R).
+
+sweep_format_term([F0,N,P], [S|SP]) :-
+    atom_string(F, F0),
+    pi_head(F/N, H),
+    length(NamedArgs, N),
+    maplist(=('$VAR'('_')), NamedArgs),
+    H =.. [F|NamedArgs],
+    term_string(H, S, [quoted(true),
+                       character_escapes(true),
+                       spacing(next_argument),
+                       numbervars(true),
+                       priority(P)]),
+    term_string(_, S, [subterm_positions(SP)]).
+
+sweep_current_functors(A0, Col) :-
+    (   A0 == []
+    ->  true
+    ;   A = A0
+    ),
+    findall([F|A],
+            (   current_functor(F0, A),
+                atom(F0),
+                atom_string(F0, F)
+            ),
+            Col).
index 1210a5b26fc6a4dc0b891c314b5c9cd1a51c33e0..c27eb36c094a427162484a9f6bf2c6ad9eba235c 100644 (file)
@@ -181,6 +181,26 @@ foo(Foo) :- bar.
                    '(sweeprolog-undefined-default-face
                      sweeprolog-body-default-face)))))
 
+(ert-deftest insert-term-with-holes ()
+  "Test `sweeprolog-insert-term-with-holes'."
+  (let ((temp (make-temp-file "sweeprolog-test"
+                              nil
+                              "pl"
+                              "")))
+    (find-file-literally temp)
+    (sweeprolog-mode)
+    (sweeprolog-insert-term-with-holes ":-" 2)
+    (call-interactively #'kill-region)
+    (sweeprolog-insert-term-with-holes "foo" 3)
+    (call-interactively #'kill-region)
+    (sweeprolog-insert-term-with-holes "bar" 0)
+    (call-interactively #'kill-region)
+    (sweeprolog-insert-term-with-holes ";" 2)
+    (call-interactively #'kill-region)
+    (sweeprolog-insert-term-with-holes "->" 2)
+    (should (string= (buffer-string)
+                     "foo(bar, (_->_;_), _):-_."))))
+
 (ert-deftest plunit-testset-skeleton ()
   "Tests inserting PlUnit test-set blocks."
   (let ((temp (make-temp-file "sweeprolog-test"
index 057b164edeeb197b0f0caddce3a169f811f89eee..9f6799d03eb5e63889cd9b0461a5315fc1e0dec4 100644 (file)
@@ -49,6 +49,8 @@
 
 (defvar sweeprolog-read-module-history nil)
 
+(defvar sweeprolog-read-functor-history nil)
+
 (defvar sweeprolog-top-level-signal-goal-history nil)
 
 (defvar sweeprolog--extra-init-args nil)
@@ -361,13 +363,14 @@ non-terminals)."
 
 (defvar sweeprolog-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "C-c C-l") #'sweeprolog-load-buffer)
     (define-key map (kbd "C-c C-c") #'sweeprolog-analyze-buffer)
-    (define-key map (kbd "C-c C-t") #'sweeprolog-top-level)
-    (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-l") #'sweeprolog-load-buffer)
+    (define-key map (kbd "C-c C-m") #'sweeprolog-insert-term-with-holes)
+    (define-key map (kbd "C-c C-o") #'sweeprolog-find-file-at-point)
+    (define-key map (kbd "C-c C-t") #'sweeprolog-top-level)
     (define-key map (kbd "C-c C-u") #'sweeprolog-update-dependencies)
     (define-key map (kbd "C-c C-`")
                 (if (fboundp 'flymake-show-buffer-diagnostics)  ;; Flymake 1.2.1+
@@ -381,11 +384,12 @@ non-terminals)."
     map)
   "Keymap for `sweeprolog-mode'.")
 
-(defvar sweeprolog-forward-hole-repeat-mode
+(defvar sweeprolog-forward-hole-repeat-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-i") #'sweeprolog-forward-hole)
-    (define-key map (kbd "p") #'sweeprolog-backward-hole)
-    (define-key map (kbd "n") #'sweeprolog-forward-hole)
+    (define-key map (kbd "C-n") #'sweeprolog-forward-hole)
+    (define-key map (kbd "C-p") #'sweeprolog-backward-hole)
+    (define-key map (kbd "C-m") #'sweeprolog-insert-term-with-holes)
     map)
   "Repeat map for \\[sweeprolog-forward-hole].")
 
@@ -867,6 +871,28 @@ depends on the value of the user option
   "Return a list of predicates whose name resembeles PATTERN."
   (sweeprolog--query-once "sweep" "sweep_predicate_apropos" pattern))
 
+(defun sweeprolog-read-functor (&optional arity)
+  "Read a Prolog functor/arity pair from the minibuffer.
+
+If ARITY is nil, prompt for the arity after reading the functor.
+Otherwise, read only the functor, with completion candidates are
+restricted to functors with arity ARITY, and return ARITY as the
+arity.
+
+Return a cons cell of the functor as a string and the arity."
+  (let* ((col (sweeprolog--query-once "sweep" "sweep_current_functors"
+                                      arity))
+         (completion-extra-properties
+          (list :annotation-function
+                (lambda (key)
+                  (when-let ((val (cdr (assoc-string key col))))
+                    (concat "/" (number-to-string val)))))))
+    (let ((functor (completing-read "Functor: "
+                                    col nil nil nil
+                                    'sweeprolog-read-functor-history)))
+      (cons functor
+            (or arity (read-number (concat functor "/")))))))
+
 (defun sweeprolog-read-predicate (&optional prompt)
   "Read a Prolog predicate from the minibuffer with prompt PROMPT.
 If PROMPT is nil, `sweeprolog-read-predicate-prompt' is used by
@@ -2091,12 +2117,15 @@ resulting list even when found in the current clause."
           (narrow-to-region beg end)
           (let ((hole (sweeprolog--next-hole)))
             (while hole
-              (font-lock--add-text-property (car hole) (cdr hole)
-                                            'font-lock-face
-                                            (sweeprolog-hole-face)
-                                            (current-buffer)
-                                            nil)
-              (setq hole (sweeprolog--next-hole)))))))))
+              (let ((hbeg (car hole))
+                    (hend (cdr hole)))
+                (font-lock--add-text-property hbeg hend
+                                              'font-lock-face
+                                              (sweeprolog-hole-face)
+                                              (current-buffer)
+                                              nil)
+                (goto-char hend)
+                (setq hole (sweeprolog--next-hole))))))))))
 
 (defun sweeprolog-analyze-start-flymake (&rest _)
   (flymake-start))
@@ -2625,64 +2654,79 @@ Interactively, POINT is set to the current point."
       (or (re-search-forward (rx "." (or white "\n")) nil t)
           (goto-char (point-max))))))
 
+(defun sweeprolog-at-hole-p (&optional point)
+  (setq point (or point (point)))
+  (get-text-property point 'sweeprolog-hole))
+
+(defun sweeprolog-beginning-of-hole (&optional point)
+  (let ((beg (or point (point))))
+    (when (sweeprolog-at-hole-p beg)
+      (while (and (< (point-min) beg)
+                  (sweeprolog-at-hole-p (1- beg)))
+        (setq beg (1- beg)))
+      beg)))
+
+(defun sweeprolog-end-of-hole (&optional point)
+  (let ((end (or point (point))))
+    (when (sweeprolog-at-hole-p end)
+      (while (and (< end (point-max))
+                  (sweeprolog-at-hole-p (1+ end)))
+        (setq end (1+ end)))
+      (1+ end))))
+
 (defun sweeprolog--next-hole (&optional wrap)
   "Return the bounds of the next hole in the current buffer.
 
 When WRAP in non-nil, wrap around if no holes are found between
 point and the end of the buffer."
-  (let ((current-hole-beg
-         (save-excursion
-           (while (and (get-text-property (point) 'sweeprolog-hole)
-                       (not (bobp)))
-             (forward-char -1))
-           (point))))
-    (while (and (get-text-property (point) 'sweeprolog-hole)
-                (not (eobp)))
-      (forward-char))
-    (while (not (or (get-text-property (point) 'sweeprolog-hole)
-                    (eobp)))
-      (forward-char))
-    (if (eobp)
-        (when wrap
-          (save-restriction
-            (goto-char (point-min))
-            (narrow-to-region (point) current-hole-beg)
-            (sweeprolog--next-hole)))
-      (let ((beg (point)))
-        (while (and (get-text-property (point) 'sweeprolog-hole)
-                    (not (eobp)))
-          (forward-char))
-        (cons beg (point))))))
+  (if-let ((current-hole-beg (sweeprolog-beginning-of-hole)))
+      (cons current-hole-beg
+            (sweeprolog-end-of-hole))
+    (let ((point (point)))
+      (while (not (or (sweeprolog-at-hole-p) (eobp)))
+        (forward-char))
+      (if (eobp)
+          (when wrap
+            (save-restriction
+              (goto-char (point-min))
+              (narrow-to-region (point) point)
+              (sweeprolog--next-hole)))
+        (cons (point)
+              (sweeprolog-end-of-hole))))))
+
+(defun sweeprolog--backward-wrap (&optional wrap)
+  (if (bobp)
+      (when wrap
+        (goto-char (point-max))
+        t)
+    (forward-char -1)
+    t))
 
 (defun sweeprolog--previous-hole (&optional wrap)
   "Return the bounds of the previous hole in the current buffer.
 
 When WRAP in non-nil, wrap around if no holes are found between
 point and the beginning of the buffer."
-  (let ((current-hole-end
-         (save-excursion
-           (while (and (get-text-property (point) 'sweeprolog-hole)
-                       (not (eobp)))
-             (forward-char))
-           (point))))
-    (forward-char -1)
-    (while (and (get-text-property (point) 'sweeprolog-hole)
-                (not (bobp)))
-      (forward-char -1))
-    (while (not (or (get-text-property (point) 'sweeprolog-hole)
-                    (bobp)))
-      (forward-char -1))
-    (if (bobp)
-        (when wrap
-          (save-restriction
-            (goto-char (point-max))
-            (narrow-to-region current-hole-end (point))
-            (sweeprolog--previous-hole)))
-      (let ((end (point)))
-        (while (and (get-text-property (point) 'sweeprolog-hole)
-                    (not (bobp)))
-          (forward-char -1))
-        (cons (1+ (point)) (1+ end))))))
+  (let ((start (point)))
+    (when (use-region-p)
+      (goto-char (region-beginning))
+      (deactivate-mark))
+    (when (sweeprolog--backward-wrap wrap)
+      (if-let ((current-hole-beg (sweeprolog-beginning-of-hole)))
+          (cons current-hole-beg
+                (sweeprolog-end-of-hole))
+        (let ((point (point)))
+          (while (not (or (sweeprolog-at-hole-p) (bobp)))
+            (forward-char -1))
+          (if (bobp)
+              (or (and wrap
+                       (save-restriction
+                         (goto-char (point-max))
+                         (narrow-to-region point (point))
+                         (sweeprolog--previous-hole)))
+                  (and (goto-char start) nil))
+            (cons (sweeprolog-beginning-of-hole)
+                  (sweeprolog-end-of-hole))))))))
 
 (defun sweeprolog--forward-hole (&optional wrap)
   (if-let ((hole (sweeprolog--next-hole wrap))
@@ -2725,11 +2769,15 @@ instead."
 
 (put 'sweeprolog-backward-hole
      'repeat-map
-     'sweeprolog-forward-hole-repeat-mode)
+     'sweeprolog-forward-hole-repeat-map)
 
 (put 'sweeprolog-forward-hole
      'repeat-map
-     'sweeprolog-forward-hole-repeat-mode)
+     'sweeprolog-forward-hole-repeat-map)
+
+(put 'sweeprolog-insert-term-with-holes
+     'repeat-map
+     'sweeprolog-forward-hole-repeat-map)
 
 (defun sweeprolog--hole (&optional string)
   (propertize (or string "_")
@@ -2738,6 +2786,104 @@ instead."
                                 cursor-sensor-functions
                                 font-lock-face)))
 
+(defun sweeprolog--precedence-at-point (&optional point)
+  (setq point (or point (point)))
+  (pcase (sweeprolog-last-token-boundaries point)
+    ((or `(operator ,obeg ,oend)
+         `(symbol   ,obeg ,oend))
+     (let ((op (buffer-substring-no-properties obeg oend)))
+       (or (and (string= "." op)
+                (or (not (char-after (1+ obeg)))
+                    (member (char-syntax (char-after (1+ obeg)))
+                            '(?> ? )))
+                1200)
+           (sweeprolog-op-infix-precedence op)
+           (sweeprolog-op-prefix-precedence op)
+           1200)))
+    (_ 1200)))
+
+(defun sweeprolog-insert-term-with-holes (functor arity)
+  "Insert a term with functor FUNCTOR and arity ARITY.
+
+If ARITY is negative, just insert a single hole at point.
+Otherwise, insert FUNCTOR along with ARITY holes, one for each of
+the term's arguments.
+
+Interactively, prompt for FUNCTOR.  Without a prefix argument,
+prompt for ARITY as well.  Otherwise, ARITY is the numeric value
+of the prefix argument."
+  (interactive
+   (let* ((arity (and current-prefix-arg
+                      (prefix-numeric-value current-prefix-arg))))
+     (if (and (numberp arity)
+              (< arity 0))
+         (list "_" arity)
+       (let ((functor-arity (sweeprolog-read-functor arity)))
+         (list (car functor-arity) (cdr functor-arity))))))
+  (combine-after-change-calls
+    (when (use-region-p)
+      (delete-region (region-beginning)
+                     (region-end)))
+    (let* ((beg (point)))
+      (when
+          (pcase (sweeprolog-last-token-boundaries beg)
+            (`(symbol ,obeg ,oend)
+             (let ((op (buffer-substring-no-properties obeg oend)))
+               (not (or (sweeprolog-op-infix-precedence op)
+                        (sweeprolog-op-prefix-precedence op)))))
+            (`(close . ,_) t))
+        (insert ", "))
+      (if (<= 0 arity)
+          (let ((term-format
+                 (sweeprolog--query-once
+                  "sweep" "sweep_format_term"
+                  (list functor arity
+                        (sweeprolog--precedence-at-point beg)))))
+            (insert (car term-format))
+            (pcase  (cdr term-format)
+              ((or `(compound
+                     "term_position"
+                     0 ,length
+                     ,_ ,_
+                     ,holes)
+                   `(compound
+                     "parentheses_term_position"
+                     0 ,length
+                     (compound
+                      "term_position"
+                      ,_ ,_ ,_ ,_
+                      ,holes)))
+               (with-silent-modifications
+                 (dolist (hole holes)
+                   (pcase hole
+                     (`(compound "-" ,hbeg ,hend)
+                      (add-text-properties
+                       (- (point) length (- hbeg))
+                       (- (point) length (- hend))
+                       (list
+                        'sweeprolog-hole t
+                        'font-lock-face (list (sweeprolog-hole-face))
+                        'rear-nonsticky '(sweeprolog-hole
+                                          cursor-sensor-functions
+                                          font-lock-face))))))))))
+        (insert (sweeprolog--hole functor)))
+      (pcase (sweeprolog-next-token-boundaries)
+        ('nil (insert "."))
+        (`(,kind ,obeg ,oend)
+         (if (save-excursion
+               (goto-char obeg)
+               (sweeprolog-at-beginning-of-top-term-p))
+             (insert ".")
+           (when (or (and (member kind '(symbol operator))
+                          (let ((op (buffer-substring-no-properties obeg oend)))
+                            (not (or (sweeprolog-op-infix-precedence op)
+                                     (sweeprolog-op-suffix-precedence op)))))
+                     (member kind '(open functor)))
+             (insert ", ")))))
+      (unless (= 0 arity)
+        (goto-char beg))
+      (sweeprolog-forward-hole))))
+
 (defun sweeprolog-insert-clause (functor arity &optional neck module)
   (let ((point (point))
         (neck (or neck ":-"))