Mercurial > hg > xemacs-beta
diff lisp/utils/skeleton.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/utils/skeleton.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,518 @@ +;;; skeleton.el --- Lisp language extension for writing statement skeletons +;; Copyright (C) 1993, 1994, 1995 by Free Software Foundation, Inc. + +;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389 +;; Maintainer: FSF +;; Keywords: extensions, abbrev, languages, tools + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Synched up with: FSF 19.30. + +;;; Commentary: + +;; A very concise language extension for writing structured statement +;; skeleton insertion commands for programming language modes. This +;; originated in shell-script mode and was applied to ada-mode's +;; commands which shrunk to one third. And these commands are now +;; user configurable. + +;;; Code: + +;; page 1: statement skeleton language definition & interpreter +;; page 2: paired insertion +;; page 3: mirror-mode, an example for setting up paired insertion + + +(defvar skeleton-transformation nil + "*If non-nil, function applied to literal strings before they are inserted. +It should take strings and characters and return them transformed, or nil +which means no transformation. +Typical examples might be `upcase' or `capitalize'.") + +; this should be a fourth argument to defvar +(put 'skeleton-transformation 'variable-interactive + "aTransformation function: ") + + + +(defvar skeleton-end-hook + (lambda () + (or (eolp) (newline-and-indent))) + "Hook called at end of skeleton but before going to point of interest. +By default this moves out anything following to next line. +The variables `v1' and `v2' are still set when calling this.") + + +;;;###autoload +(defvar skeleton-filter 'identity + "Function for transforming a skeleton-proxy's aliases' variable value.") + +(defvar skeleton-untabify t + "When non-`nil' untabifies when deleting backwards with element -ARG.") + +(defvar skeleton-newline-indent-rigidly nil + "When non-`nil', indent rigidly under current line for element `\\n'. +Else use mode's `indent-line-function'.") + +(defvar skeleton-further-elements () + "A buffer-local varlist (see `let') of mode specific skeleton elements. +These variables are bound while interpreting a skeleton. Their value may +in turn be any valid skeleton element if they are themselves to be used as +skeleton elements.") +(make-variable-buffer-local 'skeleton-further-elements) + + +(defvar skeleton-subprompt + (substitute-command-keys + "RET, \\<minibuffer-local-map>\\[abort-recursive-edit] or \\[help-command]") + "*Replacement for %s in prompts of recursive subskeletons.") + + +(defvar skeleton-abbrev-cleanup nil) + + +(eval-and-compile + (defvar skeleton-debug nil + "*If non-nil `define-skeleton' will override previous definition.")) + +;; reduce the number of compiler warnings +(defvar skeleton) +(defvar skeleton-modified) +(defvar skeleton-point) +(defvar skeleton-regions) + +;;;###autoload +(defmacro define-skeleton (command documentation &rest skeleton) + "Define a user-configurable COMMAND that enters a statement skeleton. +DOCUMENTATION is that of the command, while the variable of the same name, +which contains the skeleton, has a documentation to that effect. +INTERACTOR and ELEMENT ... are as defined under `skeleton-insert'." + (if skeleton-debug + (set command skeleton)) + `(progn + (defvar ,command ',skeleton ,documentation) + (defalias ',command 'skeleton-proxy))) + + + +;; This command isn't meant to be called, only it's aliases with meaningful +;; names are. +;;;###autoload +(defun skeleton-proxy (&optional str arg) + "Insert skeleton defined by variable of same name (see `skeleton-insert'). +Prefix ARG allows wrapping around words or regions (see `skeleton-insert'). +This command can also be an abbrev expansion (3rd and 4th columns in +\\[edit-abbrevs] buffer: \"\" command-name). + +When called as a function, optional first argument STR may also be a string +which will be the value of `str' whereas the skeleton's interactor is then +ignored." + (interactive "*P\nP") + (let ((function (nth 1 (backtrace-frame 1)))) + (if (eq function 'nth) ; uncompiled lisp function + (setq function (nth 1 (backtrace-frame 5))) + (if (eq function 'byte-code) ; tracing byte-compiled function + (setq function (nth 1 (backtrace-frame 2))))) + (if (not (setq function (funcall skeleton-filter (symbol-value function)))) + (if (memq this-command '(self-insert-command + skeleton-pair-insert-maybe + expand-abbrev)) + (setq buffer-undo-list (primitive-undo 1 buffer-undo-list))) + (skeleton-insert function + (if (setq skeleton-abbrev-cleanup + (or (eq this-command 'self-insert-command) + (eq this-command + 'skeleton-pair-insert-maybe))) + () + ;; Pretend C-x a e passed its prefix arg to us + (if (or arg current-prefix-arg) + (prefix-numeric-value (or arg + current-prefix-arg)))) + (if (stringp str) + str)) + (if skeleton-abbrev-cleanup + (setq deferred-action-list t + deferred-action-function 'skeleton-abbrev-cleanup + skeleton-abbrev-cleanup (point)))))) + + +(defun skeleton-abbrev-cleanup (&rest list) + "Value for `post-command-hook' to remove char that expanded abbrev." + (if (integerp skeleton-abbrev-cleanup) + (progn + (delete-region skeleton-abbrev-cleanup (point)) + (setq deferred-action-list () + deferred-action-function nil + skeleton-abbrev-cleanup nil)))) + + +;;;###autoload +(defun skeleton-insert (skeleton &optional skeleton-regions str) + "Insert the complex statement skeleton SKELETON describes very concisely. + +With optional third REGIONS wrap first interesting point (`_') in skeleton +around next REGIONS words, if REGIONS is positive. If REGIONS is negative, +wrap REGIONS preceding interregions into first REGIONS interesting positions +\(successive `_'s) in skeleton. An interregion is the stretch of text between +two contiguous marked points. If you marked A B C [] (where [] is the cursor) +in alphabetical order, the 3 interregions are simply the last 3 regions. But +if you marked B A [] C, the interregions are B-A, A-[], []-C. + +Optional fourth STR is the value for the variable `str' within the skeleton. +When this is non-`nil' the interactor gets ignored, and this should be a valid +skeleton element. + +SKELETON is made up as (INTERACTOR ELEMENT ...). INTERACTOR may be nil if +not needed, a prompt-string or an expression for complex read functions. + +If ELEMENT is a string or a character it gets inserted (see also +`skeleton-transformation'). Other possibilities are: + + \\n go to next line and indent according to mode + _ interesting point, interregion here, point after termination + > indent line (or interregion if > _) according to major mode + & do next ELEMENT if previous moved point + | do next ELEMENT if previous didn't move point + -num delete num preceding characters (see `skeleton-untabify') + resume: skipped, continue here if quit is signaled + nil skipped + +Further elements can be defined via `skeleton-further-elements'. ELEMENT may +itself be a SKELETON with an INTERACTOR. The user is prompted repeatedly for +different inputs. The SKELETON is processed as often as the user enters a +non-empty string. \\[keyboard-quit] terminates skeleton insertion, but +continues after `resume:' and positions at `_' if any. If INTERACTOR in such +a subskeleton is a prompt-string which contains a \".. %s ..\" it is +formatted with `skeleton-subprompt'. Such an INTERACTOR may also a list of +strings with the subskeleton being repeated once for each string. + +Quoted lisp-expressions are evaluated evaluated for their side-effect. +Other lisp-expressions are evaluated and the value treated as above. +Note that expressions may not return `t' since this impplies an +endless loop. Modes can define other symbols by locally setting them +to any valid skeleton element. The following local variables are +available: + + str first time: read a string according to INTERACTOR + then: insert previously read string once more + help help-form during interaction with the user or `nil' + input initial input (string or cons with index) while reading str + v1, v2 local variables for memorising anything you want + +When done with skeleton, but before going back to `_'-point call +`skeleton-end-hook' if that is non-`nil'." + (and skeleton-regions + (setq skeleton-regions + (if (> skeleton-regions 0) + (list (point-marker) + (save-excursion (forward-word skeleton-regions) + (point-marker))) + (setq skeleton-regions (- skeleton-regions)) + ;; copy skeleton-regions - 1 elements from `mark-ring' + (let ((l1 (cons (mark-marker) mark-ring)) + (l2 (list (point-marker)))) + (while (and l1 (> skeleton-regions 0)) + (setq l2 (cons (car l1) l2) + skeleton-regions (1- skeleton-regions) + l1 (cdr l1))) + (sort l2 '<)))) + (goto-char (car skeleton-regions)) + (setq skeleton-regions (cdr skeleton-regions))) + (let ((beg (point)) + skeleton-modified skeleton-point resume: help input v1 v2) + (unwind-protect + (eval `(let ,skeleton-further-elements + (skeleton-internal-list skeleton str))) + (run-hooks 'skeleton-end-hook) + (sit-for 0) + (or (pos-visible-in-window-p beg) + (progn + (goto-char beg) + (recenter 0))) + (if skeleton-point + (goto-char skeleton-point))))) + +(defun skeleton-read (str &optional initial-input recursive) + "Function for reading a string from the minibuffer within skeletons. +PROMPT may contain a `%s' which will be replaced by `skeleton-subprompt'. +If non-`nil' second arg INITIAL-INPUT or variable `input' is a string or +cons with index to insert before reading. If third arg RECURSIVE is non-`nil' +i.e. we are handling the iterator of a subskeleton, returns empty string if +user didn't modify input. +While reading, the value of `minibuffer-help-form' is variable `help' if that +is non-`nil' or a default string." + (let ((minibuffer-help-form (or (if (boundp 'help) (symbol-value 'help)) + (if recursive "\ +As long as you provide input you will insert another subskeleton. + +If you enter the empty string, the loop inserting subskeletons is +left, and the current one is removed as far as it has been entered. + +If you quit, the current subskeleton is removed as far as it has been +entered. No more of the skeleton will be inserted, except maybe for a +syntactically necessary termination." + "\ +You are inserting a skeleton. Standard text gets inserted into the buffer +automatically, and you are prompted to fill in the variable parts."))) + (eolp (eolp))) + ;; since Emacs doesn't show main window's cursor, do something noticeable + (or eolp + (open-line 1)) + (unwind-protect + (setq str (if (stringp str) + (read-string (format str skeleton-subprompt) + (setq initial-input + (or initial-input + (symbol-value 'input)))) + (eval str))) + (or eolp + (delete-char 1)))) + (if (and recursive + (or (null str) + (string= str "") + (equal str initial-input) + (equal str (car-safe initial-input)))) + (signal 'quit t) + str)) + +(defun skeleton-internal-list (skeleton &optional str recursive) + (let* ((start (save-excursion (beginning-of-line) (point))) + (column (current-column)) + (line (buffer-substring start + (save-excursion (end-of-line) (point)))) + opoint) + (or str + (setq str `(setq str (skeleton-read ',(car skeleton) nil ,recursive)))) + (while (setq skeleton-modified (eq opoint (point)) + opoint (point) + skeleton (cdr skeleton)) + (condition-case quit + (skeleton-internal-1 (car skeleton)) + (quit + (if (eq (cdr quit) 'recursive) + (setq recursive 'quit + skeleton (memq 'resume: skeleton)) + ;; remove the subskeleton as far as it has been shown + ;; the subskeleton shouldn't have deleted outside current line + (end-of-line) + (delete-region start (point)) + (insert line) + (move-to-column column) + (if (cdr quit) + (setq skeleton () + recursive nil) + (signal 'quit 'recursive))))))) + ;; maybe continue loop or go on to next outer resume: section + (if (eq recursive 'quit) + (signal 'quit 'recursive) + recursive)) + + +(defun skeleton-internal-1 (element &optional literal) + (cond ((char-or-string-p element) + (if (and (integerp element) ; -num + (< element 0)) + (if skeleton-untabify + (backward-delete-char-untabify (- element)) + (delete-backward-char (- element))) + (insert-before-markers (if (and skeleton-transformation + (not literal)) + (funcall skeleton-transformation element) + element)))) + ((eq element '\n) ; actually (eq '\n 'n) + (if (and skeleton-regions + (eq (nth 1 skeleton) '_)) + (progn + (or (eolp) + (newline)) + (indent-region (point) (car skeleton-regions) nil)) + (if skeleton-newline-indent-rigidly + (indent-to (prog1 (current-indentation) + (newline))) + (newline) + (indent-according-to-mode)))) + ((eq element '>) + (if (and skeleton-regions + (eq (nth 1 skeleton) '_)) + (indent-region (point) (car skeleton-regions) nil) + (indent-according-to-mode))) + ((eq element '_) + (if skeleton-regions + (progn + (goto-char (car skeleton-regions)) + (setq skeleton-regions (cdr skeleton-regions)) + (and (<= (current-column) (current-indentation)) + (eq (nth 1 skeleton) '\n) + (end-of-line 0))) + (or skeleton-point + (setq skeleton-point (point))))) + ((eq element '&) + (if skeleton-modified + (setq skeleton (cdr skeleton)))) + ((eq element '|) + (or skeleton-modified + (setq skeleton (cdr skeleton)))) + ((eq 'quote (car-safe element)) + (eval (nth 1 element))) + ((or (stringp (car-safe element)) + (consp (car-safe element))) + (if (symbolp (car-safe (car element))) + (while (skeleton-internal-list element nil t)) + (setq literal (car element)) + (while literal + (skeleton-internal-list element (car literal)) + (setq literal (cdr literal))))) + ((null element)) + ((skeleton-internal-1 (eval element) t)))) + + +;; Maybe belongs into simple.el or elsewhere + +(define-skeleton local-variables-section + "Insert a local variables section. Use current comment syntax if any." + () + '(save-excursion + (if (re-search-forward page-delimiter nil t) + (error "Not on last page."))) + comment-start "Local Variables:" comment-end \n + comment-start "mode: " + (completing-read "Mode: " obarray + (lambda (symbol) + (if (commandp symbol) + (string-match "-mode$" (symbol-name symbol)))) + t) + & -5 | '(kill-line 0) & -1 | comment-end \n + ( (completing-read (format "Variable, %s: " skeleton-subprompt) + obarray + (lambda (symbol) + (or (eq symbol 'eval) + (user-variable-p symbol))) + t) + comment-start str ": " + (read-from-minibuffer "Expression: " nil read-expression-map nil + 'read-expression-history) | _ + comment-end \n) + resume: + comment-start "End:" comment-end) + +;; Variables and command for automatically inserting pairs like () or "". + +(defvar skeleton-pair nil + "*If this is nil pairing is turned off, no matter what else is set. +Otherwise modes with `skeleton-pair-insert-maybe' on some keys +will attempt to insert pairs of matching characters.") + + +(defvar skeleton-pair-on-word nil + "*If this is nil, paired insertion is inhibited before or inside a word.") + + +(defvar skeleton-pair-filter (lambda ()) + "Attempt paired insertion if this function returns nil, before inserting. +This allows for context-sensitive checking whether pairing is appropriate.") + + +(defvar skeleton-pair-alist () + "An override alist of pairing partners matched against `last-command-char'. +Each alist element, which looks like (ELEMENT ...), is passed to +`skeleton-insert' with no interactor. Variable `str' does nothing. + +Elements might be (?` ?` _ \"''\"), (?\\( ? _ \" )\") or (?{ \\n > _ \\n ?} >).") + + +;;;###autoload +(defun skeleton-pair-insert-maybe (arg) + "Insert the character you type ARG times. + +With no ARG, if `skeleton-pair' is non-nil, and if +`skeleton-pair-on-word' is non-nil or we are not before or inside a +word, and if `skeleton-pair-filter' returns nil, pairing is performed. + +If a match is found in `skeleton-pair-alist', that is inserted, else +the defaults are used. These are (), [], {}, <> and `' for the +symmetrical ones, and the same character twice for the others." + (interactive "*P") + (if (or arg + overwrite-mode + (not skeleton-pair) + (if (not skeleton-pair-on-word) (looking-at "\\w")) + (funcall skeleton-pair-filter)) + (self-insert-command (prefix-numeric-value arg)) + (self-insert-command 1) + (if skeleton-abbrev-cleanup + () + ;; (preceding-char) is stripped of any Meta-stuff in last-command-char + (if (setq arg (assq (preceding-char) skeleton-pair-alist)) + ;; typed char is inserted (car is no real interactor) + (let (skeleton-end-hook) + (skeleton-insert arg)) + (save-excursion + (insert (or (cdr (assq (preceding-char) + '((?( . ?)) + (?[ . ?]) + (?{ . ?}) + (?< . ?>) + (?` . ?')))) + last-command-char))))))) + + +;;; ;; A more serious example can be found in sh-script.el +;;; ;; The quote before (defun prevents this from being byte-compiled. +;;;(defun mirror-mode () +;;; "This major mode is an amusing little example of paired insertion. +;;;All printable characters do a paired self insert, while the other commands +;;;work normally." +;;; (interactive) +;;; (kill-all-local-variables) +;;; (make-local-variable 'pair) +;;; (make-local-variable 'pair-on-word) +;;; (make-local-variable 'pair-filter) +;;; (make-local-variable 'pair-alist) +;;; (setq major-mode 'mirror-mode +;;; mode-name "Mirror" +;;; pair-on-word t +;;; ;; in the middle column insert one or none if odd window-width +;;; pair-filter (lambda () +;;; (if (>= (current-column) +;;; (/ (window-width) 2)) +;;; ;; insert both on next line +;;; (next-line 1) +;;; ;; insert one or both? +;;; (= (* 2 (1+ (current-column))) +;;; (window-width)))) +;;; ;; mirror these the other way round as well +;;; pair-alist '((?) _ ?() +;;; (?] _ ?[) +;;; (?} _ ?{) +;;; (?> _ ?<) +;;; (?/ _ ?\\) +;;; (?\\ _ ?/) +;;; (?` ?` _ "''") +;;; (?' ?' _ "``")) +;;; ;; in this mode we exceptionally ignore the user, else it's no fun +;;; pair t) +;;; (let ((map (make-keymap)) +;;; (i ? )) +;;; (use-local-map map) +;;; (setq map (car (cdr map))) +;;; (while (< i ?\^?) +;;; (aset map i 'skeleton-pair-insert-maybe) +;;; (setq i (1+ i)))) +;;; (run-hooks 'mirror-mode-hook)) + +;; skeleton.el ends here