diff options
Diffstat (limited to '')
-rw-r--r-- | lisp/template.el | 2609 |
1 files changed, 2609 insertions, 0 deletions
diff --git a/lisp/template.el b/lisp/template.el new file mode 100644 index 0000000..ea36c4b --- /dev/null +++ b/lisp/template.el @@ -0,0 +1,2609 @@ +;;; template.el --- use templates, decorate comments, auto-update buffers + +;; Copyright (C) 1995-2003 Free Software Foundation, Inc. +;; +;; Author: Christoph Wedler <wedler@users.sourceforge.net> +;; Version: (see `template-version' below) +;; Keywords: template, comment decoration, auto-updating, data, tools +;; X-URL: http://emacs-template.sourceforge.net/ + +;; 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 2, 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, write to the Free Software +;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;; When you create a new file with Emacs, package Template supplies an initial +;; buffer content via a template: a file with normal text and expansion +;; forms. There is a menu to easily create such templates. You can also use new +;; commands to decorate comments and update the buffer contents. + +;; The main difference between Template and other similar packages is that you +;; can define very flexible templates without having to learn Lisp or changing +;; your Emacs init file. This package does not help Lisp programmers to define +;; complex macros. + +;; For details, check <http://emacs-template.sourceforge.net/> or, if you +;; prefer the manual style, the documentation of the following commands and +;; variables: +;; +;; * for templates: \\[template-new-file], `template-auto-insert', +;; `template-derivation-alist', `template-default-expansion-alist' and +;; `template-definition-start', +;; * for comment decoration: \\[template-single-comment] and +;; \\[template-block-comment], `template-comment-specification-alist' +;; * for updating: \\[template-update-buffer], `template-auto-update', +;; `template-update-buffer-alist' and `template-header-regexp-alist'. + +;; Bug fixes, bug reports, improvements, and suggestions for the newest version +;; are strongly appreciated. + +;;; Installation: + +;; This file requires Emacs-20.2, XEmacs-20.2 or higher. + +;; Put this file into your load-path and the following into your ~/.emacs: +;; (require 'template) +;; (template-initialize) + +;; You might want to add another item to the "File" menu by (in XEmacs): +;; (add-menu-button '("File") +;; ["Insert and Expand Template..." +;; template-expand-template +;; :active (not buffer-read-only)] +;; "Insert File...") + +;; To customize, use `M-x customize-group RET template RET' or the customize +;; entry in menu Options. + +;;; Code: + +(provide 'template) +(require 'custom) + +;; General Emacs/XEmacs-compatibility compile-time macros +(eval-when-compile + (require 'cl) + (defmacro cond-emacs-xemacs (&rest args) + (cond-emacs-xemacs-macfn + args "`cond-emacs-xemacs' must return exactly one element")) + (defun cond-emacs-xemacs-macfn (args &optional msg) + (if (atom args) args + (and (eq (car args) :@) (null msg) ; (:@ ...spliced...) + (setq args (cdr args) + msg "(:@ ....) must return exactly one element")) + (let ((ignore (if (string-match "XEmacs" emacs-version) :EMACS :XEMACS)) + (mode :BOTH) code) + (while (consp args) + (if (memq (car args) '(:EMACS :XEMACS :BOTH)) (setq mode (pop args))) + (if (atom args) + (or args (error "Used selector %s without elements" mode)) + (or (eq ignore mode) + (push (cond-emacs-xemacs-macfn (car args)) code)) + (pop args))) + (cond (msg (if (or args (cdr code)) (error msg) (car code))) + ((or (null args) (eq ignore mode)) (nreverse code)) + (t (nconc (nreverse code) args)))))) + ;; Emacs/XEmacs-compatibility `defun': remove interactive "_" for Emacs, use + ;; existing functions when they are `fboundp', provide shortcuts if they are + ;; known to be defined in a specific Emacs branch (for short .elc) + (defmacro defunx (name arglist &rest definition) + (let ((xemacsp (string-match "XEmacs" emacs-version)) reuses first) + (while (memq (setq first (car definition)) + '(:try :emacs-and-try :xemacs-and-try + :emacs-only :xemacs-only)) + (if (memq first (if xemacsp + '(:xemacs-and-try :xemacs-only) + '(:emacs-and-try :emacs-only))) + (setq reuses (cadr definition) + definition nil) + (unless (memq first '(:emacs-only :xemacs-only)) + (push (cadr definition) reuses))) + (setq definition (cddr definition))) + (if (and reuses (symbolp reuses)) + `(defalias ',name ',reuses) + (let* ((docstring (if (stringp (car definition)) (pop definition))) + (spec (and (not xemacsp) + (eq (car-safe (car definition)) 'interactive) + (null (cddar definition)) + (cadar definition)))) + (if (and (stringp spec) + (not (string-equal spec "")) + (eq (aref spec 0) ?_)) + (setq definition + (cons (if (string-equal spec "_") + '(interactive) + `(interactive ,(substring spec 1))) + (cdr definition)))) + (if (null reuses) + `(defun ,name ,arglist ,docstring + ,@(cond-emacs-xemacs-macfn definition)) + ;; no dynamic docstring in this case + `(eval-and-compile ; no warnings in Emacs + (defalias ',name + (cond ,@(mapcar (lambda (func) `((fboundp ',func) ',func)) + (nreverse reuses)) + (t ,(if definition + `(lambda ,arglist ,docstring + ,@(cond-emacs-xemacs-macfn definition)) + 'ignore))))))))))) + +(eval-when-compile + (require 'cl) + (defvar init-file-loaded) ; would be useful in Emacs, too... + (defvar file-name-buffer-file-type-alist)) + + + +;;;;########################################################################## +;;;; User Options, Variables +;;;;########################################################################## + + +(defconst template-version "3.1c" + "Current version of package template. +Check <http://emacs-template.sourceforge.net/> for the newest.") + + +;;;=========================================================================== +;;; Customization and initialization +;;;=========================================================================== + +(defgroup template nil + "Use templates, decorate comments, auto-update buffers." + :group 'data + :link '(emacs-commentary-link "template.el") + :link '(url-link "http://emacs-template.sourceforge.net/") + :prefix "template-") + +(defgroup template-comments nil + "Comment decorations in package template." + :group 'template + :prefix "template-") + +(defgroup template-updating nil + "Updating with package template." + :group 'template + :prefix "template-") + +(defgroup template-derivation nil + "Deriving templates for new files." + :group 'template + :prefix "template-") + +(defgroup template-expansion nil + "Expanding the expansion forms in templates." + :group 'template + :prefix "template-") + +(defgroup template-miscellaneous nil + "Miscellaneous configurations of package template." + :group 'template + :prefix "template-") + +;; I could imagine that a future version of package custom could make this +;; `PACKAGE-initialize' stuff easier +(defcustom template-use-package nil + "Pseudo variable. Used to initialize template in custom buffer. +Put `(template-initialize)' into your ~/.emacs to initialize package +template in future sessions. See variable `template-initialize'." + :group 'template + :type '(boolean :format "%{%t%}: %[(template-initialize)%], %v\n" + :on "in use" :off "not yet initialized" + :help-echo "Initialize package Template." + :action template-initialize)) + +(defcustom template-initialize t + "Whether/what to initialize with `template-initialize'. +If t, do full initialization. Otherwise, the value should be a list +with elements. To enable, include + + * `auto' to enable `template-auto-update' and `template-auto-insert', + * `ffap' to make sure that `auto' works with `find-file-at-point', + * `cc-mode' to enable correct C/C++/Java/Antlr comment filling, i.e., + to add `template-c-init-fill-function' to `c-mode-common-hook', + * `de-html-helper' to disable `html-helper's template and time-stamps, + * `keys' to setup the default key bindings, + * `menus' to setup the menus." + :group 'template-miscellaneous + :type '(choice (const :tag "All" t) + (set :value (auto cc-mode keys menus) + (const :tag "Auto Updating/Inserting" auto) + (const :tag "Correct Auto Inserting with Ffap" ffap) + (const :tag "Correct C Comment Filling" cc-mode) + (const :tag "Deactivate html-helper" de-html-helper) + (const :tag "Setup Key Bindings" keys) + (const :tag "Setup Menus" menus)))) + + +;;;=========================================================================== +;;; Menu +;;;=========================================================================== + +(defvar template-comment-menu + '("Comment" + ["Decorate Comment Line" template-single-comment + :active (and (not buffer-read-only) + (memq (template-comment-at-point) + '(none delimited single cont)))] + ["Decorate Comment Block" template-block-comment + :active (and (not buffer-read-only) + (memq (template-comment-at-point) + '(single block)))] + "---" + ["Indent for Comment" indent-for-comment + :active (and comment-start (not buffer-read-only))] + ["Continue Comment" indent-new-comment-line + :active (and comment-start (not buffer-read-only))] + ["Comment Region" comment-region + :active (and comment-start (not buffer-read-only) (mark))] + ["Comment Region 2" (comment-region 2) + :active (and comment-start (not buffer-read-only) (mark))] + ["Comment Region 3" (comment-region 3) + :active (and comment-start (not buffer-read-only) (mark))] + "---" + ["Update Buffer" template-update-buffer + :active (and template-update-buffer-alist (not buffer-read-only))]) + "Menu for comment functions.") + +(defvar template-creation-menu + '("Template Creation" + :filter template-menu-filter + ["Open Template" template-open-template + :active (null (template-buffer-template-p))] + "--" + ["Define User Input" template-define-prompt t] + ["Define Text Register" template-define-register t] + ["Define Message" template-define-message t] + "---" + ["Insert Expansion Form" template-insert-form t]) + "Menu for template creation.") + + +;;;=========================================================================== +;;; Commenting +;;;=========================================================================== + +(defcustom template-max-column -1 + "*Width of the separator line, to use with an empty `comment-end'. +If the value is zero or negative, it is added to `fill-column'. See +also `template-max-column-with-end'." + :group 'template-comments + :type 'integer) + +(defcustom template-max-column-with-end 0 + "*Width of the separator line including a non-empty `comment-end'. +If the value is zero or negative, it is added to `fill-column'. See +also `template-max-column'." + :group 'template-comments + :type 'integer) + +(defcustom template-alt-comment-syntax-alist + '((t "/* " " */")) + "Alternative comment syntax for languages with \"mixed\" comments. +Used by function `template-comment-syntax'. Elements look like + (MODES-OR-REGEXP COMMENT-START COMMENT-END) + +If the current `major-mode' has a empty `comment-end' and a commenting +command does not work at `point' with the usual `comment-start', we +search for the first matching alternative comment syntax in this alist. + +Each element must \"pass\" MODES-OR-REGEXP. If this is a list, it must +include the current major-mode, if this is a regexp, it must match the +`buffer-file-name' without version, otherwise it must be non-nil. + +Then, COMMENT-START and COMMENT-END is used as the alternative comment +syntax if `comment-start-skip' matches COMMENT-START." + :group 'template-comments + :type '(repeat (group (choice (repeat :tag "In major modes" :value nil + function) + (regexp :tag "Buffer matching" :value "") + (sexp :tag "Always" :value t)) + (string :tag "Alt comment start" :value "/* ") + (string :tag "Alt comment end" :value " */")))) + +(defcustom template-comment-indent t + "Non-nil means, indent single-line/block comments. +Commands \\[template-single-comment] and \\[template-block-comment] +indent the comment lines if this value is non-nil and the current major +mode is not a member of `template-indent-mode-disable-list' or if this +value is nil and the current major mode is a member of +`template-indent-mode-enable-list'." + :group 'template-comments + :type 'boolean) + +(defcustom template-indent-mode-disable-list '(sh-mode makefile-mode) + "Major modes not having indented single-line/block comments. +Used if `template-comment-indent' is non-nil. Major modes in which +pressing TAB twice is different from pressing TAB once are good +candidates for this list." + :group 'template-comments + :type '(repeat (function :tag "Major mode"))) + +(defcustom template-indent-mode-enable-list nil + "Major modes having indented single-line/block comments. +Used if `template-comment-indent' is nil." + :group 'template-comments + :type '(repeat (function :tag "Major mode"))) + +(defcustom template-comment-specification-alist + '(("-" "" "" 0) + ("-" "" "" 0) + ("=" "\n\n" "\n" 1) + ("#" "\n\n\f\n" "\n\n" 2)) + "List of specifications for comment functions. +Each specification at LEVEL, starting at 1, is a list + (SEPARATOR BEFORE-BLOCK AFTER-BLOCK DELETE-LINES) + +SEPARATOR is the string which is inserted repeatedly by commands +\\[template-single-comment] and \\[template-block-comment] up to +`template-max-column'. + +After that, \\[template-block-comment] deletes DELETE-LINES after the +comment block and inserts string AFTER-BLOCK at the end of the block and +BEFORE-BLOCK at the front of the block. + +The specification LEVEL to use is determined by: + (1) If the prefix argument is non-nil and its numeric value is > 0, + this value is the LEVEL. + (2) If the prefix argument is nil, and there is an old comment style, + use old comment style. + (3) If `template-comment-specification-special' is a function or the + current major mode has a property with this name and its value is a + function, this function returns the specification. + (4) If `comment-end' is empty and `comment-start' is a string of length + 1: LEVEL is number of repetitions of `comment-start' at the + beginning of the line. Otherwise, if the correctly indented line + starts at the beginning of the line, LEVEL=3, else LEVEL=2." + :group 'template-comments + :type '(repeat (group (string :tag "Separator" :value "-") + (string :tag "Before block" :value "") + (string :tag "After block" :value "") + (integer :tag "Delete lines" :value 0)))) + +(defcustom template-comment-specification-special nil + "Function used for special commenting styles or nil. +See `template-comment-specification-alist' for details." + :group 'template-comments + :type '(choice (const nil) function)) + + +;;;=========================================================================== +;;; Auto updating +;;;=========================================================================== + +(defcustom template-auto-update 'query + "*Whether to update parts of the file when saving the buffer. +When non-nil and `template-auto-update-disable-regexp' does not match +the file name, automatically updates parts of the buffer, see +`template-update-buffer-alist'. With value t or if the entry in the +alist has no prompt, do not ask for confirmation. + +You should have called `template-initialize' to enable this feature." + :group 'template-updating + :type '(radio (const :tag "No" nil) + (const :tag "Without confirmation" t) + (sexp :tag "With confirmation" :format "%t" :value query))) + +(defcustom template-auto-update-disable-regexp nil + "*Regexp matching files not to automatically update. +Value nil matches no file. See `template-auto-update'." + :group 'template-updating + :type '(choice (const :tag "none" nil) regexp)) + +(defcustom template-update-buffer-alist + '((t "Update header in %s? " + (template-update-header t) + (file-name-sans-versions (file-name-nondirectory buffer-file-name))) + ((html-mode) "Update date inside <address> in %s? " + (-2000 + "\\([0-9]+[ \t]+[A-Za-z][A-Za-z][A-Za-z][ \t]+[0-9]+\\)[ \t\n]*</address>" + 1) + (format-time-string "%d %b %Y"))) + "Alist used how to update parts of the buffer. +Used by function `template-update-buffer'. Elements look like + (MODES-OR-REGEXP PROMPT TEST NEW REPLACEMENT-FUN) + +Each element must \"pass\" MODES-OR-REGEXP. If this is a list, it must +include the current major-mode, if this is a regexp, it must match the +`buffer-file-name' without version, otherwise it must be non-nil. + +Then, TEST is `eval'd and must return the region = (BEG . END) to be +replaced or nil if nothing should be updated according to the current +element. If TEST is a list and the `car' of TEST is not a function, +`template-update-buffer-region' is used as the default function, i.e., +REPLACEMENT-FUN looks like (LIMIT REGEXP GROUP). Then, check first/last +LIMIT characters in buffer and return region according to GROUP's regexp +group in REGEXP. + +Then, NEW is `eval'd. If it is a string, it is considered as +replacement for the region, otherwise REPLACE-FUN must be non-nil. + +Then, ask user for confirmation with PROMPT where %s is substituted by +the buffer name if PROMPT is a string and `template-auto-update' is not +t. + +Finally, REPLACEMENT-FUN is called the `eval'd NEW and the beginning and +the end of the region returned by TEST. If REPLACEMENT-FUN is nil, just +replace the region by the `eval'd NEW." + :group 'template-updating + :type '(repeat (group (choice (repeat :tag "In major modes" :value nil + function) + (regexp :tag "Buffer matching" :value "") + (sexp :tag "Always" :value t)) + (string :tag "Prompt" :value "Update in %s? ") + (choice (list :tag "Default test" + (choice (const :tag "No limit" nil) + (integer :tag "Limit" -1000)) + regexp + (integer :tag "Regexp group" :value 0)) + (sexp :tag "Eval sexp")) + (sexp :tag "Eval New string") + (option (function :tag "Replacement function"))))) + +(defcustom template-header-lines 3 + "*Last line number which is checked by \\[template-update-header]." + :group 'template-updating + :type 'integer) + +(put 'template-header-lines 'template-secure-value #'integerp) + +(defcustom template-header-regexp-alist + '(("@(#)\\([^ \t\n]+\\)" . 1) + ("^%s[ \t]*\\([^ \t\n%s][^ \t\n]*\\)[ \t]+--" . 1)) + "Alist of regexps matching the file name in the header. +The `car' of each element is the REGEXP with %s, if present, substituted +by the comment start. A second %s, if present, is substitud by a single +letter non-alpha comment start, or the empty string otherwise. + +The `cdr' is the regexp group to be replaced. Used by +\\[template-update-header]. + +The comment start is evaluated from `comment-start', the first character +in the buffer or \"#\". It is assumed that a non-alpha single character +comment start may be repeated. For example, the substituted regexp in +`emacs-lisp-mode' is \"\;+\", in `c++-mode' \"//\"." + :group 'template-updating + :type '(repeat (cons :format "%v" + regexp + (integer :tag "Regexp group" :value 0)))) + + +;;;=========================================================================== +;;; Templates: finding templates +;;;=========================================================================== + +(defcustom template-auto-insert 'query + "*Whether to automatically use template files for new files. +Used if the user gave a non-existent file as argument to a command in +`template-find-file-commands'. When non-nil and a matching template +file can be found, use a template like in `template-new-file'. File +name refinement is never performed, see `template-derivation-alist'. + +With value t, do not ask for confirmation. + +You should have called `template-initialize' to enable this feature." + :group 'template-derivation + :type '(radio (const :tag "No" nil) + (const :tag "Without confirmation" t) + (sexp :tag "With confirmation" :format "%t" :value query))) + +(defcustom template-find-file-commands + '(find-file find-file-other-frame find-file-other-screen + find-file-other-window find-file-at-point ffap nil) + "*Commands which use templates as last resort, see `template-auto-insert'. +See also `template-file-select-commands'. + +Include nil if you want to use templates for non-existing files as +command line arguments when starting Emacs." + :group 'template-derivation + :type '(repeat (function :tag "Command"))) + +(defcustom template-file-select-commands + '(exit-minibuffer minibuffer-complete-and-exit + list-mode-item-mouse-selected + list-mode-item-keyboard-selected) + "*Commands which select the file name via minibuffer/completions. +Checked with commands in `template-find-file-commands'." + :group 'template-derivation + :type '(repeat (function :tag "Command"))) + +(defface template-message-face + '((((class color) (background light)) (:background "pink")) + (t (:bold t))) + "Face for temporary message at point. This only works with XEmacs." + :group 'template-miscellaneous) + +(defcustom template-extension ".tpl" + "*Extension used for template files." + :group 'template-derivation + :type 'string) + +(defcustom template-subdirectories '("./" "Templates/") + "*List of subdirectories for template files. +See `template-derivation-alist' for details." + :group 'template-derivation + :type '(repeat directory)) + +(defcustom template-stop-derivation + (cond ((fboundp 'file-remote-p) 'file-remote-p) + ((fboundp 'efs-ftp-path) 'efs-ftp-path) + ((fboundp 'ange-ftp-ftp-path) 'ange-ftp-ftp-path)) + "If non-nil, function used to determine whether to stop derivation. +If non-nil, function is called with argument DIR. If it returns t, +`template-derivation' stops to search for more project specific +templates, i\.e\., just searches in `template-default-directories'." + :group 'template-derivation + :type '(choice (const :tag "Never" nil) + function)) + +(defcustom template-default-directories + (cons (if (and (not (file-directory-p "~/.templates/")) + (file-directory-p "~/lib/templates")) + (expand-file-name "~/lib/templates/") + (expand-file-name "~/.templates/")) + (and (fboundp 'locate-data-directory) + (let ((dir (locate-data-directory "template"))) + (and dir (list dir))))) + "*List of default directories for template files. +See `template-derivation-alist' for details." + :group 'template-derivation + :type '(repeat directory)) + +(defcustom template-derivation-alist + '(;;(("00readme" "" ".txt" "\\`00") . ("00readme" "" ".txt")) + ((t "" t)) + ((t nil null) . (nil nil t 1)) + (("TEMPLATE" "" t))) + "Alist for template file name derivation and file name refinement. +Template derivation searches for the most specific readable template +file. By default, files with the same RAW part as the name of the new +file are considered to be more specific than files with just the same +EXT part. Also files in the same directory are considered to be more +specific than files in their parent directory or any default template +directory. This behavior can be changed by this alist. + +Each FORM in this alist has the form (TEMPLATE . REFINEMENT). If +TEMPLATE matches, we have found a valid template file and the +corresponding REFINEMENT is used for the file name refinement. + +Before the derivation, the given file name is split into the directory +part DIR, the file name without directory FILE, and the raw part RAW of +FILE, the numbering NUM and the extension EXT. The result is stored in +`template-file'. + +TEMPLATE can have the form (FUNCTION ARG...). If TEMPLATE matches, +FUNCTION, called with arguments ARGs, should return the split template +file name, see `template-split-filename'. + +TEMPLATE can also have the form (T-RAW T-NUM T-EXT F-REGEXP) where all +elements are optional, i.e., have value nil as default. For TEMPLATE to +match, all conditions T-RAW, T-NUM and T-EXT must be met and F-REGEXP, +if non-nil, should match FILE, the non-directory part of the given file +name. If a condition is a string, the corresponding part of the +template file must be equal to it. If t, the part must be equal to +RAW/NUM/EXT of the given file name. If nil, any value will do it. Any +other value acts like t when the part of the given file name is +non-empty, as nil otherwise. + +REFINEMENT can have the form (FUNCTION ARG...). FUNCTION, called with +the list of the split template filename and ARGs as arguments, should +set `template-file' if the file name should be refined. + +REFINEMENT can also have the form (F-RAW F-NUM F-EXT AUTO-NUM) where all +elements are optional, i.e., have value nil as default. If F-RAW, F-NUM +and F-EXT are non-nil, they change RAW/NUM/EXT of `template-file'. A +string will be used as the new part. If t, the corresponding part of +the template name will be used. + +We will use auto numbering in the following two cases: if NUM is +non-empty and the file exists already, or if NUM is empty and AUTO-NUM +is non-nil. Auto numbering looks at the file names in DIR to generate +the next unique number which is at least as high as NUM in the first +case and AUTO-NUM in the second. + +Let us use parts of the default value as examples: + +Use a template with the same RAW part of the given file name and the +same EXT part if provided, e.g., for \"exercise2\" use template +\"exercise.tex.tpl\". Refine file name to use the extension of the +template file, also use auto numbering, e.g., if files \"exercise2.tex\" +and \"exercise3.tex\" exist, refine name to \"exercise4.tex\": + ((t nil null) \. (nil nil t 1)) + +For a file with extension EXT, use TEMPLATE.EXT: + ((\"TEMPLATE\" \"\" t)) + +We could define: If the given file name starts with \"00\", use template +\"00readme.txt.tpl\". Refine file name to \"00readme.txt\": + ((\"00readme\" \"\" \".txt\" \"\\\\`00\") \. (\"00readme\" \"\" \".txt\")) + +Since more than one template file could meet this conditions, the +template derivation searches for first readable file with extension +`template-extension' which is found by the following algorithm: + + forall FORMs in `template-derivation-alist' do + for directory BASE from DIR + while not stopped according to `template-stop-derivation' do + forall subdirectories DIRs in `template-subdirectories' + relative to BASE do + forall TEMPLATEs in DIR do + if check_form (FORM, FULL, TEMPLATE) return TEMPLATE + forall directories DIRs in `template-default-directories' do + forall TEMPLATEs in DIR do + if check_form (FORM, FULL, TEMPLATE) return TEMPLATE + if not used via `template-auto-insert' + forall TEMPLATEs in `template-default-directories' + where name_nondir (TEMPLATE) = \"DEFAULT.tpl\" do + if readable (TEMPLATE) return TEMPLATE + return TEMPLATE in first (`template-default-directories') + where name_nondir (TEMPLATE) = \"DEFAULT.tpl\"" + :group 'template-derivation + :type '(repeat (cons :format "%v" + (sexp :tag "Derivation" :value ("TEMPLATE" nil t)) + (sexp :tag "Refinement" :value nil)))) + + +;;;=========================================================================== +;;; Templates: expanding templates +;;;=========================================================================== + +(defcustom template-confirm-insecure t + "*Non-nil means, ask whether to use insecure template expansions. +Only set this to nil if you ALWAYS check template files before using +it!" + :group 'template-expansion + :type 'boolean) + +(put 'template-confirm-insecure 'risky-local-variable t) + +(defcustom template-message-buffer "*Template Message*" + "If non-nil, name of buffer where messages are shown. +The following messages will be displayed in definition sequence before +the expansion has taken place: + - :before messages, see `template-definition-start', + - user defined prompts if `template-message-prompt-format' is non-nil, + - user defined registers when there has been a :before message before, + see `template-message-register-format'. + +The following messages will be displayed in definition sequence after +the expansion has taken place: + - :after messages, see `template-definition-start', + - user defined registers, display them at point if the value of this + variable is nil, see `template-message-register-format'." + :group 'template-miscellaneous + :type '(choice (const :tag "None" nil) + (string :tag "Buffer Name"))) + +(defcustom template-message-prompt-intro + "Template expansion will ask for input with the following prompts:" + "Default intro message used before listing user defined prompts. +Used with :before messages, see `template-message-prompt-format'." + :group 'template-miscellaneous + :type '(choice (const :tag "None" nil) + (string :tag "Intro text"))) + +(defcustom template-message-prompt-format " %s" + "If non-nil, format string for user defined prompts. +If non-nil and `template-message-buffer' is non-nil, user defined +prompts will be listed before starting the expansions. Prompts can be +defined as specified in the docstring of `template-definition-start'. +For each PROMPT, this format string will be used with substitution +PROMPT/%s. + +If no :before message has been defined before, use, if non-nil, +`template-message-prompt-intro' as the first :before message." + :group 'template-miscellaneous + :type '(choice (const :tag "No prompt" nil) + (string :tag "Format string"))) + +(defcustom template-message-register-intro + "Template has defined the following registers:" + "Default intro message used before listing user defined prompts. +Used with :after messages, see `template-message-register-format'." + :group 'template-miscellaneous + :type '(choice (const :tag "None" nil) + (string :tag "Intro text"))) + +(defcustom template-message-register-format " %c:\t\"%s\"\t%s" + "If non-nil, format string for user defined registers. +If non-nil, user defined text registers will be listed. Registers can +be defined as specified in the docstring of `template-definition-start'. +For each register CHAR with contents CONTENTS and optional comment +COMMENT, this format string will be used with substitution CHAR/%c, +CONTENT/%s and \(COMMENT/%s or \"\"/%s). + +The list of register definitions will be displayed: + - at point if `template-message-buffer' is nil, + - with :before messages if there has been at least one :before message + defined before and if `template-message-buffer' is non-nil, + - with :after messages if `template-message-buffer' is non-nil. + If no :after message has been defined before, use, if non-nil, + `template-message-register-intro' as the first :after message." + :group 'template-miscellaneous + :type '(choice (const :tag "No register content" nil) + (string :tag "Format string"))) + +(defcustom template-message-timeout 600 + "*Maximum duration the temporary message will be displayed at point. +Any user event will also make the temporary message disappear. The +temporary message uses face in `template-message-face'." + :group 'template-miscellaneous + :type 'integer) + +(put 'template-message-timeout 'template-secure-value #'integerp) + +(defcustom template-date-format "%d %b %Y" + "*Date/time format used with the expansion form (>>>DATE<<<). +See `template-default-expansion-alist' and `format-time-string'. See +also `template-time-format'." + :group 'template-expansion + :type 'string) + +(put 'template-date-format 'template-secure-value #'stringp) + +(defcustom template-time-format "%T" + "*Date/time format used with the expansion form (>>>TIME<<<). +See `template-default-expansion-alist' and `format-time-string'. See +also `template-date-format'." + :group 'template-expansion + :type 'string) + +(put 'template-time-format 'template-secure-value #'stringp) + +(defcustom template-string-default "%0.0S" + "*Format string used for non-string variable extensions. +If SYMBOL in (\"KEY\" \. SYMBOL) is not a string, use string with +substitution SYMBOL/%S. Default value \"%0.0S\" causes to print +nothing. See `template-definition-start'." + :group 'template-expansion + :type 'string) + +(put 'template-string-default 'template-secure-value #'stringp) + +(defcustom template-expansion-format "(>>>%s<<<)" + "Format string for expansion forms. +Is a expansion form with substitution KEY/%s. The value should +correspond with `template-expansion-regexp'. Used by +`template-insert-form'." + :group 'template-expansion + :type 'string) + +(put 'template-expansion-format 'template-secure-value #'stringp) + +(defcustom template-expansion-regexp "(>>>\\([-A-Za-z0-9_]+\\)<<<)" + "Regexp matching strings which are replaced by their expansions. +The first regexp group contains the KEY used by the per-template +expansion, see `template-definition-start' and the global expansions in +`template-expansion-alist' and `template-default-expansion-alist'. The +value should correspond with `template-expansion-alist'. + +If there is no defined expansion for the key, ask the user for a +replacement, see `template-read'. If the key is matched by +`template-register-regexp', store buffer position in register, see +`template-register', . + +If you want to use a text literally which is matched by this regexp, use +the zero expansion form (>>>ZERO_FORM<<<)." + :group 'template-expansion + :type 'regexp) + +(put 'template-expansion-regexp 'template-secure-value #'stringp) + +(defcustom template-literal-environment '("LITERAL" . "/LITERAL") + "Environment for literal text in template. +Looks like (OPEN . CLOSE). Text between expansion forms with keys OPEN +and CLOSE is not expanded. If you change OPEN, you should change key +\"LITERAL\" in `template-default-expansion-alist' accordingly." + :group 'template-expansion + :type '(cons (string :tag "Open tag") (string :tag "Close tag"))) + +(defcustom template-register-regexp "\\`[0-9]\\'" + "*Regexp matching keys for storing point positions in registers. +These keys use `template-register' as the default expansion instead of +`template-read'. See `template-expansion-regexp'. If a register is used +twice, it is marked by a \"*\" in the echo area after the expansion." + :group 'template-expansion + :type 'regexp) + +(put 'template-register-regexp 'template-secure-value #'stringp) + +(defcustom template-expansion-alist nil + "User defined expansions forms. +Predefined expansion forms for `template-expansion-regexp'. Each entry +has the form (KEY . SEXP). These expansion forms shadow those in +`template-default-expansion-alist' and are shadowed by those in the +per-template definition section. See `template-definition-start'." + :group 'template-expansion + :type '(repeat (cons :format "%v" + (string :tag "Key" :value "") + (repeat :tag "Evaluate all" sexp)))) + +(put 'template-expansion-alist 'risky-local-variable t) + +(defvar template-default-expansion-alist + '(("POINT" (setq template-point (point-marker))) ; point + ("MARK" (setq template-mark (point-marker))) ; mark + ("DIR" (insert (car template-file))) ; directory + ("FILE" (insert (cadr template-file))) ; file name without directory + ("FILE_SANS" (insert (nth 2 template-file) + (nth 3 template-file))) + ("FILE_RAW" (insert (nth 2 template-file))) ; raw file name without number + ("FILE_NUM" (insert (nth 3 template-file))) ; number + ("FILE_UPCASE" (insert (upcase (nth 2 template-file)) + (nth 3 template-file))) + ("FILE_EXT" (or (string= (nth 4 template-file) "") ; extension + (insert (substring (nth 4 template-file) 1)))) + ("DATE" (template-insert-time template-date-format)) + ("TIME" (template-insert-time template-time-format)) + ("VC_DATE" (set-time-zone-rule "UTC") + (template-insert-time "%Y/%m/%d %T" "0000/00/00 00:00:00") + ;; using saved `current-time-zone' doesn't work, but nil does + (set-time-zone-rule nil)) + ("YEAR" (template-insert-time "%Y" "0000")) + ("ISO_DATE" (template-insert-time "%Y-%m-%d" "0000-00-00")) + ("COMMENT" (template-read "Initial comment: ")) ; comment + ("AUTHOR" (insert (or user-mail-address ; author + (and (fboundp 'user-mail-address) + (user-mail-address)) + (concat (user-login-name) "@" (system-name))))) + ("USER_NAME" (insert (or (and (boundp 'user-full-name) ; user name + user-full-name) + (user-full-name)))) + ("LOGIN_NAME" (insert (user-login-name))) ; login name + ("HOST_ADDR" (insert (or (and (boundp 'mail-host-address) ; host address + (stringp mail-host-address) + mail-host-address) + (system-name)))) + ("LITERAL" (if (search-forward (format template-expansion-format + (cdr template-literal-environment)) + nil 'limit) + (delete-region (match-beginning 0) (match-end 0)))) + ("ZERO_FORM")) ; zero form + "Predefined default expansions forms. +Predefined expansion forms for `template-expansion-regexp'. Each entry +has the form (KEY . SEXP). These expansion forms are shadowed by those +in `template-expansion-alist' and by those in the per-template +definition section. See `template-definition-start'. + +The default predefined expansion forms are --default is inserting--: + (>>>POINT<<<) set point + (>>>MARK<<<) set mark, jump to it with \\[exchange-point-and-mark] + (>>>DIR<<<) directory: /home/clstaff/wedler/lib/ + (>>>FILE<<<) file w/o directory: text1.txt + (>>>FILE_SANS<<<) file name w/o extension: text1 + (>>>FILE_RAW<<<) raw file name: text + (>>>FILE_NUM<<<) number in name: 1 + (>>>FILE_EXT<<<) extension: txt + (>>>FILE_UPCASE<<<) upcase file name w/o extension: TEXT1 + (>>>DATE<<<) date using `template-date-format': 11 Jan 1999 + (>>>TIME<<<) time using `template-time-format': 11:58:49 + (>>>YEAR<<<) the year: 1999 + (>>>ISO_DATE<<<) ISO 8601 date: 1999-01-11 + (>>>VC_DATE<<<) UTC date/time for vc: 1999/01/11 10:58:49 + (>>>COMMENT<<<) ask user for initial comment + (>>>AUTHOR<<<) author, i.e., `user-mail-address' + (>>>USER_NAME<<<) user name: Christoph Wedler + (>>>LOGIN_NAME<<<) login name: wedler + (>>>HOST_ADDR<<<) Host address: fmi.uni-passau.de + (>>>LITERAL<<<) literal text up to (>>>/LITERAL<<<) + (>>>ZERO_FORM<<<) zero form, i.e., insert nothing. Useful to insert + a text part matched by `template-expansion-regexp' literally. + +There are aliases with one-letter keys, see `template-key-alias-alist'. + +It is useful to follow the following conventions: upper case keys for +predefined extensions, lower case and digits for per-template and the +following default expansions: + (>>>0<<<) to (>>>9<<<) set registers 0 to 9, jump to it with + \\[jump-to-register] 0 etc., see `template-register-regexp' + (>>>x<<<) where x is any unused letter sequence: ask user.") + +(put 'template-default-expansion-alist 'risky-local-variable t) + +(defvar template-key-alias-alist + '(("P" . "POINT") + ("M" . "MARK") + ("D" . "DIR") + ("F" . "FILE") + ("R" . "FILE_RAW") + ("N" . "FILE_NUM") + ("B" . "FILE_UPCASE") + ("E" . "FILE_EXT") + ("T" . "DATE") + ("V" . "VC_DATE") + ("Y" . "YEAR") + ("I" . "ISO_DATE") + ("C" . "COMMENT") + ("A" . "AUTHOR") + ("U" . "USER_NAME") + ("L" . "LOGIN_NAME") + ("H" . "HOST_ADDR") + ("Z" . "ZERO_FORM")) + "Alist to support the old one-letter predefined expansion forms. +Used for `template-expansion-alist' and +`template-default-expansion-alist'.") + +(defcustom template-definition-start + ">>>TEMPLATE-DEFINITION-SECTION<<<" + "Header for the per-template definition section. +The region following the the first match of this regexp defines the +per-template definition section. The region will be deleted before the +actual expansion, see `template-new-file'. If you use the \"Local +Variables:\" section, define it before this region. + +The definition section defines expansion forms for strings KEYs matched +by `template-expansion-regexp' which might shadow those in +`template-expansion-alist' and `template-default-expansion-alist': + + (\"KEY\"): zero form, same as (>>>ZERO_FORM<<<) in default value of +`template-default-expansion-alist', useful for inserting text matched by +`template-expansion-regexp' literally. + + (\"KEY\". CHAR): CHAR is the register where the current buffer +position is stored, see `template-register-regexp'. + + (\"KEY\" \"PROMPT\" \"PREFIX\" \"SUFFIX\" \"DEFAULT\" AGAIN-P) where +the last four arguments are optional: ask user with PROMPT for a STRING. +If STRING is not \"\", insert PREFIX STRING SUFFIX, otherwise DEFAULT. +For AGAIN-P, see `template-read'. To define, use +\\[template-define-prompt]. + + (\"KEY\" \"PROMPT\" (\"ANSWER\" \. \"TEXT\")...): ask user with PROMPT +for an input with completion over all ANSWERs and insert corresponding +TEXT. Expansion forms in TEXT will be expanded. + + (\"KEY\" \"PROMPT\" (t \. \"TEXT-y\") (nil \. \"TEXT-n\")): ask user +with PROMPT a \"y or n\" question with `y-or-n-p' and insert TEXT-y or +TEXT-n, correspondingly. Expansion forms in TEXT-X will be expanded. +The y-case and the n-case are optional and can be exchanged. + + (\"KEY\" \. SYMBOL): insert value of SYMBOL; if value is no string at +the time of the replacement, use `template-string-default' as format +string for SYMBOL. + + (\"KEY\" COMMAND \. PREFIX): COMMAND is a symbol or a vector and is +called with `command-execute' after setting `prefix-arg' to PREFIX, not +evaluated. If COMMANDs symbol property `template-secure-command' is +nil, the form is insecure. If that symbol property is a function, it is +called with PREFIX to check whether COMMAND could be called directly +with PREFIX as remaining arguments. + + (\"KEY\" SEXPR...): evaluate SEXPR during the expansion, see +`template-expansion-alist' for examples. This form is insecure. + +There are other per-template definitions: + + \"MESSAGE\": additional line displayed at point until first user event +or after `template-message-timeout' seconds. The lines are displayed +with face in `template-message-face'. With active form selector +:before, define a message which is displayed in +`template-message-buffer' before the exansion has started. With active +form selector :after, define a message which is displayed in +`template-message-buffer' after the exansion has taken place. To +define interactively, use \\[template-define-message]. + + (CHAR \. \"CONTENTS\"): Set register CHAR to have contents CONTENTS. +CONTENTS can then be inserted into a buffer with \\[insert-register] CHAR. + + (CHAR \"CONTENTS\" COMMENT) where COMMENT is optional: Set register +CHAR to have contents CONTENTS. CONTENTS can then be inserted into a +buffer with \\[insert-register] CHAR. Also display an additional line +at point to show the contents with COMMENT. To define, use +\\[template-define-register]. + +The following forms depend on the active form selector which is the last +of the following expansion forms: + - :before: \"MESSAGE\" will be displayed before the expansion + - :after: \"MESSAGE\" will be displayed after the expansion + - :eval-before: execute COMMAND and SEXPR before expansion + - :eval-after: execute COMMAND and SEXPR after expansion + - nil, deprecated: with the first form, the active form selector is + :eval-before, with the second, it is :eval-after. + + (VARIABLE . VALUE): set SYMBOL's local value to VALUE, not evaluated. +This form is only secure if VARIABLE has a symbol property +`template-secure-value' which returns non-nil when applied to VALUE, not +evaluated. This form is useful for variables which determine the +expansion, like `template-time-format' and `template-date-format'. For +local variables in your new file, use the normal way via the \"Local +Variables:\" section. The active form selector must not be :eval-before +or :eval-after. + + COMMAND: COMMAND is a symbol or a vector and is called with +`command-execute' before the expansion with form selector :eval-before, +and after the expansion with form selector :eval-after. If COMMANDs +symbol property `template-secure-command' is nil, the form is insecure. +You should use the safe command `normal-mode' in the pre-expansion forms +if the expansion forms depend on the correct major mode. + + SEXPR: evaluate SEXPR before the expansion with form selector +:eval-before, and after the expansion with form selector :eval-after. +This form is insecure. + +If any insecure forms have been used, the user of the template will be +asked whether to use the template, see `template-confirm-insecure'." + :group 'template-expansion + :type 'string) + + + +;;;;########################################################################## +;;;; Commenting +;;;;########################################################################## + + +(defunx template-point-at-bol (&optional count) + :emacs-only line-beginning-position + :xemacs-only point-at-bol) + +(defunx template-point-at-eol (&optional count) + :emacs-only line-end-position + :xemacs-only point-at-eol) + +(defunx template-char-or-char-int-p (object) + :emacs-only integerp + :xemacs-only char-or-char-int-p) + +(defunx template-char-or-int-to-char (object) + :emacs-only identity + "Convert character or integer OBJECT into the equivalent character." + (if (characterp object) object (int-to-char object))) + + +;;;=========================================================================== +;;; Main functions +;;;=========================================================================== + +;;;###autoload +(defun template-single-comment (&optional arg) + "Decorate the comment in the current line with dashes and alike. +The line must be a comment-only line or must contain a comment ending by +eol. That is, jump to the end of the current line and insert the dashes +and the final comment end-string up-to the fill position. Prefix +argument ARG and `template-comment-specification' determines the comment +style to use. The length of the resulting line is determined by +`template-max-column' and `template-max-column-with-end'." + (interactive "*P") + (let* ((orig (point-marker)) + (syntax0 (and comment-start comment-start-skip + (condition-case nil + (template-comment-syntax orig 'boc) + (error nil)))) + (syntax (cond ((cdr syntax0) + (template-comment-syntax orig)) + (syntax0 + (condition-case nil + (template-comment-syntax (point-marker)) + (error syntax0))) + (t + (back-to-indentation) + nil))) + (sep (template-comment-separator-regexp syntax)) + (end (template-point-at-eol)) + old) + (save-excursion + (cond ((re-search-forward sep end t) + ;; with sep in current line + (setq old (buffer-substring (match-beginning 1) (match-end 1))) + (delete-region (match-beginning 0) (match-end 0))) + ((cdr syntax) ; with start-end comment + (if (looking-at (concat "[ \t]*\\(.+\\)?" + (regexp-quote (cadr syntax)) + "[ \t]*\\(.+\\)?$")) + (if (or (match-beginning 1) (match-beginning 2)) + (error "This line contains non-separator chars and %S" + (cadr syntax)) + ;; Delete comment-end. Don't delete its first char if it is + ;; the same as the second of comment-start. + (delete-region (if (and (= (length (car syntax)) 2) + (= (length (cadr syntax)) 2) + (eq (aref (car syntax) 1) + (aref (cadr syntax) 0))) + (1+ (match-beginning 0)) + (match-beginning 0)) + (match-end 0)))) + (goto-char (cddr syntax)) + (if (re-search-forward sep end t) + ;; sep in line between comment-start and point-at-eol + (setq old (buffer-substring (match-beginning 1) + (match-end 1))))))) + (template-insert-separator + (car (template-comment-specification arg old syntax)) + nil syntax))) +(put 'template-single-comment 'template-secure-command t) + +;;;###autoload +(defun template-block-comment (&optional arg) + "Decorate the current block of comment-only lines with dashes and alike. +That is, surround the the contiguous comment-only lines around point +with extra lines containing dashes and alike and to put the correct +number of newlines around the block. + +Barf if the comment syntax at point has a non-empty `comment-end' or if +point is not in a comment-only line. + +A block comment consists of all neighboring lines which start with +spaces and `comment-start'. If `comment-start' is a string of length 1, +the number of repetitions of `comment-start' must be the same or larger +than in the line where the command is invoked from, too. + +Prefix argument ARG and `template-comment-specification' determines the +comment style to use. The length of the separator line is determined by +`template-max-column'. + +This command can also be used with point in an empty line after a block +comment. A second invocation of this command directly after a +successful invocation deletes the remaining empty lines from the current +line on." + (interactive "*P") + (let* ((orig (point-marker)) + (syntax (progn + (end-of-line) + (skip-chars-backward " \t\n\f") + (template-comment-syntax orig)))) + (when (cdr syntax) + (goto-char orig) + (error "Command only works with comments terminated by end-of-line")) + + (if (and (eq last-command 'template-block-comment-success) + (looking-at "[ \t]*$")) + (template-insert-newline "" nil (1- (template-point-at-bol))) + (let* ((prefix (concat "[ \t]*" (regexp-quote (car syntax)))) + (sepline (concat prefix "[ \t]*" + (template-comment-separator-regexp syntax))) + old block-beg block-end def) + ;; go to the first line with same comment prefix --------------------- + (beginning-of-line) + (while (and (not (bobp)) (looking-at prefix)) + (beginning-of-line 0)) + (or (looking-at prefix) (beginning-of-line 2)) + (while (looking-at sepline) + (setq old (buffer-substring (1- (match-end 0)) (match-end 0))) + (kill-line 1)) + (setq block-beg (point-marker)) + ;; go to the last line with same comment prefix ---------------------- + (while (looking-at prefix) + (template-indent-according-to-mode) + (beginning-of-line 2)) + (if (eobp) (newline)) + (setq block-end (copy-marker (point) t)) + (while (progn (forward-line -1) (looking-at sepline)) + (setq old (buffer-substring (1- (match-end 0)) (match-end 0))) + (kill-line 1)) + ;; insert separator lines -------------------------------------------- + (goto-char block-beg) + (set-marker block-beg nil) + (back-to-indentation) + (setq def (template-comment-specification arg old syntax)) + (beginning-of-line) + (template-insert-newline (cadr def)) + (template-insert-separator (car def) (car syntax) syntax) + (goto-char block-end) + (set-marker block-end nil) + (template-insert-separator (car def) (car syntax) syntax) + (template-insert-newline (caddr def) + (and (cadddr def) + (save-excursion + (forward-line (cadddr def)) + (point)))) + (setq this-command 'template-block-comment-success))) + (template-indent-according-to-mode) + (back-to-indentation))) +(put 'template-block-comment 'template-secure-command t) + + +;;;=========================================================================== +;;; Check comment start, return specification +;;;=========================================================================== + +(defun template-indent-according-to-mode () + "Indent line according to `template-comment-indent'." + (if (if template-comment-indent + (not (memq major-mode template-indent-mode-disable-list)) + (memq major-mode template-indent-mode-enable-list)) + (indent-according-to-mode))) + +(defun template-default-comment () + "Return default comment according to current position." + (if comment-start + (substring comment-start 0 (string-match "[ \t]\\'" comment-start)) + (if (eolp) "#" + (let ((default (buffer-substring (point) (1+ (point))))) + (if (string-match "[A-Za-z]" default) "#" default))))) + +(defun template-comment-at-point () + "Return the comment syntax at the current position. +Return nil, if no commenting command can be used, i.e., if point is not +in a comment-only line. Return `none' if the `major-mode' doesn't +define a comment syntax. Return `delimited' if point is between +`comment-start' and a non-empty `comment-end'. Return `single' if point +is in a comment line where the comment syntax has a empty `comment-end', +return `block' if point is in an empty line after such a comment line." + (if (and comment-start comment-start-skip) + (save-excursion + (let ((orig (point))) + (condition-case nil + (progn + (end-of-line) + (skip-chars-backward " \t\n\f") + (if (cdr (template-comment-syntax orig t)) 'delimited + (if (< (template-point-at-eol) orig) 'block 'single))) + (error + (condition-case nil + (progn + (goto-char orig) + (beginning-of-line) + (when (re-search-forward comment-start-skip + (template-point-at-eol) t) + (goto-char (or (match-end 1) (match-beginning 0))) + (unless (or (cdr (template-comment-syntax orig 'boc)) + (< (template-point-at-eol) orig)) + 'cont))) + (error nil)))))) + 'none)) + +(defun template-comment-syntax (orig &optional no-indent) + "Return the comment syntax at ORIG. Signal error if not in comment. +Return (COMMENT-START) if the comment syntax has an empty `comment-end'. +Return (COMMENT-START COMMENT-END . START-POS) if the comment syntax has +a non-empty `comment-end' where START-POS is the position of the first +character inside the comment. Move point to first character after the +comment start or the first non-whitespace character on this line. + +ORIG should be the same as `point' or in a empty line after `point'. + +If optional argument NO-INDENT is nil, indents the current line +according to `template-comment-indent' and `indent-according-to-mode'. +If NO-INDENT is `boc', move point to the beginning of the comment. + +COMMENT-START is stripped off its final spaces, COMMENT-END off its +initial spaces." + (unless (and comment-start comment-start-skip) + (error "No comment syntax has been defined for %s" major-mode)) + (if (eq no-indent 'boc) + (progn + (beginning-of-line) + (if (re-search-forward comment-start-skip (template-point-at-eol) t) + (goto-char (or (match-end 1) (match-beginning 0))))) + (or no-indent (template-indent-according-to-mode)) + (back-to-indentation)) + (let* ((string (template-default-comment))) + (if (string= comment-end "") + (if (looking-at (concat (regexp-quote string) + (and (= (length string) 1) + (not (eq no-indent 'boc)) + "+"))) + (progn + (goto-char (match-end 0)) + (list (buffer-substring (match-beginning 0) (point)))) + (let ((alist template-alt-comment-syntax-alist) + elem c-start c-end) + (while alist + (setq elem (pop alist)) + (and (template-match-modes-or-regexp (car elem)) + (string-match comment-start-skip (cadr elem)) + (setq c-start (cadr elem) + c-end (caddr elem) + alist nil))) + (template-comment-syntax-0 orig c-start c-end string))) + (template-comment-syntax-0 orig comment-start comment-end)))) + +(defun template-comment-syntax-0 (orig c-start c-end &optional single) + ;; checkdoc-params: (orig c-start c-end single) + "Internal function for `template-comment-syntax'." + (unless (and (stringp c-start) (stringp c-end) (not (string= c-end ""))) + (goto-char orig) + (error "Line does not start with %S" + (or single (template-default-comment)))) + (setq c-start (substring c-start 0 (string-match "[ \t]\\'" c-start)) + c-end (if (string-match "\\`[ \t]+" c-end) + (substring c-end (match-end 0)) + c-end)) + (cond ((looking-at (regexp-quote c-start)) + (goto-char (match-end 0)) + (cons c-start (cons c-end (point)))) + ((save-excursion + (when (re-search-backward (concat "^[ \t]*" (regexp-quote c-start)) + nil t) + (goto-char (match-end 0)) + (let ((match (point))) + (unless (when (search-forward c-end orig t) + (skip-chars-forward " \t") + (< (point) orig)) + (cons c-start (cons c-end match))))))) + (t + (goto-char orig) + (if single + (error "Not inside a comment (%S or %S-%S) starting in new line" + single c-start c-end) + (error "Not inside a comment (%S-%S) starting in new line" + c-start c-end))))) + + +;;;=========================================================================== +;;; Comment specification +;;;=========================================================================== + +;; A simple `mapconcat' is likely to slow down Emacs' regexp search algorithm +;; considerably (backtracking => near-infloop). +(defun template-comment-separator-regexp (syntax) + "Return regexp matching separator comment lines. +The regexp also matches if the lines ends with parts of COMMENT-END in +argument SYNTAX, see `template-comment-syntax'." + (let ((estring (cadr syntax)) + (alist template-comment-specification-alist) + (chars nil) + str i c) + (while alist + (setq str (car (pop alist))) + (when str + (setq i (length str)) + (while (>= (decf i) 0) + ;; (pushnew (aref str i) chars), but requires cl at runtime: + (or (memq (setq c (aref str i)) chars) (push c chars))))) + (concat "\\(" + (mapconcat (lambda (c) (regexp-quote (char-to-string c))) + (or chars "#") + "\\|") + (if estring + (concat "\\)+[ \t]*" + (mapconcat (lambda (c) + (regexp-quote (char-to-string c))) + estring + "?") + "?[ \t]*$") + "\\)+[ \t]*$")))) + +(defun template-comment-specification (arg old syntax) + "Return the comment specification to use. +See `template-comment-specification-alist' for details. ARG is the +prefix argument, OLD the SEPARATOR of the old comment style and SYNTAX +is the comment syntax returned by `template-comment-syntax'." + (and arg (setq arg (prefix-numeric-value arg))) + ;; assumes point-at-indentation + (or (and arg (> arg 0) + (if (< (length template-comment-specification-alist) arg) + (car (last template-comment-specification-alist)) + (nth (1- arg) template-comment-specification-alist))) + (and (null arg) old + (assoc old template-comment-specification-alist)) + (and (functionp template-comment-specification-special) + (funcall template-comment-specification-special)) + (and (functionp (get major-mode 'template-comment-specification-special)) + (funcall (get major-mode 'template-comment-specification-special))) + (and syntax + (template-comment-specification + (if (or (cdr syntax) (> (length comment-start) 1)) + (save-excursion + (if (cddr syntax) (goto-char (cddr syntax))) + (beginning-of-line) + (if (looking-at "[ \t]") 2 3)) + (length (car syntax))) + nil nil)) + '("-" "" "" 0))) + + +;;;=========================================================================== +;;; Inserting +;;;=========================================================================== + +(defun template-insert-newline (string &optional limit start-limit) + "Deletes blank lines around point and insert STRING. +After optional LIMIT and before optional START-LIMIT, no character will +be deleted." + (let ((start (save-excursion + (skip-chars-backward " \t\n\f" start-limit) + (or (bobp) (forward-line 1)) + (point))) + (end (save-excursion + (skip-chars-forward " \t\n\f" limit) + (beginning-of-line) + (point)))) + (if (> end start) (delete-region start end))) + (or (bobp) (insert string))) + +(defun template-insert-separator (separator &optional cstring syntax) + "Insert separator line at point. +If CSTRING is not nil, insert in special line which starts with CSTRING. +Insert SEPARATOR repeatedly. End the line with COMMENT-END as specified +in `template-comment-syntax'." + (when separator + (when cstring + (open-line 1) + (insert cstring) + (template-indent-according-to-mode)) + (end-of-line) + (let* ((estring (cadr syntax)) + (max-column (if estring + template-max-column-with-end + template-max-column)) + (max (- (if (> max-column 0) max-column (+ fill-column max-column)) + (length separator) + (length estring)))) + (while (<= (current-column) max) (insert separator)) + (if (>= (length separator) (- (current-column) max)) + (insert (substring separator 0 (- max (current-column))))) + (if estring (insert estring)) + (if cstring (forward-line 1))))) + + +;;;=========================================================================== +;;; Adaptations: cc-mode +;;;=========================================================================== + +;; There isn't really anything I can do against the filling of "/**" in C, C++ +;; and Antlr mode (it is correct in Java), it should be done in the cc-mode +;; package. Similar for filling "*/" with the previous line... + +(defun template-c-fill-paragraph (&optional arg) + ;; checkdoc-params: (arg) + "Like \\[c-fill-paragraph] but handles comment separator lines." + (let* ((regexp (concat "\\|[ \t]*\\(/[*/]\\|\\*\\)[ \t]*" + (template-comment-separator-regexp '("/*" "*/")))) + (paragraph-start (concat paragraph-start regexp)) ;#dynamic + (paragraph-separate (concat paragraph-separate regexp))) + (c-fill-paragraph arg))) + +(defun template-c-init-fill-function () + "Set `fill-paragraph-function' to use `template-c-fill-paragraph'." + (when (boundp 'fill-paragraph-function) + (make-local-variable 'fill-paragraph-function) + (setq fill-paragraph-function 'template-c-fill-paragraph))) + + + +;;;;########################################################################## +;;;; Updating (File Name in Header) +;;;;########################################################################## + + +;;;=========================================================================== +;;; General updating +;;;=========================================================================== + +(defun template-update-buffer-region (limit regexp group) + "Return region = (BEG . END) in buffer to be updated. +If LIMIT is positive, check first LIMIT characters in buffer, otherwise +check last -LIMIT characters in buffer for a text to be matched by +REGEXP. Return region according to GROUP's regexp group in REGEXP." + (let ((case-fold-search nil)) + (goto-char (if limit + (if (natnump limit) (point-min) (+ (point-max) limit)) + (point-min))) + (when (re-search-forward regexp + (if (natnump limit) + (+ (point-min) limit) + (point-max)) + t) + (cons (match-beginning group) (match-end group))))) + +(defun template-match-modes-or-regexp (modes-or-regexp) + "Return non-nil, if the current buffer passes MODES-OR-REGEXP. +If MODES-OR-REGEXP is a list, it must include the current `major-mode', +if it is a regexp, it must match the `buffer-file-name' without version, +otherwise it must be non-nil." + (if (stringp modes-or-regexp) + (and buffer-file-name + (string-match modes-or-regexp + (file-name-sans-versions buffer-file-name))) + (or (nlistp modes-or-regexp) (memq major-mode modes-or-regexp)))) + +(defun template-update-buffer (&optional arg) + "Update buffer according to `template-update-buffer-alist'. +Do not do anything if `template-auto-update-disable-regexp' matches the +file name or if `template-auto-update' is nil. When optional ARG is +non-nil, i.e., if called interactively *without* prefix arg, always +update." + (interactive (list (null current-prefix-arg))) + (when (or arg + (and template-auto-update buffer-file-name + (null (and template-auto-update-disable-regexp + (string-match template-auto-update-disable-regexp + buffer-file-name))))) + (save-excursion + (save-restriction + (widen) + (let ((alist template-update-buffer-alist) + (case-fold-search (memq system-type '(vax-vms ms-dos windows-nt))) + stamp prompt region new) + (while alist + (setq stamp (pop alist)) + (condition-case nil + (and (template-match-modes-or-regexp (pop stamp)) + ;; Run TEST --------------------------------------------- + (setq prompt (pop stamp) + region (pop stamp) ; TEST + region (eval (if (or (atom region) + (functionp (car region))) + region + (cons 'template-update-buffer-region + region)))) + (if (stringp (setq new (eval (pop stamp)))) + (null (string= (buffer-substring (car region) + (cdr region)) + new)) + (car stamp)) + ;; user confirmation, replacement ----------------------- + (or (null prompt) + arg + (eq template-auto-update t) + (y-or-n-p (format prompt (buffer-name)))) + (progn + (goto-char (car region)) + (if (car stamp) + (funcall (car stamp) new (car region) (cdr region)) + (delete-region (car region) (cdr region)) + (insert new)))) + (error nil)))))))) + + +;;;=========================================================================== +;;; Update header +;;;=========================================================================== + +;;;###autoload +(defun template-update-header (&optional show) + "Replace old file name in header with current file name. +If SHOW is t, just return region of the filename or nil. Otherwise, +replace filename if possible and signal an error if SHOW is nil and +there is no filename in the header. See `template-header-lines' and +`template-header-regexp-alist'." + (interactive "*P") + (if buffer-file-name + (save-excursion + (goto-char (point-min)) + (let ((case-fold-search nil) + (comment-regexp (template-default-comment)) ; at `point-min'! + (end (progn (forward-line template-header-lines) (point))) + (alist template-header-regexp-alist) + (disallowed "") + group) + (if (string-match "[A-Za-z]\\|.." comment-regexp) + (setq comment-regexp (regexp-quote comment-regexp) + disallowed "") + (or (eq comment-regexp '(?\])) + (setq disallowed comment-regexp)) + (setq comment-regexp (concat (regexp-quote comment-regexp) "+"))) + (while alist + (goto-char (point-min)) + (if (re-search-forward (format (caar alist) + comment-regexp disallowed) + end t) + (setq group (cdar alist) + alist nil) + (setq alist (cdr alist)))) + (if (and group (match-beginning group)) + (if (eq show t) + (cons (match-beginning group) (match-end group)) + (goto-char (match-beginning group)) + (delete-region (point) (match-end group)) + (insert (file-name-sans-versions + (file-name-nondirectory buffer-file-name))) + t) + (if show nil (error "No file name in header"))))) + (if show nil (error "Buffer is not visiting a file")))) + + + +;;;;########################################################################## +;;;; Templates +;;;;########################################################################## + + +(defvar template-history nil + "History, used by `template-read'.") + +(defvar template-choice-history nil + "History, used by `template-choice'.") + +(put 'normal-mode 'template-secure-command t) + +(defvar template-all-templates nil + "Internal variable. Template files used for template derivation.") +(defvar template-file nil + "Partitioned name of new file: (DIR FILE RAW NUMBER EXT). +Internal variable. DIR is the directory part, FILE the file name +without directory part. FILE consists of its extension EXT, RAW and a +numbering NUMBER just in front of the extension. It is used by the +expansions DIR, FILE, FILE_SANS, FILE_EXT and others in +`template-expansion-alist'. Also useful for user defined functions in +`template-derivation-alist' and the per-template definition section.") + +(defvar template-modified nil + "Internal variable. Whether user is asked during the expansion process.") +(defvar template-secure t + "Internal variable. Whether all per-template definitions are secure.") +(defvar template-point-messages nil + "Internal variable. List of lines for temporary message at point.") +(defvar template-before-messages nil + "Internal variable. List of lines for temporary message before expansion.") +(defvar template-after-messages nil + "Internal variable. List of lines for temporary message after expansion.") + +(defvar template-point nil + "Internal variable. Position of point. Set with expansion form P.") +(defvar template-mark nil + "Internal variable. Position of mark. Set with expansion form M.") + +(defvar template-current nil + "Internal variable. Current key of expansion form.") +(defvar template-string-alist nil + "Internal variable. Alist of user inputs for `template-read'.") +(defvar template-register-alist nil + "Internal variable. Alist of used registers.") +(defvar template-local-alist nil + "Internal variable. Alist of per-template defined expansions.") + +(defvar template-ffap-file-finder nil + "Value used inside `template-ffap-find-file'. +If nil, initialize it to the value of `ffap-file-finder', i.e., this +variable holds the original value of that variable which will be set to +`template-ffap-find-file' in `template-initialize'.") + + +;;;=========================================================================== +;;; Functions: `find-file'/`insert-file-contents', hooking into `find-file' +;;;=========================================================================== + +(defun template-find-template (filename &optional replace) + "Switch to a buffer visiting template file FILENAME. +If optional REPLACE is non-nil, replace the current buffer contents with +the contents of file FILENAME. + +This function always considers template files as text files." + (let ((file-name-buffer-file-type-alist nil)) ; Emacs on DOS/NT + (if replace + (insert-file-contents filename nil nil nil + ;; 5th arg not t with empty accessible part + ;; (XEmacs bug workaround: would infloop) + (> (point-max) (point-min))) + (let ((template-auto-insert nil)) + (switch-to-buffer (find-file-noselect filename)))))) + +(defun template-not-found-function () + "Use a template when visiting a non-existent file. +See `template-auto-insert' and `template-find-file-commands'. Function +in `find-file-not-found-hooks'." + (and template-auto-insert (not buffer-read-only) (bobp) (eobp) + (or (memq this-command template-find-file-commands) + (and (memq this-command template-file-select-commands) + ;; thanks to Dave Love <d.love@dl.ac.uk>: + (memq (car-safe (car command-history)) + ;; To always include `find-file-at-point', use ffap + ;; initialization (see `template-ffap-find-file') + template-find-file-commands))) + (let ((template (cdr (template-derivation buffer-file-name t t)))) + (and template + (file-readable-p template) + (or (eq template-auto-insert t) + (y-or-n-p + (format "Use template %s? " + (cond-emacs-xemacs + (abbreviate-file-name template :XEMACS t))))) + (progn + (template-new-file nil template) + (setq this-command 'session-disable) + t))))) + +(defun template-ffap-find-file (filename) + "Function to use in `ffap-file-finder'. +Add an entry to `command-history' if necessary and call function in +`template-ffap-file-finder' with argument FILENAME." + (or (memq (car-safe (car command-history)) + '(ffap find-file-at-point)) + (setq command-history + (cons (list 'find-file-at-point filename) command-history))) + (if (eq template-ffap-file-finder 'template-ffap-find-file) + (find-file filename) + (funcall template-ffap-file-finder filename))) + + +;;;=========================================================================== +;;; Main function +;;;=========================================================================== + +(defun template-expand-template-interactive () + (let* ((use (template-derivation (expand-file-name + (or buffer-file-name "NONE")) + t)) + (tpl (read-file-name "Insert and expand template: " + (file-name-directory (cdr use)) + (file-name-nondirectory (cdr use)) + t + (file-name-nondirectory (cdr use))))) + (if (string= tpl "") + (error "No template file provided")) + (list (expand-file-name tpl (file-name-directory (cdr use)))))) + +;;;###autoload +(defun template-expand-template (template) + "Expand template file TEMPLATE and insert result in current buffer. +Using a template for inserting some text consists of: + 1. Template derivation: suggest a reasonable template file to the user + according to `buffer-file-name', see `template-derivation-alist'. + 2. Template insertion: insert the template file at point into the + current buffer. + 3.. as steps 6.. of `template-new-file'." + (interactive (template-expand-template-interactive)) + (save-restriction + (narrow-to-region (point) (point)) + (template-new-file nil template t))) + +(defun template-new-file-interactive () + "Interactive specification for `template-new-file'. +Return \(FILE TEMPLATE)." + (let* ((inp (read-file-name (if current-prefix-arg + "New file (+template, no name change): " + "New file (+template): ") + nil "")) + (use (cond ((equal inp "") + (error "Empty/no input")) + ((file-directory-p inp) + (error "%S is a directory" inp)) + (t (template-derivation (expand-file-name inp) + current-prefix-arg)))) + (tpl (read-file-name (format "File %s uses template: " + (file-name-nondirectory (car use))) + (file-name-directory (cdr use)) + (file-name-nondirectory (cdr use)) + t + (file-name-nondirectory (cdr use))))) + (list (car use) + (if (string= tpl "") + nil + (expand-file-name tpl (file-name-directory (cdr use))))))) + +;;;###autoload +(defun template-new-file (file template &optional with-undo) + "Open a new file FILE by using a TEMPLATE. +Using a template for creating a new file consists of, steps 1 to 3 are +only executed when called interactively: + 1. Prompt for the name of the new file. + 2. Template derivation: suggest a reasonable template file to the user + see `template-derivation-alist'. + 3. File name refinement: e.g., if the given file name is \"exercise\" + and there are two files \"exercise1.tex\" and \"exercise2.tex\" in + the same directory and if we have a template \"exercise.tex.tpl\", + the file name is refined to \"exercise3.tex\". This is turned off + when \\[template-new-file] is called with a prefix argument. + 4. Template insertion: insert the template file into the empty buffer. + 5. Read per-template expansion definition section starting at + `template-definition-start' and delete it. + 6. Display :before message in `template-message-buffer'. + 7. Execute pre-expansion commands defined in the definition section. + 8. Set local variables defined in the definition section. + 9. Expansion: expand the expansion forms (text matched by + `template-expansion-regexp') They are defined in the definition + section, in `template-expansion-alist', or provided by default, see + `template-expansion-regexp' and `template-register-regexp'. + 10. Execute post-expansion commands defined in the definition section. + 11. Run `normal-mode' and functions in `find-file-hooks'. + 12. Update header according to `template-update-header' with argument + `if-exists'. + 13. Display :after message in `template-message-buffer'. + 14. Report: display a temporary message at point defined in the + definition section and an automatically generated message in the + minibuffer area, see `template-message-timeout'. + +If optional WITH-UNDO is non-nil, store corresponding changes in +`buffer-undo-list'. If FILE is nil, the buffer for FILE has already +been created and the accessible part will be replaced by the expanded +template. If TEMPLATE is nil (empty input when called interactively), +do not use a template." + (interactive (template-new-file-interactive)) + ;; check template and file name -------------------------------------------- + (if template + (if (file-readable-p template) + (if (file-directory-p template) + (error "Template %s is a directory" template)) + (if (null (yes-or-no-p (format "Template %s does not exist. Create? " + template))) + (error "No template file to use") + (template-make-directory (file-name-directory template)) + (template-find-template template) + (error "You should create this template first")))) + (if (not file) + (switch-to-buffer (current-buffer)) + (and (or (get-file-buffer file) (file-exists-p file)) + (null (yes-or-no-p (format "File %s exists. Delete contents? " file))) + (error "Cannot use templates for existing files")) + (let ((auto-mode-alist nil) + (enable-local-variables nil) + (find-file-not-found-hooks nil) + (enable-local-eval nil)) + (switch-to-buffer (find-file-noselect file)))) + (when template + (or with-undo (setq buffer-undo-list t)) + (template-find-template template t) + (template-new-file-0 with-undo))) + +(defun template-new-file-0 (with-undo) + "Perform template replacements in current buffer. +If WITH-UNDO is non-nil, store corresponding changes in +`buffer-undo-list'." + ;; start replacement ------------------------------------------------------- + (or with-undo (set-buffer-modified-p nil)) + (goto-char (point-min)) + (setq template-secure t + template-point nil + template-mark nil + template-modified nil + template-point-messages nil + template-before-messages nil + template-after-messages nil + template-local-alist nil + template-register-alist nil + template-string-alist nil) + (let ((form-selector nil) + (pre-command-list nil) + (post-command-list nil) + (local-variable-list nil) + val) + ;; read per-template definition section ---------------------------------- + (goto-char (point-min)) + (when (re-search-forward + (concat "^[ \t]*" template-definition-start "[ \t]*$") nil t) + (condition-case () + (while t + (setq val (read (current-buffer))) + (cond (;; ("KEY" . xxx): ask user -------------------------------- + (and (consp val) (stringp (car val))) + (let* ((def (cdr val)) ; expansion forms + (msg (cond ((null template-message-prompt-format) + nil) + ((stringp def) def) + ((and (consp def) (stringp (car def))) + (car def))))) + (when msg ; list prompts in before-messages + (or template-before-messages + (null template-message-prompt-intro) + (push template-message-prompt-intro + template-before-messages)) + (push (format template-message-prompt-format msg) + template-before-messages)) + (push (cons (car val) (template-translate-definition def)) + template-local-alist))) + ;; :before, :after, :eval-before, :eval-after -------------- + ((null val) ; nil is deprecated + (setq form-selector + (cond ((null form-selector) :old-before) + ((eq form-selector :old-before) :old-after) + ((eq form-selector :old-after) + (error "More than two (obsolete) nil forms")) + (t + (error "Used obsolete nil form with new form selectors"))))) + ((memq val '(:before :after :eval-before :eval-after)) + (setq form-selector val)) + ;; "MESSAGE" ----------------------------------------------- + ((stringp val) + (cond ((eq form-selector :before) + (push val template-before-messages)) + ((eq form-selector :after) + (push val template-after-messages)) + (t + (push val template-point-messages)))) + ;; (CHAR . xxx): set register ------------------------------ + ((and (consp val) (template-char-or-char-int-p (car val))) + (let ((reg (template-char-or-int-to-char (car val)))) + (if (atom (cdr val)) + (set-register reg (cdr val)) + (set-register reg (cadr val)) + (when template-message-register-format + (let ((msg (format template-message-register-format + reg (cadr val) + (or (caddr val) "")))) + (if template-message-buffer + (progn + (if template-before-messages + (push msg template-before-messages)) + (or template-after-messages + (null template-message-register-intro) + (push template-message-register-intro + template-after-messages)) + (push msg template-after-messages)) + (push msg template-point-messages))))))) + ;; set var, execute command and sexpr ---------------------- + ((and (memq form-selector '(nil :before :after)) + (consp val) + (symbolp (car val))) + (or (and (functionp (get (car val) 'template-secure-value)) + (funcall (get (car val) 'template-secure-value) + (cdr val))) + (setq template-secure nil)) + (push val local-variable-list)) + ((memq form-selector '(:eval-before :old-before)) + (push (template-elisp-in-definition val) + pre-command-list)) + ((memq form-selector '(:eval-after :old-after)) + (push (template-elisp-in-definition val) + post-command-list)) + (t + (error "Illegal form")))) + (error nil)) + (skip-chars-forward " \t\n\f") + (or (eobp) + (error "Invalid definition in line %d (pos %d) of the template file" + (count-lines 1 (point)) (point))) + (or template-secure + (null (default-value template-confirm-insecure)) + (y-or-n-p "Have you checked the template functions? ") + (error "Failed security check")) + (delete-region (match-beginning 0) (point-max))) + ;; expand ---------------------------------------------------------------- + (template-display-messages template-before-messages) + (eval (cons 'progn (nreverse pre-command-list))) + (while local-variable-list + (make-local-variable (caar local-variable-list)) + (set (caar local-variable-list) (cdar local-variable-list)) + (setq local-variable-list (cdr local-variable-list))) + (goto-char (point-min)) + (while (re-search-forward template-expansion-regexp nil t) + (setq template-current (buffer-substring (match-beginning 1) + (match-end 1)) + val (assoc template-current template-local-alist)) + (unless val + (if (setq val (assoc template-current template-key-alias-alist)) + (setq template-current (cdr val))) + (setq val (or (assoc template-current template-expansion-alist) + (assoc template-current + template-default-expansion-alist)))) + (delete-region (match-beginning 0) (match-end 0)) + (cond (val + (eval (cons 'progn (cdr val)))) + ((string-match template-register-regexp template-current) + (template-register)) + (t + (template-read (format "Replacement for `%s': " + template-current))))) + (eval (cons 'progn (nreverse post-command-list))) + (save-restriction + (widen) + (normal-mode t) + (or with-undo (template-update-header 'if-exists)) + (run-hooks 'find-file-hooks)) + ;; message --------------------------------------------------------------- + (template-display-messages template-after-messages) + (cond ((null template-register-alist) + (message "%s, no buffer location in register" + (if template-mark "Mark set" "No mark"))) + (t (message "%s, buffer location in register: %s" + (if template-mark "Mark set" "No mark") + (mapconcat (function + (lambda (x) + (if (cdr x) + (concat (char-to-string (car x)) "*") + (char-to-string (car x))))) + (nreverse template-register-alist) + ", ")))) + (or with-undo (set-buffer-modified-p template-modified)) + (goto-char (point-min)) + (when template-point + (goto-char template-point) + (set-marker template-point nil)) + (when template-mark + (push-mark template-mark) + (set-marker template-mark nil) + (if (fboundp 'zmacs-activate-region) (zmacs-activate-region))) + (when (and template-point-messages + (or (cdr template-point-messages) + (not (string-equal (car template-point-messages) "")))) + (let ((beg (point)) + end) + (if (cdr template-point-messages) + (insert (mapconcat 'identity + (nreverse template-point-messages) + "\n") + "\n") + (insert (car template-point-messages))) + (setq end (point)) + (goto-char beg) + (and (fboundp 'make-extent) (fboundp 'set-extent-face) + (set-extent-face (make-extent beg end) 'template-message-face)) + (recenter) + (sit-for template-message-timeout) + (delete-region beg end)))) + (recenter) + (unless with-undo + (setq buffer-undo-list nil) + (set-buffer-modified-p template-modified))) + +(defun template-display-messages (messages) + (when (and messages template-message-buffer) + (setq messages (nreverse messages)) + (with-output-to-temp-buffer template-message-buffer + (while messages + (princ (pop messages)) + (if messages (princ "\n")))))) + + +;;;=========================================================================== +;;; Determine name of the new file and the template +;;;=========================================================================== + +(defun template-derivation (full arg &optional no-default) + "Derive template file name and do file name refinement. +Return (REFINED . TEMPLATE) where REFINED is the refined version of FULL +and TEMPLATE and template file name, see `template-derivation-alist'. +FULL is the initial file name given by the user. File name refinement +is turned off when ARG is non-nil. If optional argument NO-DEFAULT is +non-nil, return nil instead (FULL \. \"~/.templates/DEFAULT.tpl\") if no +matching entry can be found in `template-derivation-alist'." + ;; Get all templates ------------------------------------------------------- + (setq template-all-templates nil) + (let* ((dir (file-name-directory full)) + (len (length dir)) + (case-fold-search (memq system-type '(vax-vms ms-dos windows-nt)))) + (while (and dir + (not (and template-stop-derivation + (fboundp template-stop-derivation) + (funcall template-stop-derivation dir)))) + (template-all-templates template-subdirectories dir) + (setq dir (file-name-directory (directory-file-name dir))) + (or (> len (setq len (length dir))) + (setq dir nil))) + (template-all-templates template-default-directories) + (setq template-all-templates (nreverse template-all-templates))) + ;; Get template file ------------------------------------------------------- + (if (string= (file-name-nondirectory full) "") + (error "You cannot use templates for directories")) + (setq template-file (template-split-filename full)) + (let ((tests template-derivation-alist) + test template file) + (while tests + (setq test (caar tests) + file (cdar tests)) + (if (setq template + (if (functionp (car test)) + (apply (car test) (cdr test)) + (apply 'template-default-template test))) + (setq tests nil) + (setq tests (cdr tests)))) + (if template + (or arg + (if (functionp (car file)) + (apply (car file) template (cdr file)) + (apply 'template-unique-file template file))) + (or no-default + (setq template (template-split-filename + "DEFAULT" + (template-default-directory))))) + (if template + (cons (expand-file-name (cadr template-file) (car template-file)) + (expand-file-name (concat (cadr template) template-extension) + (car template)))))) + +(defun template-default-directory () + "Return directory of file \"DEFAULT.tpl\"." + (let ((dirs template-default-directories) + (name (concat "DEFAULT" template-extension)) + dir) + (while dirs + (setq dir (pop dirs)) + (if (file-readable-p (expand-file-name name dir)) + (setq dirs nil) + (setq dir nil))) + (or dir + (car template-default-directories) + (expand-file-name "~/.templates/")))) + + +;;;=========================================================================== +;;; Small functions +;;;=========================================================================== + +(defun template-make-directory (dir) + "Create DIR if it does not exists yet." + (cond ((file-exists-p dir)) + ((yes-or-no-p (format "The directory %s does not exist. Create? " dir)) + (make-directory dir t)) + (t (error "You should create a directory \"%s\"" dir))) + dir) + +(defun template-split-filename (file &optional dir) + "Split file name into its parts. +If DIR is nil, FILE is a fully expanded file name, otherwise FILE is a +file name without its directory part DIR. See `template-file'." + (or dir (setq dir (template-make-directory (file-name-directory file)) + file (file-name-nondirectory file))) + (let* ((ext (string-match "\\.[^.]*\\'" file)) + (raw (substring file 0 ext)) + (num (string-match "[^0-9][0-9]+\\'" raw))) + (if num + (list dir file + (substring raw 0 (1+ num)) + (substring raw (1+ num)) + (if ext (substring file ext) "")) + (list dir file raw "" (if ext (substring file ext) ""))))) + +(defun template-translate-definition (def) + "Translate DEF of expansion and set `template-secure' accordingly." + (cond ((null def) ; zero form + nil) + ((template-char-or-char-int-p def) + `((template-register ,def))) + ((stringp def) + `((template-read ,def nil nil nil t))) + ((symbolp def) + `((insert (if (stringp ,def) ,def template-string-default)))) + ((and (consp def) (stringp (car def))) + (if (consp (car-safe (cdr def))) + `((template-choice ,(car def) (quote ,(cdr def)))) + `((apply (quote template-read) (quote ,def))))) + ((consp (car-safe def)) + (setq template-secure nil) + def) + (t + (list (template-elisp-in-definition (car def) (cdr def)))))) + +(defun template-elisp-in-definition (def &optional prefix) + "Return valid elisp definition and set `template-secure' accordingly. +DEF is the elisp form, PREFIX would be the prefix argument if DEF is a +command." + (cond ((consp def) + (setq template-secure nil) + def) + ((or (symbolp def) (vectorp def)) + (or (and (symbolp def) (get def 'template-secure-command)) + (setq template-secure nil)) + (if (and (symbolp def) + (functionp (get def 'template-secure-command)) + (listp prefix) + (funcall (get def 'template-secure-command) prefix)) + `(apply (quote ,def) (quote ,prefix)) + `(progn (setq prefix-arg (quote ,prefix)) + (command-execute (quote ,def))))) + (t + (error "Illegal form")))) + + +;;;=========================================================================== +;;; Compute template name +;;;=========================================================================== + +(defun template-all-templates (dirs &optional base) + "Read names of template files in DIRS relatively to BASE. +Insert the names to internal variable `template-all-templates'." + (let ((regexp (concat (regexp-quote template-extension) "\\'")) + (endpos (- (length template-extension))) + dir templates) + (while dirs + (setq dir (expand-file-name (car dirs) base) + dirs (cdr dirs)) + (cond-emacs-xemacs + (and (file-accessible-directory-p dir) + (file-readable-p dir) + (setq templates (directory-files dir t regexp :XEMACS nil t)) + (while templates + (and :EMACS + (not (file-directory-p (car templates))) + :BOTH + (file-readable-p (car templates)) + (push (template-split-filename (substring (car templates) + 0 + endpos)) + template-all-templates)) + (setq templates (cdr templates)))))))) + +(defun template-set-template-part (part file-part) + "Set template part according to definition PART and FILE-PART. +See `template-derivation-alist' for details." + (when part + (cond ((stringp part) part) + ((eq part t) file-part) + ((null (string= file-part "")) file-part)))) + +(defun template-default-template (&optional raw num ext regexp) + "Return template according to RAW, NUM, EXT and REGEXP. +See `template-derivation-alist' for details." + (if (or (null regexp) (string-match regexp (cadr template-file))) + (let ((templates template-all-templates) + (file-rne (cddr template-file)) + result template-rne) + (setq raw (template-set-template-part raw (car file-rne)) + num (template-set-template-part num (cadr file-rne)) + ext (template-set-template-part ext (caddr file-rne))) + (while templates + (setq template-rne (cddar templates)) + (if (and (or (null raw) (string= (car template-rne) raw)) + (or (null num) (string= (cadr template-rne) num)) + (or (null ext) (string= (caddr template-rne) ext))) + (setq result (car templates) + templates nil) + (setq templates (cdr templates)))) + result))) + + +;;;=========================================================================== +;;; File name refinement +;;;=========================================================================== + +(defun template-default-file (template &optional raw num ext) + "Refine file name according to TEMPLATE, RAW, NUM and EXT. +The result is in `template-file'. See `template-derivation-alist'." + (let ((template-rne (cddr template)) + (file-rne (cddr template-file))) + (if raw + (if (eq raw t) (setq raw (car template-rne))) + (setq raw (car file-rne))) + (if num + (if (eq num t) (setq num (cadr template-rne))) + (setq num (cadr file-rne))) + (if ext + (if (eq ext t) (setq ext (caddr template-rne))) + (setq ext (caddr file-rne))) + (setcdr template-file (list (concat raw num ext) raw num ext)))) + +(defunx template-unique-file (template &optional raw num ext auto-num) + "Refine file name according to TEMPLATE, RAW, NUM, EXT and AUTO-NUM. +Use auto numbering if NUM is not \"\" or AUTO-NUM is non-nil. The +result is in `template-file'. See `template-derivation-alist'." + (template-default-file template raw num ext) + (let* ((dir (car template-file)) + (full (expand-file-name (cadr template-file) dir))) + (when (if (string= (fourth template-file) "") + auto-num + (setq auto-num + (and (or (get-file-buffer full) + (file-readable-p full)) + (string-to-int (fourth template-file))))) + (setq auto-num (1- auto-num) + raw (third template-file) + ext (fifth template-file)) + (let ((list (buffer-list)) + file1 dir1) + (while list + (and (setq file1 (buffer-file-name (car list))) + (setq dir1 (file-name-directory file1)) + (string= dir1 dir) + (setq auto-num + (max (template-filename-number + (cddr (template-split-filename + (file-name-nondirectory file1) + dir1)) + raw ext) + auto-num))) + (setq list (cdr list))) + (setq list (directory-files dir nil nil t :XEMACS t)) + (while list + (unless (:EMACS file-directory-p (car list)) + (setq auto-num + (max (template-filename-number + (cddr (template-split-filename (car list) dir)) + raw ext) + auto-num) + list (cdr list)))) + (template-default-file template raw + (int-to-string (1+ auto-num)) + ext))))) + +(defun template-filename-number (file-rne raw ext) + "Return numbering in FILE-RNE if the RAW and EXT parts are equal." + (or (and (string= (car file-rne) raw) + (string= (caddr file-rne) ext) + (string-to-int (cadr file-rne))) + 0)) + + +;;;=========================================================================== +;;; Safe commands for per-template expansions +;;;=========================================================================== + +(defun template-insert-time (&optional format default) + "Insert time into current buffer using time format FORMAT. +If FORMAT is not a string, it uses DEFAULT or `current-time-string'." + (interactive) + (insert (if (and (stringp format) (fboundp 'format-time-string)) + (format-time-string format (current-time)) + (or default (current-time-string))))) +(put 'template-insert-time 'template-secure-command + (lambda (args) + (or (null args) (and (stringp (car args)) (null (cdr args)))))) + + +;;;=========================================================================== +;;; Functions for the predefined expansions +;;;=========================================================================== + +(defun template-register (&optional register) + "Set current location in register REGISTER. +That is, \\[jump-to-register] REGISTER jumps to the current position. +If REGISTER is nil, use register corresponding to the last character in +`template-current'." + (let* ((char (if register + (template-char-or-int-to-char register) + (aref template-current (1- (length template-current))))) + (elem (assoc char template-register-alist))) + (point-to-register char) + (if elem + (setcdr elem t) + (push (list char) template-register-alist)))) + +(defun template-read (prompt &optional prefix suffix default again-p) + "Ask user with PROMPT for a STRING to be inserted. +If STRING is not \"\", insert PREFIX STRING SUFFIX, otherwise DEFAULT. +If AGAIN-P is nil, do not ask if `template-current' appears another time +as key in a expansion form. If AGAIN-P is `expand', the inserted region +is searched for expansion forms where STRING is marked as a literal +environment, see `template-literal-environment'." + (setq template-modified t) + (let ((pos (point)) + (elem (and (null again-p) + (assoc template-current template-string-alist)))) + (if elem + (setq elem (cdr elem)) + (setq elem (read-from-minibuffer prompt nil nil nil + 'template-history) + elem (cond ((string= elem "") (or default "")) + ((eq again-p 'expand) + (concat prefix + (format template-expansion-format + (car template-literal-environment)) + elem + (format template-expansion-format + (cdr template-literal-environment)) + suffix + (format template-expansion-format + (car template-literal-environment)) + (format template-expansion-format + (cdr template-literal-environment)))) + (t + (concat prefix elem suffix)))) + (or again-p (push (cons template-current elem) template-string-alist))) + (insert elem) + (if (eq again-p 'expand) (goto-char pos)))) + +(defun template-choice (prompt table) + "Ask user with PROMPT for a choice and insert it. +Each element in TABLE looks like (ANSWER . TEXT). Ask for an input with +completion over all ANSWERs and insert corresponding TEXT if ANSWER is a +string, otherwise ask a \"y or n\" question and use the result of +`y-or-n-p' as ANSWER. Expansion forms in TEXT will be expanded." + (setq template-modified t) + (let ((pos (point))) + (insert (or (cdr (assoc (if (stringp (caar table)) + (completing-read prompt table nil t nil + 'template-choice-history) + (y-or-n-p prompt)) + table)) + "") + (format template-expansion-format + (car template-literal-environment)) + (format template-expansion-format + (cdr template-literal-environment))) + (goto-char pos))) + + +;;;=========================================================================== +;;; Menu filter +;;;=========================================================================== + +(defun template-menu-filter (menu-items) + ;; checkdoc-params: (menu-items) + "Menu filter for `template-creation-menu'." + (let ((alist (append template-expansion-alist + template-default-expansion-alist)) + menu used key) + (while alist + (unless (member (setq key (car (pop alist))) used) + (push key used) + (push (vector (concat "Insert " key) + (list 'template-insert-form current-prefix-arg key) + t) + menu))) + (append menu-items (nreverse menu)))) + + +;;;=========================================================================== +;;; Insert and define forms +;;;=========================================================================== + +(defun template-buffer-template-p () + "Return non-nil, if current buffer is likely to be a template file." + (and buffer-file-name + (string-match (concat (regexp-quote template-extension) "\\'") + (file-name-sans-versions buffer-file-name)))) + +(defun template-open-template () + "If current buffer is no template file, open a new one." + (interactive) + (if (template-buffer-template-p) + (barf-if-buffer-read-only) + (let (name + (dir (and (car template-subdirectories) + (expand-file-name (car template-subdirectories))))) + (if (null buffer-file-name) + (setq name (concat "TEMPLATE" template-extension)) + (setq name (file-name-sans-versions + (file-name-nondirectory buffer-file-name))) + (if (string-match ".\\.[^.]*\\'" name) + (setq name (concat "TEMPLATE" + (substring name (1+ (match-beginning 0))) + template-extension)) + (setq name (concat name template-extension) + ;; dot file => template not specific for directory + dir (car template-default-directories)))) + (setq name (read-file-name "Open template file (empty=none): " + dir nil nil name)) + (or (string= name "") + (template-find-template name))))) + +(defun template-insert-form (arg key) + "Insert an expansion form according to KEY into template. +When called interactively, allow completion over all keys in +`template-expansion-alist' and `template-default-expansion-alist'. +If prefix ARG is nil, run `template-open-template' first." + (interactive + (list current-prefix-arg + (completing-read "Insert key (0-9 for register position): " + (append template-expansion-alist + template-default-expansion-alist)))) + (or arg (template-open-template)) + (insert (format template-expansion-format key)) + (if (equal key (car template-literal-environment)) + (let ((pos (point))) + (insert (format template-expansion-format + (cdr template-literal-environment))) + (goto-char pos)))) + +(defun template-define-start (arg &rest args) + "Insert a definition section and definition into template. +See `template-definition-start'. If ARGS is non-nil, pass ARGS to +`format' for a new definition. If prefix ARG is nil, run +`template-open-template' first." + (interactive "P") + (or arg (template-open-template)) + (save-excursion + (goto-char (point-min)) + (unless (re-search-forward (concat "^[ \t]*" + template-definition-start + "[ \t]*$") nil t) + (goto-char (point-max)) + (or (bolp) (insert "\n")) + (insert template-definition-start)) + (goto-char (point-max)) + (or (bolp) (insert "\n")) + (if args (insert (apply 'format args) "\n"))) + (message "Put definition at the end of the template")) + +(defun template-define-message (arg message) + "Insert a temporary message MESSAGE definition into template. +For ARG, see `template-define-start'." + (interactive "P\nsTemporary message: ") + (template-define-start arg "%S" message)) + +(defun template-define-prompt (arg key prompt &optional prefix suffix default) + "Insert a definition for KEY as PROMPT into template. +For ARG, see `template-define-start'." + (interactive "P\nsExpansion key: \nsExpansion prompt: \nsPrefix for non-empty input: \nsSuffix for non-empty input: \nsDefault for empty input: ") + (template-define-start arg "(%S %S %S %S %S)" + key prompt prefix suffix default)) + +(defun template-define-register (arg register) + "Insert a setting of REGISTER into template. +For ARG, see `template-define-start'." + (interactive "P\ncDefine register: ") + (let* ((old (get-register register)) + (contents (read-from-minibuffer "Register contents: " + (and (stringp old) + (not (string-match "\n" old)) + old))) + (comment (read-from-minibuffer "Comment (empty=none): "))) + (if (string= comment "") + (template-define-start arg "(%S %S)" register contents) + (template-define-start arg "(%S %S %S)" register contents comment)))) + + +;;;=========================================================================== +;;; Initialization +;;;=========================================================================== + +;; easymenu.el is for top-level menus only... +(defunx template-add-submenu (menu &optional where) + "Add the submenu MENU to the end of a menu in WHERE in the menubar. +WHERE is a list of menus tried to add MENU to. If no such menu exist, +no menu is added. When using Emacs, always add to the \"Edit\" menu. +See `easy-menu-define' for the format of MENU." + (and menu + :EMACS + (>= emacs-major-version 21) + (boundp 'menu-bar-edit-menu) + (let ((keymap (easy-menu-create-menu (car menu) (cdr menu)))) + ;; `easy-menu-get-map' doesn't get the right one => use hard-coded + (define-key-after menu-bar-edit-menu (vector (intern (car menu))) + (cons 'menu-item + (cons (car menu) + (if (not (symbolp keymap)) + (list keymap) + (cons (symbol-function keymap) + (get keymap 'menu-prop))))))) + :XEMACS + (featurep 'menubar) + (let ((current-menubar default-menubar) path) + (while where + (setq path (list (pop where))) + (if (find-menu-item default-menubar path) + (setq where nil) + (setq path nil))) + (when path (add-submenu path menu))))) + +;;;###autoload +(defunx template-initialize (&rest dummies) + ;; checkdoc-params: (dummies) + "Initialized package template. See variable `template-initialize'." + (interactive) + (setq template-use-package t) + (let ((regexp (concat (regexp-quote template-extension) "\\'"))) + (or (assoc regexp auto-mode-alist) + (push (list regexp nil 'template-new-file) auto-mode-alist))) + (when (or (eq template-initialize t) + (memq 'cc-mode template-initialize)) + (add-hook 'c-mode-common-hook 'template-c-init-fill-function) + (add-hook 'antlr-mode-hook 'template-c-init-fill-function)) + (when (or (eq template-initialize t) + (memq 'de-html-helper template-initialize)) + (setq html-helper-build-new-buffer nil) + (setq html-helper-do-write-file-hooks nil)) + (when (or (eq template-initialize t) + (memq 'keys template-initialize)) + (condition-case nil ; older Emacses don't understand all + (progn + (define-key ctl-x-map "t" 'template-new-file) + (define-key ctl-x-map [(control =)] 'template-single-comment) + (define-key ctl-x-map [(control ?\;)] 'template-block-comment)) + (error nil))) + (when (or (eq template-initialize t) + (memq 'menus template-initialize)) + (template-add-submenu template-comment-menu :XEMACS '("Edit")) + (template-add-submenu template-creation-menu :XEMACS '("Cmds" "Edit")) + :EMACS + (and (boundp 'menu-bar-files-menu) + (define-key-after menu-bar-files-menu [template-new-file] + '(menu-item "New File Using Template..." template-new-file + :enable (not (window-minibuffer-p + (frame-selected-window + menu-updating-frame))) + :help "Create a new file, using a template") + 'dired)) + :XEMACS + (and (featurep 'menubar) + (find-menu-item default-menubar '("File")) + (let ((current-menubar default-menubar)) + ;; XEmacs-20.4 `add-submenu' does not have 4th arg IN-MENU + (add-menu-button '("File") + ["New File Using Template..." template-new-file + :active t] + "Insert File...")))) + (if (and (boundp 'init-file-loaded) init-file-loaded) + ;; doesn't exist in Emacs + (template-after-init) + (add-hook 'after-init-hook 'template-after-init t))) + +(defun template-after-init () + "Late initialization for package template. +See function and variable `template-initialize'." + (when (or (eq template-initialize t) + (memq 'auto template-initialize)) + (add-hook 'write-file-hooks 'template-update-buffer) + (add-hook 'find-file-not-found-hooks 'template-not-found-function t)) + (when (or (eq template-initialize t) + (memq 'ffap template-initialize)) + (or template-ffap-file-finder + (setq template-ffap-file-finder + (if (boundp 'ffap-file-finder) + ffap-file-finder + (or (get 'ffap-file-finder 'saved-value) 'find-file)))) + (setq ffap-file-finder 'template-ffap-find-file))) + +;;; Local IspellPersDict: .ispell_template +;;; template.el ends here |