From d7e13b9e41f7fd8a5cbd08b2d19679b84baed4ab Mon Sep 17 00:00:00 2001 From: Marten Lienen Date: Thu, 19 Feb 2026 13:37:19 +0100 Subject: [PATCH] Always evaluate active templates at the insertion point This ensures that :when conditions are evaluated at consistent positions between prefix expansion and template insertion with ~tempel-insert~. --- README.org | 26 ++++++++------ tempel.el | 103 ++++++++++++++++++++++++++++------------------------- 2 files changed, 69 insertions(+), 60 deletions(-) diff --git a/README.org b/README.org index 4fa80e2..f7d469d 100644 --- a/README.org +++ b/README.org @@ -106,16 +106,17 @@ if more templates are contributed there. * Template file format -The templates are defined in a Lisp data file configured by ~tempel-path~. Lisp -data files are files containing Lisp s-expressions (see ~lisp-data-mode~). By -default the file =templates= in the ~user-emacs-directory~ is used, e.g., -=~/.config/emacs/templates=. The templates are grouped by major mode with -an optional ~:when~ condition. Each template is a list in the concise form of the -Emacs Tempo syntax. The first element of each list is the name of the template. -I recommend to avoid special letters for the template names, since special -letters may carry meaning during completion filtering and as such make it harder -to select the desired template. Thus the name =lett= is better than =let*=. Behind -the name, the Tempo syntax elements follow. +The templates are defined in a Lisp data file configured by ~tempel-path~. Lisp data files +are files containing Lisp s-expressions (see ~lisp-data-mode~). By default the file +=templates= in the ~user-emacs-directory~ is used, e.g., =~/.config/emacs/templates=. The +templates are grouped by major mode with an optional ~:when~ condition. The condition is +evaluated at the point where the template will be inserted, so for example before the +completion prefix in the case of abbrev expansion or auto-complete. Each template is a list +in the concise form of the Emacs Tempo syntax. The first element of each list is the name +of the template. I recommend to avoid special letters for the template names, since +special letters may carry meaning during completion filtering and as such make it harder +to select the desired template. Thus the name =lett= is better than =let*=. Behind the +name, the Tempo syntax elements follow. In addition, /after/ the template elements, each template may specify several key/value pairs. Specifically, templates may specify =:pre= and/or =:post= keys with @@ -277,6 +278,10 @@ c-mode :when (re-search-backward "^\\S-*$" (line-beginning-position) 'noerror) org-mode +(inlsrc "src_" p "{" q "}") + +org-mode :when (bolp) + (caption "#+caption: ") (drawer ":" p ":" n r ":end:") (begin "#+begin_" (s name) n> r> n "#+end_" name) @@ -293,7 +298,6 @@ org-mode (src "#+begin_src " q n r n "#+end_src") (gnuplot "#+begin_src gnuplot :var data=" (p "table") " :file " (p "plot.png") n r n "#+end_src" :post (org-edit-src-code)) (elisp "#+begin_src emacs-lisp" n r n "#+end_src" :post (org-edit-src-code)) -(inlsrc "src_" p "{" q "}") (title "#+title: " p n "#+author: Daniel Mendler" n "#+language: en") ;; Local Variables: diff --git a/tempel.el b/tempel.el index d6b1210..d5c5a1f 100644 --- a/tempel.el +++ b/tempel.el @@ -43,7 +43,8 @@ (require 'compat) (eval-when-compile (require 'subr-x) - (require 'cl-lib)) + (require 'cl-lib) + (require 'seq)) (defgroup tempel nil "Tempo templates/snippets with in-buffer field editing." @@ -767,17 +768,16 @@ If prefix argument ALL is given, abort all templates." (with-current-buffer buf (tempel--disable st)))) -(defun tempel--prefix-bounds (templates) - "Return prefix bounds given TEMPLATES list." - (let ((beg (save-excursion (skip-chars-backward "^[:space:]") (point))) - (end (point))) - (if (and (/= beg end) - ;; Check if prefix matches a template name. - (try-completion (buffer-substring-no-properties beg end) - templates)) - (cons beg end) - ;; Fallback to `bounds-of-thing-at-point'. - (bounds-of-thing-at-point 'symbol)))) +(defun tempel--prefix-bounds () + "Return prefix bounds." + (bounds-of-thing-at-point 'symbol)) + +(defmacro tempel--with-point-at (pos &rest body) + "Execute the forms in BODY with point as POS." + (declare (indent 1) (debug t)) + `(save-excursion + (goto-char ,pos) + ,@body)) ;;;###autoload (defun tempel-expand (&optional interactive) @@ -792,8 +792,8 @@ command." (interactive (list t)) (when interactive (tempel--save)) - (if-let* ((templates (tempel--templates)) - (bounds (tempel--prefix-bounds templates)) + (if-let* ((bounds (tempel--prefix-bounds)) + (templates (tempel--with-point-at (car bounds) (tempel--templates))) (name (buffer-substring-no-properties (car bounds) (cdr bounds))) (sym (intern-soft name)) @@ -824,10 +824,9 @@ Capf, otherwise like an interactive completion command." (user-error "tempel-complete: No matching templates"))) ;; Use the marked region for template insertion if triggered manually. (let ((region (and (eq this-command #'tempel-complete) (tempel--region)))) - (when-let* ((templates (tempel--templates)) - (bounds (or (and (not region) - (tempel--prefix-bounds templates)) - (cons (point) (point))))) + (when-let* ((bounds (or (and (not region) (tempel--prefix-bounds)) + (cons (point) (point)))) + (templates (tempel--with-point-at (car bounds) (tempel--templates)))) (list (car bounds) (cdr bounds) templates :category 'tempel :exclusive 'no @@ -857,30 +856,32 @@ Capf, otherwise like an interactive completion command." "Insert TEMPLATE-OR-NAME. If called interactively, select a template with `completing-read'." (interactive (list nil)) - (tempel--insert - (if (consp template-or-name) template-or-name - (let ((templates (or (tempel--templates) - (error "Tempel: No templates for %s" major-mode)))) - (unless template-or-name - (setq template-or-name - (intern-soft - (completing-read - "Template: " - ;; TODO: Use `completion-table-with-metadata' via Compat 31 - (lambda (str pred action) - (if (eq action 'metadata) - `(metadata - (category . tempel) - ,@(when tempel-insert-annotation - `((annotation-function - . ,(apply-partially - #'tempel--annotate templates tempel-insert-annotation - #(" " 1 2 (display (space :align-to (+ left 20))))))))) - (complete-with-action action templates str pred))) - nil t nil 'tempel--history)))) - (or (and template-or-name (alist-get template-or-name templates)) - (user-error "Template %s not found" template-or-name)))) - (tempel--region))) + (let ((region (tempel--region))) + (tempel--insert + (if (consp template-or-name) template-or-name + (let* ((insertion-point (if region (car region) (point))) + (templates (or (tempel--with-point-at insertion-point (tempel--templates)) + (error "Tempel: No templates for %s" major-mode)))) + (unless template-or-name + (setq template-or-name + (intern-soft + (completing-read + "Template: " + ;; TODO: Use `completion-table-with-metadata' via Compat 31 + (lambda (str pred action) + (if (eq action 'metadata) + `(metadata + (category . tempel) + ,@(when tempel-insert-annotation + `((annotation-function + . ,(apply-partially + #'tempel--annotate templates tempel-insert-annotation + #(" " 1 2 (display (space :align-to (+ left 20))))))))) + (complete-with-action action templates str pred))) + nil t nil 'tempel--history)))) + (or (and template-or-name (alist-get template-or-name templates)) + (user-error "Template %s not found" template-or-name)))) + region))) ;;;###autoload (defmacro tempel-key (key template-or-name &optional map) @@ -913,17 +914,21 @@ If called interactively, select a template with `completing-read'." (kill-local-variable 'abbrev-minor-mode-table-alist)) (when tempel-abbrev-mode (let ((table (make-abbrev-table)) - (tempel--ignore-condition t)) - (dolist (sym (delete-dups (mapcar #'car (tempel--templates)))) - (let ((hook (make-symbol (symbol-name sym)))) + (templates (let ((tempel--ignore-condition t)) (tempel--templates)))) + (pcase-dolist (`(,sym . ,template) (seq-uniq templates (lambda (a b) (equal (car a) (car b))))) + (let* ((prefix (symbol-name sym)) + (hook (make-symbol prefix))) (fset hook (lambda () - (tempel--delete-word (symbol-name sym)) - (tempel--insert (alist-get sym (tempel--templates)) nil) + (tempel--delete-word prefix) + (tempel--insert template nil) t)) (put hook 'no-self-insert t) - (define-abbrev table (symbol-name sym) 'Template hook + (define-abbrev table prefix 'Template hook :system t :enable-function - (lambda () (assq sym (tempel--templates)))))) + (lambda () + (let* ((prefix-start (max (- (point) (length prefix)) (point-min))) + (templates (tempel--with-point-at prefix-start (tempel--templates)))) + (assq sym templates)))))) (setq-local abbrev-minor-mode-table-alist (cons `(tempel-abbrev-mode . ,table) abbrev-minor-mode-table-alist)))))