[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/tempel 69f7cb3212 06/82: Reimplement without Tempo for
From: |
ELPA Syncer |
Subject: |
[elpa] externals/tempel 69f7cb3212 06/82: Reimplement without Tempo for more flexibility |
Date: |
Sun, 9 Jan 2022 20:58:39 -0500 (EST) |
branch: externals/tempel
commit 69f7cb3212fe58e2c920a796b137877dac453370
Author: Daniel Mendler <mail@daniel-mendler.de>
Commit: Daniel Mendler <mail@daniel-mendler.de>
Reimplement without Tempo for more flexibility
---
README.org | 80 +++++++-----------------
tempel.el | 207 +++++++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 176 insertions(+), 111 deletions(-)
diff --git a/README.org b/README.org
index eaefca0d53..f024b04a50 100644
--- a/README.org
+++ b/README.org
@@ -9,23 +9,24 @@
* Introduction
-Tempel is a tiny template package for Emacs, which uses the builtin Tempo Emacs
-library for expansion. Tempo is an ancient temple. It is 27 years old, but
still
+Tempel is a tiny template package for Emacs, which uses the syntax of the
+Emacs Tempo library. Tempo is an ancient temple. It is 27 years old, but still
in good shape since it successfully resisted change over the decades. Tempel
-provides a modernized UI on top of Tempo, in the form of two commands:
+is a modernized implementation of Tempo, in the form of two commands:
+ ~tempel-expand~: Expand a template at point in the buffer. If called
non-interactively the function behaves like a completion-at-point-function
(Capf). You may want to use my [[https://github.com/minad/corfu][Corfu]]
completion at point UI.
+ ~tempel-insert~: Select a template by name and insert it into the current
buffer.
-After inserting a template you can move between the visible template fields
-with keys ~M-left/right~ as defined in the ~tempel-map~ keymap. As soon as you
move
+After inserting a template you can move between the visible template fields
with
+keys ~M-left/right~ as defined in the ~tempel-map~ keymap. As soon as you move
before (behind) the first (last) field, the fields are removed.
Note that this package is not a competitor to the mature and widely used
YASnippet library. Try Tempel only if your snippet and templating requirements
-are limited and if you like tiny and simple packages.
+are limited and if you like tiny and simple packages. Tempel took inspiration
+from the [[https://github.com/nschum/tempo-snippets.el][Tempo-Snippets]]
package by Nikolaj Schumacher.
* Quick start
@@ -81,53 +82,20 @@ org-mode
(title "#+title: " p n "#+author: Daniel Mendler" n "#+language: en" n n)
#+end_src
-Take a look at the documentation of ~tempo-define-template~ for the
documentation
-of the template elements. You can even define your own template elements via
-~tempo-user-elements~.
-
-#+begin_quote
- - A string: It is sent to the hooks in `tempo-insert-string-functions',
- and the result is inserted.
- - The symbol `p': This position is saved in `tempo-marks'.
- - The symbol `r': If `tempo-insert' is called with ON-REGION non-nil
- the current region is placed here. Otherwise it works like `p'.
- - (p PROMPT <NAME> <NOINSERT>): If `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 <NAME> <NOINSERT>): Works just like the previous tag, but
- forces `tempo-interactive' to be true.
- - (r PROMPT <NAME> <NOINSERT>): Like the previous tag, but if
- `tempo-interactive' is nil and `tempo-insert' is called with
- ON-REGION non-nil, the current region is placed here. This usually
- happens when you call the template function with a prefix argument.
- - (s NAME): Inserts text previously read with the (p ..) construct.
- Finds the insertion saved under NAME and inserts it. Acts like `p'
- if 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 <NAME> <NOINSERT>): 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 `tempo-user-elements' 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."
-#+end_quote
+All the syntax elements of ~tempo-define-template~ are supported. We document
+the important ones here:
+
+ - "string" :: Inserts a string literal.
+ - ~p~ :: Inserts an unnamed prompt field.
+ - ~r~ :: Inserts the current region.
+ - ~(s NAME)~ :: Inserts a named field.
+ - ~(q PROMPT NAME)~ :: Query the user via ~read-string~, store variable
~NAME~.
+ - ~n~ :: Inserts a newline.
+ - ~>~ :: Indents with ~indent-according-to-mode~.
+ - ~r>~ :: The region, but indented.
+ - ~n>~ :: Inserts a newline and indents.
+ - ~&~ :: Insert newline if there is only whitespace between line start and
point.
+ - ~%~ :: Insert newline if there is only whitespace between point and line
end.
+ - ~o~ :: Like ~%~ but leaves the point before newline.
+ - ~(form ...)~ :: Other lisp forms are evaluated. Named fields are lexically
bound.
+ Use caution with templates which execute code!
diff --git a/tempel.el b/tempel.el
index 158c2e4e0c..bb773b16c0 100644
--- a/tempel.el
+++ b/tempel.el
@@ -1,4 +1,4 @@
-;;; tempel.el --- Tempo Templates -*- lexical-binding: t -*-
+;;; tempel.el --- Simple templates for Emacs -*- lexical-binding: t -*-
;; Author: Daniel Mendler
;; Created: 2022
@@ -24,26 +24,27 @@
;;; Commentary:
-;; This package provides a modern user interface to the Emacs Tempo
-;; template library. Your templates are stored in the `tempel-file' (by
-;; default the file "templates" in the `user-emacs-directory'). Bind the
+;; Tempel implements a simple template/snippet system. The template
+;; format is compatible with the template format of the Emacs Tempo
+;; library. Your templates are stored in the `tempel-file' (by default
+;; the file "templates" in the `user-emacs-directory'). Bind the
;; commands `tempel-expand' or `tempel-insert' to some keys in your user
;; configuration. You can jump with the keys M-up/down from field to
;; field. `tempel-expands' works best with the Corfu completion UI,
-;; while `temple-insert' uses `completing-read' under the hood.
+;; while `tempel-insert' uses `completing-read' under the hood.
;;; Code:
-(require 'tempo)
(require 'seq)
(eval-when-compile (require 'subr-x))
(defvar tempel-file (expand-file-name "templates" user-emacs-directory))
+(defvar tempel-region nil)
(defvar tempel--templates nil)
(defvar tempel--modified nil)
-(defvar tempel--current nil)
(defvar tempel--history nil)
-(defvar-local tempel--fields nil)
+(defvar tempel--state nil)
+(defvar-local tempel--overlays nil)
(defvar tempel-map
(let ((map (make-sparse-keymap)))
@@ -53,7 +54,7 @@
"Keymap to navigate across template markers.")
(defun tempel--load (file)
- "Load Tempo templates from FILE."
+ "Load templates from FILE."
(with-temp-buffer
(insert "(\n")
(insert-file-contents-literally file)
@@ -78,83 +79,183 @@
(propertize
(replace-regexp-in-string
"\\s-+" " "
- (mapconcat (lambda (x)
- (pcase x
- ((pred stringp) x)
- ((or 'n 'n>) " ")
- (_ "_")))
+ (mapconcat (lambda (x) (pcase x
+ ((pred stringp) x)
+ ((or 'n 'n>) " ")
+ (_ "_")))
def ""))
'face 'completions-annotations))
20 0 ?\s))))
-(defun tempel--insert (templates region name)
- "Insert template NAME given the list of TEMPLATES and REGION flag."
+(defun tempel--replace-field (ov str)
+ "Replace OV content with STR."
+ (let ((inhibit-modification-hooks t)
+ (beg (overlay-start ov)))
+ (goto-char beg)
+ (delete-char (- (overlay-end ov) beg))
+ (insert str)
+ (move-overlay ov beg (point))))
+
+(defun tempel--update-field (ov after beg end &optional _len)
+ "Update field overlay OV.
+AFTER is non-nil after the modification.
+BEG and END are the boundaries of the modification."
+ (when (and after (>= beg (overlay-start ov)) (<= beg (overlay-end ov)))
+ (move-overlay ov (overlay-start ov) (max end (overlay-end ov)))
+ (when-let (name (overlay-get ov 'tempel--name))
+ (let ((state (overlay-get ov 'tempel--state))
+ (str (buffer-substring-no-properties (overlay-start ov)
(overlay-end ov))))
+ (setf (alist-get name (cddr state)) str)
+ (save-excursion
+ ;; Update overlays
+ (dolist (other (alist-get name (car state)))
+ (unless (eq other ov)
+ (tempel--replace-field other str)))
+ ;; Update forms
+ (dolist (other (cadr state))
+ (tempel--replace-field other (eval (overlay-get other
'tempel--form) (cddr state)))))))))
+
+(defun tempel--field ()
+ "Create template field."
+ (let ((ov (make-overlay (point) (point))))
+ (overlay-put ov 'face 'highlight)
+ (overlay-put ov 'before-string #(" " 0 1 (face highlight display (space
:width (1)))))
+ (overlay-put ov 'modification-hooks (list #'tempel--update-field))
+ (overlay-put ov 'insert-behind-hooks (list #'tempel--update-field))
+ (push ov tempel--overlays)
+ ov))
+
+(defun tempel--named (name)
+ "Create template field named NAME."
+ (let ((ov (tempel--field)))
+ (push ov (alist-get name (car tempel--state)))
+ (overlay-put ov 'tempel--name name)
+ (overlay-put ov 'tempel--state tempel--state)
+ (when-let (str (alist-get name (cddr tempel--state)))
+ (insert str)
+ (move-overlay ov (overlay-start ov) (point)))))
+
+(defun tempel--form (form)
+ "Create template field evaluating FORM."
+ (let ((ov (tempel--field)))
+ (push ov (cadr tempel--state))
+ (overlay-put ov 'tempel--form form)
+ (insert (eval form (cddr tempel--state)))
+ (move-overlay ov (overlay-start ov) (point))))
+
+(defun tempel--query (prompt name)
+ "Read input with PROMPT and assign to NAME."
+ (setf (alist-get name (cddr tempel--state)) (read-string prompt)))
+
+(defun tempel--element (element)
+ "Insert template ELEMENT."
+ (pcase element
+ ('nil)
+ ('n (insert "\n"))
+ ('n> (insert "\n") (indent-according-to-mode))
+ ('> (indent-according-to-mode))
+ ((pred stringp) (insert element))
+ ('& (unless (or (bolp) (save-excursion (re-search-backward "^\\s-*\\=" nil
t)))
+ (insert "\n")))
+ ('% (unless (or (eolp) (save-excursion (re-search-forward "\\=\\s-*$" nil
t)))
+ (insert "\n")))
+ ('o (unless (or tempel-region (eolp)
+ (save-excursion (re-search-forward "\\=\\s-*$" nil t)))
+ (open-line 1)))
+ ('p (tempel--field))
+ ((or 'r `(r . ,_)) (if tempel-region (goto-char (cdr tempel-region))
(tempel--field)))
+ ((or 'r> `(r> . ,_))
+ (if (not tempel-region) (tempel--field)
+ (goto-char (cdr tempel-region))
+ (indent-region (car tempel-region) (cdr tempel-region) nil)))
+ (`(,(or 'p 'P) ,prompt . ,rest) ;; Tempo legacy, use i, s, or plain p
instead
+ (cond
+ ((cadr rest) (tempel--query prompt (car rest)))
+ ((car rest) (tempel--named (car rest)))
+ (t (tempel--field))))
+ (`(q ,prompt ,name) (tempel--query prompt name)) ;; Tempel Extension over
Tempo
+ (`(s ,name) (tempel--named name))
+ (_ (tempel--form element))))
+
+(defun tempel--insert (templates name)
+ "Insert template NAME given the list of TEMPLATES."
(when-let* ((name (intern-soft name))
(template (cdr (assoc name templates))))
- (setf (alist-get 'tempo-marks minor-mode-overriding-map-alist) tempel-map)
- (let ((tempel--current template))
- (mapc #'delete-overlay tempel--fields)
- (setq tempel--fields nil)
- (tempo-insert-template 'tempel--current region)
- (dolist (x tempo-marks)
- (let ((ov (make-overlay x x nil nil t)))
- (overlay-put ov 'face 'highlight)
- (overlay-put ov 'before-string #(" " 0 1 (face highlight display
(space :width (1)))))
- (push ov tempel--fields))))))
+ (setf (alist-get 'tempel--overlays minor-mode-overriding-map-alist)
tempel-map)
+ (save-excursion
+ ;; Split old overlays
+ (dolist (ov tempel--overlays)
+ (when (and (<= (overlay-start ov) (point)) (>= (overlay-end ov)
(point)))
+ (setf (overlay-end ov) (point))))
+ ;; Begin marker
+ (push (make-overlay (point) (point)) tempel--overlays)
+ (let ((tempel--state (list nil nil))
+ (inhibit-modification-hooks t))
+ (mapc #'tempel--element template))
+ ;; End marker
+ (push (make-overlay (point) (point)) tempel--overlays))
+ (setq tempel--overlays (sort tempel--overlays (lambda (x y) (<
(overlay-start x) (overlay-start y)))))
+ ;; Jump to first field
+ (tempel-next-field 1)))
(defun tempel--save ()
- "Save Tempo file buffer."
+ "Save template file buffer."
(when-let (buf (get-file-buffer tempel-file))
(with-current-buffer buf
(when (and (buffer-modified-p) (y-or-n-p (format "Save file %s? "
tempel-file)))
(save-buffer buf)))))
(defun tempel--templates ()
- "Return Tempo templates for current mode."
+ "Return templates for current mode."
(let ((mod (file-attribute-modification-time (file-attributes tempel-file))))
(unless (equal tempel--modified mod)
(setq tempel--templates (tempel--load tempel-file)
tempel--modified mod)))
(cdr (seq-find (lambda (x) (derived-mode-p (car x))) tempel--templates)))
+(defun tempel--region ()
+ "Return region bounds."
+ (when (use-region-p)
+ (when (< (mark) (point)) (exchange-point-and-mark))
+ (deactivate-mark)
+ (cons (point-marker) (mark-marker))))
+
(defun tempel-next-field (arg)
- "Move to next template field and quit at the end."
+ "Move ARG fields forward and quit at the end."
(interactive "p")
(catch 'tempel--break
(cond
((> arg 0)
- (dolist (ov (reverse tempel--fields))
+ (dolist (ov tempel--overlays)
(when (> (overlay-start ov) (point))
- (if (> arg 1) (cl-decf arg)
- (goto-char (overlay-start ov))
+ (if (> arg 1) (setq arg (1- arg))
+ (goto-char (overlay-end ov))
(throw 'tempel--break nil)))))
((< arg 0)
- (dolist (ov tempel--fields)
+ (dolist (ov (reverse tempel--overlays))
(when (< (overlay-end ov) (point))
- (if (< arg -1) (cl-incf arg)
- (goto-char (overlay-start ov))
+ (if (< arg -1) (setq arg (1+ arg))
+ (goto-char (overlay-end ov))
(throw 'tempel--break nil))))))
(tempel-done)))
(defun tempel-previous-field (arg)
- "Move to previous template field and quit at the beginning."
+ "Move ARG fields backward and quit at the beginning."
(interactive "p")
(tempel-next-field (- arg)))
(defun tempel-done ()
"Template completion is done."
(interactive)
- (dolist (mark tempo-marks) (set-marker mark nil))
- (mapc #'delete-overlay tempel--fields)
- (setq tempo-marks nil
- tempel--fields nil
+ (mapc #'delete-overlay tempel--overlays)
+ (setq tempel--overlays nil
minor-mode-overriding-map-alist
- (delq (assq-delete-all 'tempo-marks minor-mode-overriding-map-alist)
+ (delq (assq-delete-all 'tempel--overlays
minor-mode-overriding-map-alist)
minor-mode-overriding-map-alist)))
;;;###autoload
(defun tempel-expand (&optional interactive)
- "Complete Tempo template at point.
+ "Complete template at point.
If INTERACTIVE is nil the function acts like a capf."
(interactive (list t))
(if interactive
@@ -163,25 +264,20 @@ If INTERACTIVE is nil the function acts like a capf."
(tempel--save)
(or (completion-at-point) (user-error "Tempel: No completions")))
(when-let (templates (tempel--templates))
- (let ((region (use-region-p)) bounds)
- (when region
- (when (< (mark) (point)) (exchange-point-and-mark))
- (deactivate-mark))
- (setq bounds
- (or (and (not region) (bounds-of-thing-at-point 'symbol))
- (cons (point) (point))))
- (list (car bounds)
- (cdr bounds)
- templates
+ (let* ((region (tempel--region))
+ (bounds (or (and (not region) (bounds-of-thing-at-point 'symbol))
+ (cons (point) (point)))))
+ (list (car bounds) (cdr bounds) templates
:exclusive 'no
:exit-function (lambda (name _status)
(delete-region (max (point-min) (- (point)
(length name))) (point))
- (tempel--insert templates region name))
+ (let ((tempel-region region))
+ (tempel--insert templates name)))
:annotation-function (apply-partially #'tempel--annotate
templates " "))))))
;;;###autoload
(defun tempel-insert ()
- "Insert Tempo template using `completing-read'."
+ "Insert template using `completing-read'."
(interactive)
(let* ((templates (or (tempel--templates)
(error "Tempel: No templates for %s" major-mode)))
@@ -190,7 +286,8 @@ If INTERACTIVE is nil the function acts like a capf."
(apply-partially #'tempel--annotate templates
#(" " 1 2 (display (space :align-to (+ left
20)))))))
(name (completing-read "Template: " templates nil t nil
'tempel--history)))
- (tempel--insert templates (use-region-p) name)))
+ (let ((tempel-region (tempel--region)))
+ (tempel--insert templates name))))
(provide 'tempel)
;;; tempel.el ends here
- [elpa] externals/tempel f0df571f66 17/82: Respect derived modes, (continued)
- [elpa] externals/tempel f0df571f66 17/82: Respect derived modes, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel bf95b3f1c2 31/82: Add support for (q (FORM ...) var), ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 64f896d782 44/82: Fix jump to first field, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 9948c6889d 43/82: Add tempel-form-prefix and tempel-field-prefix, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel ad71c25632 32/82: Add syntax extension (p (FORM...) <NAME>), ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 5dbdc76eb2 74/82: Specify the completion category tempel, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 65a3eb0458 76/82: Improve abbreviation expansion, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 57bbc5e681 78/82: README update, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel ac3df540a4 69/82: Remove underlines from faces, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 7cf25bd795 71/82: Simplify templates data structure at load time, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 69f7cb3212 06/82: Reimplement without Tempo for more flexibility,
ELPA Syncer <=
- [elpa] externals/tempel fbef94d77d 48/82: Minor cleanup, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 01e7430343 27/82: Add docstrings, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel a03c36c8dd 02/82: Improve behavior when templates are nested, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel db6847a7f9 56/82: Make temple-key a macro, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 0e618cd169 23/82: Do not sort overlays, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 04d4f6c055 55/82: Use named key function, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel 91f6978ddd 60/82: Use Tempo PROMPT as default value, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel e20f982dc5 25/82: Remove global variable tempel--state, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel fa99fb7f96 34/82: README: Update keybindings, ELPA Syncer, 2022/01/09
- [elpa] externals/tempel c4cd6e0f69 73/82: Rename tempel-expand -> tempel-complete, add tempel-expand (Fix #13), ELPA Syncer, 2022/01/09