diff options
Diffstat (limited to 'lisp/org/org-tempo.el')
-rw-r--r-- | lisp/org/org-tempo.el | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/lisp/org/org-tempo.el b/lisp/org/org-tempo.el new file mode 100644 index 00000000000..78424b28a0e --- /dev/null +++ b/lisp/org/org-tempo.el @@ -0,0 +1,188 @@ +;;; org-tempo.el --- Template expansion for Org structures -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2019 Free Software Foundation, Inc. +;; +;; Author: Rasmus Pank Roulund <emacs at pank dot eu> +;; Keywords: outlines, hypermedia, calendar, wp +;; Homepage: http://orgmode.org +;; +;; This file is part of GNU Emacs. +;; +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. +;; +;;; Commentary: +;; +;; Org Tempo reimplements completions of structure template before +;; point like `org-try-structure-completion' in Org v9.1 and earlier. +;; For example, strings like "<e" at the beginning of the line will be +;; expanded to an example block. +;; +;; All blocks defined in `org-structure-template-alist' are added as +;; Org Tempo shortcuts, in addition to keywords defined in +;; `org-tempo-keywords-alist'. +;; +;; `tempo' can also be used to define more sophisticated keywords +;; completions. See the section "Additional keywords" below for +;; examples. +;; +;;; Code: + +(require 'tempo) +(require 'cl-lib) +(require 'org) + +(defvar org-structure-template-alist) + + +(defgroup org-tempo nil + "Template expansion of Org structures." + :tag "Org structure" + :group 'org) + +(defvar org-tempo-tags nil + "Tempo tags for Org mode.") + +(defcustom org-tempo-keywords-alist + '(("L" . "latex") + ("H" . "html") + ("A" . "ascii") + ("i" . "index")) + "Keyword completion elements. + +This is an alist of KEY characters and corresponding KEYWORDS, +just like `org-structure-template-alist'. The tempo snippet +\"<KEY\" will be expanded using the KEYWORD value. For example +\"<L\" at the beginning of a line is expanded to \"#+latex:\". + +Do not use \"I\" as a KEY, as it it reserved for expanding +\"#+include\"." + :group 'org-tempo + :type '(repeat (cons (string :tag "Key") + (string :tag "Keyword"))) + :package-version '(Org . "9.2")) + + + +;;; Org Tempo functions and setup. + +(defun org-tempo-setup () + "Setup tempo tags and match finder for the current buffer." + (org-tempo--update-maybe) + (tempo-use-tag-list 'org-tempo-tags) + (setq-local tempo-match-finder "^ *\\(<[[:word:]]+\\)\\=")) + +(defun org-tempo--keys () + "Return a list of all Org Tempo expansion strings, like \"<s\"." + (mapcar (lambda (pair) (format "<%s" (car pair))) + (append org-structure-template-alist + org-tempo-keywords-alist))) + +(defun org-tempo--update-maybe () + "Check and add new Org Tempo templates if necessary. +In particular, if new entries were added to +`org-structure-template-alist' or `org-tempo-keywords-alist', new +Tempo templates will be added." + (unless (cl-every (lambda (key) (assoc key org-tempo-tags)) + (org-tempo--keys)) + (org-tempo-add-templates))) + +(defun org-tempo-add-templates () + "Update all Org Tempo templates. + +Go through `org-structure-template-alist' and +`org-tempo-keywords-alist' and update tempo templates." + (mapc 'org--check-org-structure-template-alist '(org-structure-template-alist + org-tempo-keywords-alist)) + (let ((keys (org-tempo--keys))) + ;; Check for duplicated snippet keys and warn if any are found. + (when (> (length keys) (length (delete-dups keys))) + (warn + "Duplicated keys in `org-structure-template-alist' and `org-tempo-keywords-alist'")) + ;; Remove any keys already defined in case they have been updated. + (setq org-tempo-tags + (cl-remove-if (lambda (tag) (member (car tag) keys)) org-tempo-tags)) + (mapc #'org-tempo-add-block org-structure-template-alist) + (mapc #'org-tempo-add-keyword org-tempo-keywords-alist))) + +(defun org-tempo-add-block (entry) + "Add block entry from `org-structure-template-alist'." + (let* ((key (format "<%s" (car entry))) + (name (cdr entry)) + (special (member name '("src" "export")))) + (tempo-define-template (format "org-%s" (replace-regexp-in-string " " "-" name)) + `(,(format "#+begin_%s%s" name (if special " " "")) + ,(when special 'p) '> n '> ,(unless special 'p) n + ,(format "#+end_%s" (car (split-string name " "))) + >) + key + (format "Insert a %s block" name) + 'org-tempo-tags))) + +(defun org-tempo-add-keyword (entry) + "Add keyword entry from `org-tempo-keywords-alist'." + (let* ((key (format "<%s" (car entry))) + (name (cdr entry))) + (tempo-define-template (format "org-%s" (replace-regexp-in-string " " "-" name)) + `(,(format "#+%s: " name) p '>) + key + (format "Insert a %s keyword" name) + 'org-tempo-tags))) + +(defun org-tempo-complete-tag (&rest _) + "Look for a tag and expand it silently. +Unlike to `tempo-complete-tag', do not give a signal if a partial +completion or no match at all is found. Return nil if expansion +didn't succeed." + (org-tempo--update-maybe) + ;; `tempo-complete-tag' returns its SILENT argument when there is no + ;; completion available at all. + (not (eq 'fail (tempo-complete-tag 'fail)))) + + +;;; Additional keywords + +(defun org-tempo--include-file () + "Add #+include: and a file name." + (let ((inhibit-quit t)) + (unless (with-local-quit + (prog1 t + (insert + (format "#+include: %S " + (file-relative-name + (read-file-name "Include file: ")))))) + (insert "<I") + (setq quit-flag nil)))) + +(tempo-define-template "org-include" + '((org-tempo--include-file) + p >) + "<I" + "Include keyword" + 'org-tempo-tags) + +;;; Setup of Org Tempo +;; +;; Org Tempo is set up with each new Org buffer and potentially in the +;; current Org buffer. + +(add-hook 'org-mode-hook 'org-tempo-setup) +(add-hook 'org-tab-before-tab-emulation-hook 'org-tempo-complete-tag) + +;; Enable Org Tempo in all open Org buffers. +(dolist (b (org-buffer-list 'files)) + (with-current-buffer b (org-tempo-setup))) + +(provide 'org-tempo) + +;;; org-tempo.el ends here |