org-tempo.el (6625B)
1 ;;; org-tempo.el --- Template expansion for Org structures -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2017-2024 Free Software Foundation, Inc. 4 ;; 5 ;; Author: Rasmus Pank Roulund <emacs at pank dot eu> 6 ;; Keywords: outlines, hypermedia, calendar, text 7 ;; URL: https://orgmode.org 8 ;; 9 ;; This file is part of GNU Emacs. 10 ;; 11 ;; GNU Emacs is free software: you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; GNU Emacs is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 23 ;; 24 ;;; Commentary: 25 ;; 26 ;; Org Tempo reimplements completions of structure template before 27 ;; point in Org v9.1 and earlier. 28 ;; For example, strings like "<e" at the beginning of the line will be 29 ;; expanded to an example block. 30 ;; 31 ;; All blocks defined in `org-structure-template-alist' are added as 32 ;; Org Tempo shortcuts, in addition to keywords defined in 33 ;; `org-tempo-keywords-alist'. 34 ;; 35 ;; `tempo' can also be used to define more sophisticated keywords 36 ;; completions. See the section "Additional keywords" below for 37 ;; examples. 38 ;; 39 ;;; Code: 40 41 (require 'org-macs) 42 (org-assert-version) 43 44 (require 'tempo) 45 (require 'cl-lib) 46 (require 'org) 47 48 (defvar org-structure-template-alist) 49 50 51 (defgroup org-tempo nil 52 "Template expansion of Org structures." 53 :tag "Org structure" 54 :group 'org) 55 56 (defvar org-tempo-tags nil 57 "Tempo tags for Org mode.") 58 59 (defcustom org-tempo-keywords-alist 60 '(("L" . "latex") 61 ("H" . "html") 62 ("A" . "ascii") 63 ("i" . "index")) 64 "Keyword completion elements. 65 66 This is an alist of KEY characters and corresponding KEYWORDS, 67 just like `org-structure-template-alist'. The tempo snippet 68 \"<KEY\" will be expanded using the KEYWORD value. For example 69 \"<L\" at the beginning of a line is expanded to \"#+latex:\". 70 71 Do not use \"I\" as a KEY, as it is reserved for expanding 72 \"#+include\"." 73 :type '(repeat (cons (string :tag "Key") 74 (string :tag "Keyword"))) 75 :package-version '(Org . "9.2")) 76 77 78 79 ;;; Org Tempo functions and setup. 80 81 (defun org-tempo-setup () 82 "Setup tempo tags and match finder for the current buffer." 83 (org-tempo--update-maybe) 84 (tempo-use-tag-list 'org-tempo-tags) 85 (setq-local tempo-match-finder "^ *\\(<[[:word:]]+\\)\\=")) 86 87 (defun org-tempo--keys () 88 "Return a list of all Org Tempo expansion strings, like \"<s\"." 89 (mapcar (lambda (pair) (format "<%s" (car pair))) 90 (append org-structure-template-alist 91 org-tempo-keywords-alist))) 92 93 (defun org-tempo--update-maybe () 94 "Check and add new Org Tempo templates if necessary. 95 In particular, if new entries were added to 96 `org-structure-template-alist' or `org-tempo-keywords-alist', new 97 Tempo templates will be added." 98 (unless (cl-every (lambda (key) (assoc key org-tempo-tags)) 99 (org-tempo--keys)) 100 (org-tempo-add-templates))) 101 102 (defun org-tempo-add-templates () 103 "Update all Org Tempo templates. 104 105 Go through `org-structure-template-alist' and 106 `org-tempo-keywords-alist' and update tempo templates." 107 (mapc #'org--check-org-structure-template-alist '(org-structure-template-alist 108 org-tempo-keywords-alist)) 109 (let ((keys (org-tempo--keys))) 110 ;; Check for duplicated snippet keys and warn if any are found. 111 (when (> (length keys) (length (delete-dups keys))) 112 (warn 113 "Duplicated keys in `org-structure-template-alist' and `org-tempo-keywords-alist'")) 114 ;; Remove any keys already defined in case they have been updated. 115 (setq org-tempo-tags 116 (cl-remove-if (lambda (tag) (member (car tag) keys)) org-tempo-tags)) 117 (mapc #'org-tempo-add-block org-structure-template-alist) 118 (mapc #'org-tempo-add-keyword org-tempo-keywords-alist))) 119 120 (defun org-tempo-add-block (entry) 121 "Add block ENTRY from `org-structure-template-alist'." 122 (let* ((key (format "<%s" (car entry))) 123 (name (cdr entry)) 124 (special (member name '("src" "export"))) 125 (upcase? (string= (car (split-string name)) 126 (upcase (car (split-string name)))))) 127 (tempo-define-template (format "org-%s" (replace-regexp-in-string " " "-" name)) 128 `(,(format "#+%s_%s%s" 129 (if upcase? "BEGIN" "begin") 130 name 131 (if special " " "")) 132 ,(when special 'p) '> n ,(unless special 'p) n 133 ,(format "#+%s_%s" 134 (if upcase? "END" "end") 135 (car (split-string name " "))) 136 >) 137 key 138 (format "Insert a %s block" name) 139 'org-tempo-tags))) 140 141 (defun org-tempo-add-keyword (entry) 142 "Add keyword ENTRY from `org-tempo-keywords-alist'." 143 (let* ((key (format "<%s" (car entry))) 144 (name (cdr entry))) 145 (tempo-define-template (format "org-%s" (replace-regexp-in-string " " "-" name)) 146 `(,(format "#+%s: " name) p '>) 147 key 148 (format "Insert a %s keyword" name) 149 'org-tempo-tags))) 150 151 (defun org-tempo-complete-tag (&rest _) 152 "Look for a tag and expand it silently. 153 Unlike to `tempo-complete-tag', do not give a signal if a partial 154 completion or no match at all is found. Return nil if expansion 155 didn't succeed." 156 (org-tempo--update-maybe) 157 ;; `tempo-complete-tag' returns its SILENT argument when there is no 158 ;; completion available at all. 159 (not (eq 'fail (tempo-complete-tag 'fail)))) 160 161 162 ;;; Additional keywords 163 164 (defun org-tempo--include-file () 165 "Add #+include: and a file name." 166 (let ((inhibit-quit t)) 167 (unless (with-local-quit 168 (prog1 t 169 (insert 170 (format "#+include: %S " 171 (file-relative-name 172 (read-file-name "Include file: ")))))) 173 (insert "<I") 174 (setq quit-flag nil)))) 175 176 (tempo-define-template "org-include" 177 '((org-tempo--include-file) 178 p >) 179 "<I" 180 "Include keyword" 181 'org-tempo-tags) 182 183 ;;; Setup of Org Tempo 184 ;; 185 ;; Org Tempo is set up with each new Org buffer and potentially in the 186 ;; current Org buffer. 187 188 (add-hook 'org-mode-hook #'org-tempo-setup) 189 (add-hook 'org-tab-before-tab-emulation-hook #'org-tempo-complete-tag) 190 191 ;; Enable Org Tempo in all open Org buffers. 192 (dolist (b (org-buffer-list 'files)) 193 (with-current-buffer b (org-tempo-setup))) 194 195 (provide 'org-tempo) 196 197 ;;; org-tempo.el ends here