diff --git a/tempel-tempo-tests.el b/tempel-tempo-tests.el new file mode 100644 index 0000000..3cf290d --- /dev/null +++ b/tempel-tempo-tests.el @@ -0,0 +1,262 @@ +;;; tempel-tempo-tests.el --- Test suite for tempo.el compatibility -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Free Software Foundation, Inc. + +;; Author: Elias Gabriel Pérez +;; Keywords: abbrev, languages, tools, text + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Code: + +(require 'tempel-tempo) +(eval-when-compile (require 'cl-lib)) + +(ert-deftest tempel-tempo-string-element-test () + "Test a template containing a string element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("GNU Emacs Tempo test")) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "GNU Emacs Tempo test")))) + +(ert-deftest tempel-tempo-p-bare-element-test () + "Test a template containing a bare `p' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("abcde" p)) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (tempel-tempo-forward-mark) + (should (equal (point) 6)))) + +(ert-deftest tempel-tempo-r-bare-element-test () + "Test a template containing a bare `r' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("abcde" r "ghijk")) + (insert "F") + (set-mark (point)) + (goto-char (point-min)) + (tempel-tempo-insert-template 'tempel-tempo-template-test t) + (should (equal (buffer-string) "abcdeFghijk")))) + +(ert-deftest tempel-tempo-p-element-test () + "Testing template containing a `p' (prompt) element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("hello " (p ">"))) + (let ((tempel-tempo-interactive t)) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "world"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil)) + (should (equal (buffer-string) "hello world"))))) + +(ert-deftest tempel-tempo-P-element-test () + "Testing template containing a `P' (prompt) element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("hello " (P ">"))) + ;; By default, `tempo-interactive' is nil, `P' should ignore this. + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "world"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil)) + (should (equal (buffer-string) "hello world")))) + +(ert-deftest tempel-tempo-r-element-test () + "Testing template containing an `r' (with prompt) element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("abcde" (r ">") "ghijk")) + (let ((tempel-tempo-interactive t)) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "F"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil)) + (should (equal (buffer-string) "abcdeFghijk"))))) + +(ert-deftest tempel-tempo-s-element-test () + "Testing template containing an `s' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("hello " (p ">" P1) " " (s P1))) + (let ((tempel-tempo-interactive t)) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) "world!"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil)) + (should (equal (buffer-string) "hello world! world!"))))) + +(ert-deftest tempel-tempo-&-element-test () + "Testing template containing an `&' element." + (tempel-tempo-define-template "test" '(& "test")) + (with-temp-buffer + (insert " ") + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) " test"))) + (with-temp-buffer + (insert "hello") + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "hello\ntest")))) + +(ert-deftest tempel-tempo-%-element-test () + "Testing template containing an `%' element." + (tempel-tempo-define-template "test" '("test" %)) + (with-temp-buffer + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "test"))) + (with-temp-buffer + (insert "hello") + (goto-char (point-min)) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "test\nhello")))) + +(ert-deftest tempel-tempo-n-element-test () + "Testing template containing an `n' element." + (tempel-tempo-define-template "test" '("test" n "test")) + (with-temp-buffer + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "test\ntest")))) + +(ert-deftest tempel-tempo-n>-element-test () + "Testing template containing an `n>' element." + (tempel-tempo-define-template "test" '("(progn" n> "(list 1 2 3))")) + (with-temp-buffer + (emacs-lisp-mode) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + ;; Tempo should have inserted two spaces before (list 1 2 3) + (should (equal (buffer-string) "(progn\n (list 1 2 3))")))) + +(ert-deftest tempel-tempo->-element-test () + "Testing template containing a `>' element." + (with-temp-buffer + (emacs-lisp-mode) + (insert "(progn\n)") + (backward-char) + (tempel-tempo-define-template "test" '("(list 1 2 3)" >)) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + ;; Tempo should have inserted two spaces before (list 1 2 3) + (should (equal (buffer-string) "(progn\n (list 1 2 3))")))) + +(ert-deftest tempel-tempo-r>-bare-element-test () + "Testing template containing a bare `r>' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("(progn" n r> ")")) + (emacs-lisp-mode) + (insert "(list 1 2 3)") + (set-mark (point)) + (goto-char (point-min)) + (tempel-tempo-insert-template 'tempel-tempo-template-test t) + ;; Tempo should have inserted two spaces before (list 1 2 3) + (should (equal (buffer-string) "(progn\n (list 1 2 3))")))) + +(ert-deftest tempel-tempo-r>-element-test () + "Testing template containing an `r>' (with prompt) element." + (tempel-tempo-define-template "test" '("(progn" n (r> ":") ")")) + (with-temp-buffer + ;; Test on-region use + (emacs-lisp-mode) + (insert "(list 1 2 3)") + (set-mark (point)) + (goto-char (point-min)) + (tempel-tempo-insert-template 'tempel-tempo-template-test t) + (should (equal (buffer-string) "(progn\n (list 1 2 3))"))) + (with-temp-buffer + ;; Test interactive use + (emacs-lisp-mode) + (let ((tempel-tempo-interactive t)) + (cl-letf (((symbol-function 'read-string) (lambda (&rest _) " (list 1 2 3)"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil)) + (should (equal (buffer-string) "(progn\n (list 1 2 3))"))))) + +(ert-deftest tempel-tempo-o-element-test () + "Testing template containing an `o' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("test" o)) + (insert "hello") + (goto-char (point-min)) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "test\nhello")) + (should (equal (point) 5)))) + +(ert-deftest tempel-tempo-nil-element-test () + "Testing template with nil elements." + (with-temp-buffer + (tempel-tempo-define-template "test" '("Hello," nil " World!")) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "Hello, World!")))) + +(ert-deftest tempel-tempo-eval-element-test () + "Testing template with Emacs Lisp expressions." + (with-temp-buffer + (tempel-tempo-define-template "test" '((int-to-string (+ 1 1)) "=" (concat "1" "+1"))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "2=1+1")))) + +(ert-deftest tempel-tempo-l-element-test () + "Testing template containing an `l' element." + (with-temp-buffer + (tempel-tempo-define-template "test" '("list: " (l "1, " "2, " (int-to-string (+ 1 2))))) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "list: 1, 2, 3")))) + +(ert-deftest tempel-tempo-tempo-user-elements-test () + "Testing a template with elements for `tempo-user-elements'." + (with-temp-buffer + (make-local-variable 'tempo-user-elements) + (add-to-list 'tempel-user-elements (lambda (x) (int-to-string (* x x)))) + (tempel-tempo-define-template "test" '(1 " " 2 " " 3 " " 4)) + (tempel-tempo-insert-template 'tempel-tempo-template-test nil) + (should (equal (buffer-string) "1 4 9 16")))) + +(ert-deftest tempel-tempo-expand-tag-test () + "Testing expansion of a template with a tag." + (with-temp-buffer + (tempel-tempo-define-template "test" '("Hello, World!") "hello") + (insert "hello") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "Hello, World!")))) + +(ert-deftest tempel-tempo-define-tag-globally-test () + "Testing usage of a template tag defined from another buffer." + (tempel-tempo-define-template "test" '("Hello, World!") "hello") + + (with-temp-buffer + ;; Use a tag in buffer 1 + (insert "hello") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "Hello, World!")) + (erase-buffer) + + ;; Define a tag on buffer 2 + (with-temp-buffer + (tempel-tempo-define-template "test2" '("Now expanded.") "mytag")) + + ;; I should be able to use this template back in buffer 1 + (insert "mytag") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "Now expanded.")))) + +(ert-deftest tempel-tempo-overwrite-tag-test () + "Testing ability to reassign templates to tags." + (with-temp-buffer + ;; Define a tag and use it + (tempel-tempo-define-template "test-tag-1" '("abc") "footag") + (insert "footag") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "abc")) + (erase-buffer) + + ;; Define a new template with the same tag + (tempel-tempo-define-template "test-tag-2" '("xyz") "footag") + (insert "footag") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "xyz")))) + +(ert-deftest tempel-tempo-expand-partial-tag-test () + "Testing expansion of a template with a tag, with a partial match." + (with-temp-buffer + (tempel-tempo-define-template "test" '("Hello, World!") "hello") + (insert "hel") + (tempel-tempo-complete-tag) + (should (equal (buffer-string) "Hello, World!")))) + +(provide 'tempel-tempo-tests) +;;; tempel-tempo-tests.el ends here diff --git a/tempel-tempo.el b/tempel-tempo.el new file mode 100644 index 0000000..091a66f --- /dev/null +++ b/tempel-tempo.el @@ -0,0 +1,310 @@ +;;; tempel-tempo.el --- Tempo Compatibility layer -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Free Software Foundation, Inc. + +;; Author: Elias Gabriel Pérez +;; Keywords: abbrev, languages, tools, text + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This package provides ports of some tempo functions and variables. + +;;; Code: + +(require 'tempel) + + +;;; User Options + +(defgroup tempel nil + "Flexible template insertion." + :prefix "tempo-" + :group 'tools + :group 'tempel) + +;; NOTE: `tempel-tempo-insert-region' and `tempel-tempo-interactive' +;; must have effect only in `tempo-complete-tag' and +;; `tempo-expand-if-complete'. + +;; counterpart of `tempo-interactive' +(defcustom tempel-tempo-interactive nil + "Prompt user for strings in templates. +If this variable is non-nil, tempel prompts the user for text to insert +in the templates." + :type 'boolean) + +;; counterpart of `tempo-insert-region' +(defcustom tempel-tempo-insert-region nil + "Automatically insert current region when there is a `r' in the template. +If this variable is nil, `r' elements will be treated just like `p' +elements, unless the template function is given a prefix (or a non-nil +argument). If this variable is non-nil, the behavior is reversed." + :type 'boolean) + +;; counterpart of `tempo-show-completion-buffer' +(defcustom tempel-tempo-show-completion-buffer t + "If non-nil, show a buffer with possible completions. +This only have effect in `tempel-tempo-complete-tag'." + :type 'boolean) + +;; counterpart of `tempo-leave-completion-buffer' +;; TODO: How to implement this, deprecate it instead? +(defcustom tempel-tempo-leave-completion-buffer nil + "Whether the completion buffer should be closed. +If nil, a completion buffer generated by \\[tempel-complete-tag] +disappears at the next keypress; otherwise, it remains forever. + +This only have effect in `tempel-tempo-complete-tag'." + :type 'boolean) + + +;;; Variables + +(defvar tempel-tempo-tags nil + "An association list with tags and corresponding templates.") + +(defvar-local tempel-tempo-dirty-collection + "Indicates if the tag collection needs to be rebuilt.") + +(make-obsolete-variable + 'tempel-tempo-dirty-collection + "This variable is not used anymore." "31.1") + +(defvar-local tempel-tempo-local-tags nil + "A list of locally installed tag completion lists. +`tempel-tempo-tags' is always in the last position in this list.") + + +;;; Functions and Macros + +;; Not from tempo.el +(defmacro tempel-tempo--progn (&rest body) + "Used to provide tempo compatibility to some functions. +This acts like `progn', with BODY." + `(cl-letf (((symbol-function #'tempel--region) + (lambda () + (when (and (use-region-p) + tempel-tempo-insert-region) + (when (< (mark) (point)) (exchange-point-and-mark)) + (cons (point-marker) (mark-marker))))) + ((symbol-function #'tempel--placeholder) + (lambda (st &optional prompt name noinsert only-prompt) + (when tempel-tempo-interactive (setq only-prompt t)) + (setq prompt + (cond + ((and (stringp prompt) (or only-prompt noinsert)) + (read-string prompt)) + ((stringp prompt) (propertize prompt 'tempel--default t)) + ;; TEMPEL EXTENSION: Evaluate prompt + (t (eval prompt (cdr st))))) + (if noinsert + (progn (setf (alist-get name (cdr st)) prompt) nil) + (tempel--field st name prompt)) + (deactivate-mark)))) + ,@body)) + +(defalias 'tempel-tempo-user-elements-functions 'tempel-user-elements + "Element handlers for user-defined elements. + +This is an abnormal hook where the functions are called with one argument +(an element in a template) and they should return something to be sent to +`tempel--insert' if they recognize the argument, and nil otherwise.") + +(defalias 'tempel-tempo-insert-string-functions 'tempel-insert-string-functions) + +;; Not from tempo.el +(defun tempel-tempo--get-tag () + "Return which tag to use. +This return `tempel-tempo-local-tags' if it have any template, otherwise +return `tempel-tempo-tags' value." + (append tempel-tempo-local-tags tempel-tempo-tags)) + +(defun tempel-tempo-define-template (name elements &optional tag documentation taglist) + "Define a template. +This function creates a template variable `tempel-tempo-template-NAME' +and an interactive function `tempel-tempo-template-NAME' that inserts +the template at the point. The created function is returned. + +NAME is a string that contains the name of the template, ELEMENTS is a +list of elements in the template, TAG is the tag used for completion, +DOCUMENTATION is the documentation string for the insertion command +created, and TAGLIST (a symbol) is the tag list that TAG (if provided) +should be added to. If TAGLIST is nil and TAG is non-nil, TAG is +added to `tempo-tags'. If TAG already corresponds to a template in +the tag list, modify the list so that TAG now corresponds to the newly +defined template. + +The elements in ELEMENTS can be of several types: + + - A string: It is sent to the hooks in + `tempel-tempo-insert-string-functions', and the result is inserted. + - The symbol `p': Inserts an unnamed placeholder field. + - The symbol `r': Inserts the currently active region. If no region is + active, a placeholder field is inserted. If `tempel-done-on-region' + is non-nil, the template is finished when you jump to the field (see + also `q'). + - (p PROMPT ): If `tempel-tempo-interactive' is + non-nil, the user is prompted in the minibuffer with PROMPT for a + string to be inserted. If the optional parameter NAME is non-nil, + the text is saved for later insertion with the `s' tag. If there + already is something saved under NAME that value is used instead and + no prompting is made. If NOINSERT is provided and non-nil, nothing + is inserted, but text is still saved when a NAME is provided. For + clarity, the symbol `noinsert' should be used as argument. + - (P PROMPT ): Works just like the previous tag, but + forces `tempel-tempo-interactive' to be true. + - (r PROMPT ): Insert region or act like (p ...). + - (s NAME): Inserts text previously read with the (p ..) construct. + Finds the insertion saved under NAME and inserts it. Acts like `p' + if `tempel-tempo-interactive' is nil. + - `&': If there is only whitespace between the line start and point, + nothing happens. Otherwise a newline is inserted. + - `%': If there is only whitespace between point and end of line, + nothing happens. Otherwise a newline is inserted. + - `n': Inserts a newline. + - `>': The line is indented using `indent-according-to-mode'. Note + that you often should place this item after the text you want on + the line. + - `r>': Like `r', but it also indents the region. + - (r> PROMPT ): Like (r ...), but is also indents + the region. + - `n>': Inserts a newline and indents line. + - `o': Like `%' but leaves the point before the newline. + - nil: It is ignored. + - Anything else: Each function in `tempel-tempo-user-element-functions' + is called with it as argument until one of them returns non-nil, and the + result is inserted. If all of them return nil, it is evaluated and + the result is treated as an element to be inserted. One additional + tag is useful for these cases. If an expression returns a list (l + foo bar), the elements after `l' will be inserted according to the + usual rules. This makes it possible to return several elements + from one expression." + (let ((tag-name (intern (concat "tempel-tempo-template-" name))) + (taglist (or taglist 'tempel-tempo-tags))) + (set tag-name elements) + (fset tag-name (lambda (&optional arg only-return) + (:documentation + (or documentation (concat "Insert a " name "."))) + (interactive "*P") + (unless only-return + (tempel--insert + elements + (and (or arg tempel-tempo-insert-region) + (tempel--region)))) + elements)) + (when tag (tempel-tempo-add-tag tag elements taglist)) + tag-name)) + +(defun tempel-tempo-use-tag-list (tag-list) + "Install TAG-LIST to be used for template completion in the current buffer. +TAG-LIST is a symbol whose variable value is a tag list created with +`tempo-add-tag'." + (setq-local tempel-tempo-local-tags (symbol-value tag-list))) + +;; NOTE: It seems this function is internal use only in tempo.el +;; Currently this is only used in tests. +(defun tempel-tempo-insert-template (template on-region) + "Insert a template. +TEMPLATE is the template to be inserted. If ON-REGION is non-nil the +`r' elements are replaced with the current region. In Transient Mark +mode, ON-REGION is ignored and assumed true if the region is active." + (let ((tempel-tempo-insert-region + (or on-region tempel-tempo-insert-region))) + (tempel-tempo--progn + (when (functionp template) + (setq template (funcall template nil :only-return))) + (tempel--insert template (tempel--region))))) + + +;;; Commands + +;;;###autoload +(defalias 'tempel-tempo-forward-mark 'tempel-next + "Jump to the next mark.") + +;;;###autoload +(defalias 'tempel-tempo-backward-mark 'tempel-previous + "Jump to the previous mark.") + +;;;###autoload +(defun tempel-tempo-expand-if-complete () + "Expand the tag before point if it is complete. +Returns non-nil if an expansion was made and nil otherwise. + +This could as an example be used in a command that is bound to the +space bar, and looks something like this: + +\(defun tempel-space () + (interactive \"*\") + (or (tempel-tempo-expand-if-complete) + (insert \" \")))" + (interactive) + (tempel-tempo--progn + (tempel--save) + ;; This is extracted from `tempel-expand', we cannot define this as + ;; alias because `tempel-expand' disables the region inserting. + (if-let* ((templates (tempel--templates)) + (bounds (bounds-of-thing-at-point 'symbol)) + (name (buffer-substring-no-properties + (car bounds) (cdr bounds))) + (sym (intern-soft name)) + (template (assq sym templates))) + (tempel--exit templates (tempel--region) name 'finished) + (user-error "tempel-expand: No matching templates")))) + +;;;###autoload +(defun tempel-tempo-add-tag (tag template &optional tag-list) + "Add a template tag. +Add the TAG, that should complete to TEMPLATE to the list in TAG-LIST, +or to `tempel-tempo-tags' if TAG-LIST is nil. If TAG was already +in the list, replace its template with TEMPLATE." + (interactive "sTag: \nCTemplate: ") + (setq tag (intern tag)) + (unless tag-list (setq tag-list 'tempel-tempo-tags)) + + (when (functionp template) + (setq template (funcall template nil :only-return))) + + (let ((place (assoc tag (symbol-value tag-list)))) + (if place + ;; Tag is already in the list, assign a new template to it. + (setcdr place template) + ;; Tag is not present in the list, add as a new template + (add-to-list tag-list `(,tag ,@template))))) + +;;;###autoload +(defun tempel-tempo-complete-tag (&optional _silent) + "Look for a tag and expand it. +If a single match is found, the corresponding template is expanded in +place of the matching string. + +If a partial completion is found and `tempel-tempo-show-completion-buffer' is +non-nil, a buffer containing possible completions is displayed." + (interactive "*") + (tempel-tempo--progn + (tempel--save) + (if tempel-tempo-show-completion-buffer ;; TODO: (?) + ;; This let is for enable the region inserting + (let ((this-command #'tempel-complete)) + (tempel-complete :interactive)) + (tempel-tempo-expand-if-complete)))) + + + +(add-hook 'tempel-template-sources #'tempel-tempo--get-tag) + +(provide 'tempel-tempo) +;;; tempel-tempo.el ends here diff --git a/tempel.el b/tempel.el index 57e21b9..314e403 100644 --- a/tempel.el +++ b/tempel.el @@ -82,6 +82,15 @@ The functions take a template element as argument and must return either nil or a new template element, which is subsequently evaluated." :type 'hook) +(defcustom tempel-insert-string-functions nil + "List of functions to run when inserting a string. +Each function is called with a single argument, STRING and should return +another string. This could be used for making all strings upcase by +setting it to (upcase), for example. + +Alternatively, the value can be a function." + :type '(choice (const nil) function hook)) + (defcustom tempel-template-sources (list #'tempel-path-templates) "List of template sources. @@ -358,6 +367,23 @@ Return the added field." `(with-demoted-errors "Tempel Error: %S" ,@body)) +(defun tempel--process-and-insert-string (string) + "Insert a STRING from a template. +Run a string through the pre-processors in +`tempo-insert-string-functions' and insert the results." + (cond ((null tempel-insert-string-functions) + nil) + ((symbolp tempel-insert-string-functions) + (setq string + (funcall tempel-insert-string-functions string))) + ((listp tempel-insert-string-functions) + (dolist (fn tempel-insert-string-functions) + (setq string (funcall fn string)))) + (t + (error "Bogus value in tempo-insert-string-functions: %s" + tempel-insert-string-functions))) + (insert string)) + (defun tempel--element (st region elt) "Add template ELT to ST given the REGION." (pcase elt @@ -366,7 +392,7 @@ Return the added field." ;; `indent-according-to-mode' fails sometimes in Org. Ignore errors. ('n> (insert "\n") (tempel--protect (indent-according-to-mode))) ('> (tempel--protect (indent-according-to-mode))) - ((pred stringp) (insert elt)) + ((pred stringp) (tempel--process-and-insert-string elt)) ('& (unless (or (bolp) (save-excursion (re-search-backward "^\\s-*\\=" nil t))) (insert "\n"))) ('% (unless (or (eolp) (save-excursion (re-search-forward "\\=\\s-*$" nil t))) @@ -375,7 +401,9 @@ Return the added field." (open-line 1))) (`(s ,name) (tempel--field st name)) (`(l . ,lst) (dolist (e lst) (tempel--element st region e))) - ((or 'p `(,(or 'p 'P) . ,rest)) (apply #'tempel--placeholder st rest)) + (`(P . ,lst) ; Only for tempo backward compatibility + (tempel--placeholder st (nth 0 lst) (nth 1 lst) (nth 2 lst) :only-prompt)) + ((or 'p `(p . ,rest)) (apply #'tempel--placeholder st rest)) ((or 'r 'r> `(,(or 'r 'r>) . ,rest)) (if (not region) (when-let* ((ov (apply #'tempel--placeholder st rest)) @@ -398,14 +426,19 @@ Return the added field." ;; TEMPEL EXTENSION: Evaluate forms (tempel--form st elt))))) -(defun tempel--placeholder (st &optional prompt name noinsert) +(defun tempel--placeholder (st &optional prompt name noinsert only-prompt) "Handle placeholder element and add field with NAME to ST. If NOINSERT is non-nil do not insert a field, only bind the value to NAME. + +If ONLY-PROMPT is non-nil, prompt in the minibuffer for a value to +insert. + PROMPT is the optional prompt/default value. If a field was added, return it." (setq prompt (cond - ((and (stringp prompt) noinsert) (read-string prompt)) + ((and (stringp prompt) (or only-prompt noinsert)) + (read-string prompt)) ((stringp prompt) (propertize prompt 'tempel--default t)) ;; TEMPEL EXTENSION: Evaluate prompt (t (eval prompt (cdr st))))) @@ -612,10 +645,11 @@ TEMPLATES must be a list in the form (modes plist . templates)." (kill-region (overlay-start ov) (overlay-end ov)) (kill-sentence nil))) -(defun tempel-next (arg) +(defun tempel-next (&optional arg) "Move ARG fields forward and quit at the end." (declare (completion tempel--active-p)) (interactive "p") + (unless arg (setq arg 1)) (cl-loop for i below (abs arg) do (if-let* ((next (tempel--find arg))) (goto-char next) @@ -627,10 +661,11 @@ TEMPLATES must be a list in the form (modes plist . templates)." (fun (overlay-get ov 'tempel--enter))) (funcall fun ov))) -(defun tempel-previous (arg) +(defun tempel-previous (&optional arg) "Move ARG fields backward and quit at the beginning." (declare (completion tempel--active-p)) (interactive "p") + (unless arg (setq arg 1)) (tempel-next (- arg))) (defun tempel--beginning ()