(rmail-summary-bury): New function.
[bpt/emacs.git] / lisp / skeleton.el
CommitLineData
f3611c70 1;;; skeleton.el --- Lisp language extension for writing statement skeletons
b578f267 2
f3611c70 3;; Copyright (C) 1993, 1994, 1995 by Free Software Foundation, Inc.
ac59aed8 4
f3611c70 5;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
ac59aed8 6;; Maintainer: FSF
f3611c70 7;; Keywords: extensions, abbrev, languages, tools
ac59aed8
RS
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
b578f267
EN
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
ac59aed8
RS
25
26;;; Commentary:
27
f3611c70 28;; A very concise language extension for writing structured statement
ac59aed8
RS
29;; skeleton insertion commands for programming language modes. This
30;; originated in shell-script mode and was applied to ada-mode's
31;; commands which shrunk to one third. And these commands are now
32;; user configurable.
33
34;;; Code:
35
f3611c70 36;; page 1: statement skeleton language definition & interpreter
ac59aed8
RS
37;; page 2: paired insertion
38;; page 3: mirror-mode, an example for setting up paired insertion
39
40
41(defvar skeleton-transformation nil
f3611c70 42 "*If non-nil, function applied to literal strings before they are inserted.
ac59aed8
RS
43It should take strings and characters and return them transformed, or nil
44which means no transformation.
45Typical examples might be `upcase' or `capitalize'.")
46
47; this should be a fourth argument to defvar
48(put 'skeleton-transformation 'variable-interactive
49 "aTransformation function: ")
50
51
52
da6a884f
KH
53(defvar skeleton-end-hook
54 (lambda ()
55 (or (eolp) (newline-and-indent)))
56 "Hook called at end of skeleton but before going to point of interest.
57By default this moves out anything following to next line.
58The variables `v1' and `v2' are still set when calling this.")
59
60
f3611c70
KH
61;;;###autoload
62(defvar skeleton-filter 'identity
63 "Function for transforming a skeleton-proxy's aliases' variable value.")
64
f3611c70
KH
65(defvar skeleton-untabify t
66 "When non-`nil' untabifies when deleting backwards with element -ARG.")
67
4bfd70e9
KH
68(defvar skeleton-newline-indent-rigidly nil
69 "When non-`nil', indent rigidly under current line for element `\\n'.
70Else use mode's `indent-line-function'.")
f3611c70
KH
71
72(defvar skeleton-further-elements ()
73 "A buffer-local varlist (see `let') of mode specific skeleton elements.
74These variables are bound while interpreting a skeleton. Their value may
75in turn be any valid skeleton element if they are themselves to be used as
76skeleton elements.")
77(make-variable-buffer-local 'skeleton-further-elements)
78
79
ac59aed8
RS
80(defvar skeleton-subprompt
81 (substitute-command-keys
82 "RET, \\<minibuffer-local-map>\\[abort-recursive-edit] or \\[help-command]")
f3611c70
KH
83 "*Replacement for %s in prompts of recursive subskeletons.")
84
ac59aed8 85
f3611c70 86(defvar skeleton-abbrev-cleanup nil)
ac59aed8
RS
87
88
89(defvar skeleton-debug nil
90 "*If non-nil `define-skeleton' will override previous definition.")
91
4bfd70e9
KH
92;; reduce the number of compiler warnings
93(defvar skeleton)
94(defvar skeleton-modified)
95(defvar skeleton-point)
96(defvar skeleton-regions)
ac59aed8 97
ac59aed8 98;;;###autoload
f3611c70 99(defmacro define-skeleton (command documentation &rest skeleton)
ac59aed8
RS
100 "Define a user-configurable COMMAND that enters a statement skeleton.
101DOCUMENTATION is that of the command, while the variable of the same name,
f3611c70
KH
102which contains the skeleton, has a documentation to that effect.
103INTERACTOR and ELEMENT ... are as defined under `skeleton-insert'."
ac59aed8 104 (if skeleton-debug
f3611c70 105 (set command skeleton))
773500ab
KH
106 `(progn
107 (defvar ,command ',skeleton ,documentation)
108 (defalias ',command 'skeleton-proxy)))
f3611c70
KH
109
110
ac59aed8 111
f3611c70
KH
112;; This command isn't meant to be called, only it's aliases with meaningful
113;; names are.
114;;;###autoload
4bfd70e9
KH
115(defun skeleton-proxy (&optional str arg)
116 "Insert skeleton defined by variable of same name (see `skeleton-insert').
f3611c70
KH
117Prefix ARG allows wrapping around words or regions (see `skeleton-insert').
118This command can also be an abbrev expansion (3rd and 4th columns in
4bfd70e9
KH
119\\[edit-abbrevs] buffer: \"\" command-name).
120
121When called as a function, optional first argument STR may also be a string
122which will be the value of `str' whereas the skeleton's interactor is then
123ignored."
124 (interactive "*P\nP")
f3611c70
KH
125 (let ((function (nth 1 (backtrace-frame 1))))
126 (if (eq function 'nth) ; uncompiled lisp function
127 (setq function (nth 1 (backtrace-frame 5)))
128 (if (eq function 'byte-code) ; tracing byte-compiled function
129 (setq function (nth 1 (backtrace-frame 2)))))
130 (if (not (setq function (funcall skeleton-filter (symbol-value function))))
4bfd70e9
KH
131 (if (memq this-command '(self-insert-command
132 skeleton-pair-insert-maybe
133 expand-abbrev))
134 (setq buffer-undo-list (primitive-undo 1 buffer-undo-list)))
f3611c70 135 (skeleton-insert function
f3611c70
KH
136 (if (setq skeleton-abbrev-cleanup
137 (or (eq this-command 'self-insert-command)
da6a884f
KH
138 (eq this-command
139 'skeleton-pair-insert-maybe)))
f3611c70 140 ()
4bfd70e9 141 ;; Pretend C-x a e passed its prefix arg to us
f3611c70
KH
142 (if (or arg current-prefix-arg)
143 (prefix-numeric-value (or arg
4bfd70e9
KH
144 current-prefix-arg))))
145 (if (stringp str)
146 str))
f3611c70
KH
147 (if skeleton-abbrev-cleanup
148 (setq deferred-action-list t
149 deferred-action-function 'skeleton-abbrev-cleanup
150 skeleton-abbrev-cleanup (point))))))
151
152
153(defun skeleton-abbrev-cleanup (&rest list)
154 "Value for `post-command-hook' to remove char that expanded abbrev."
155 (if (integerp skeleton-abbrev-cleanup)
156 (progn
157 (delete-region skeleton-abbrev-cleanup (point))
158 (setq deferred-action-list ()
159 deferred-action-function nil
160 skeleton-abbrev-cleanup nil))))
ac59aed8
RS
161
162
163;;;###autoload
da6a884f 164(defun skeleton-insert (skeleton &optional skeleton-regions str)
f3611c70 165 "Insert the complex statement skeleton SKELETON describes very concisely.
ac59aed8 166
f3611c70
KH
167With optional third REGIONS wrap first interesting point (`_') in skeleton
168around next REGIONS words, if REGIONS is positive. If REGIONS is negative,
169wrap REGIONS preceding interregions into first REGIONS interesting positions
170\(successive `_'s) in skeleton. An interregion is the stretch of text between
171two contiguous marked points. If you marked A B C [] (where [] is the cursor)
172in alphabetical order, the 3 interregions are simply the last 3 regions. But
173if you marked B A [] C, the interregions are B-A, A-[], []-C.
174
4bfd70e9
KH
175Optional fourth STR is the value for the variable `str' within the skeleton.
176When this is non-`nil' the interactor gets ignored, and this should be a valid
177skeleton element.
178
f3611c70
KH
179SKELETON is made up as (INTERACTOR ELEMENT ...). INTERACTOR may be nil if
180not needed, a prompt-string or an expression for complex read functions.
ac59aed8
RS
181
182If ELEMENT is a string or a character it gets inserted (see also
183`skeleton-transformation'). Other possibilities are:
184
4bfd70e9 185 \\n go to next line and indent according to mode
f3611c70
KH
186 _ interesting point, interregion here, point after termination
187 > indent line (or interregion if > _) according to major mode
188 & do next ELEMENT if previous moved point
189 | do next ELEMENT if previous didn't move point
190 -num delete num preceding characters (see `skeleton-untabify')
ac59aed8
RS
191 resume: skipped, continue here if quit is signaled
192 nil skipped
193
f3611c70
KH
194Further elements can be defined via `skeleton-further-elements'. ELEMENT may
195itself be a SKELETON with an INTERACTOR. The user is prompted repeatedly for
196different inputs. The SKELETON is processed as often as the user enters a
197non-empty string. \\[keyboard-quit] terminates skeleton insertion, but
198continues after `resume:' and positions at `_' if any. If INTERACTOR in such
199a subskeleton is a prompt-string which contains a \".. %s ..\" it is
4bfd70e9
KH
200formatted with `skeleton-subprompt'. Such an INTERACTOR may also a list of
201strings with the subskeleton being repeated once for each string.
ac59aed8 202
f3611c70 203Quoted lisp-expressions are evaluated evaluated for their side-effect.
ac59aed8 204Other lisp-expressions are evaluated and the value treated as above.
49168e73 205Note that expressions may not return `t' since this implies an
f3611c70
KH
206endless loop. Modes can define other symbols by locally setting them
207to any valid skeleton element. The following local variables are
208available:
ac59aed8 209
f3611c70 210 str first time: read a string according to INTERACTOR
ac59aed8 211 then: insert previously read string once more
f3611c70 212 help help-form during interaction with the user or `nil'
773500ab 213 input initial input (string or cons with index) while reading str
4bfd70e9
KH
214 v1, v2 local variables for memorising anything you want
215
216When done with skeleton, but before going back to `_'-point call
217`skeleton-end-hook' if that is non-`nil'."
218 (and skeleton-regions
219 (setq skeleton-regions
220 (if (> skeleton-regions 0)
f3611c70 221 (list (point-marker)
4bfd70e9
KH
222 (save-excursion (forward-word skeleton-regions)
223 (point-marker)))
224 (setq skeleton-regions (- skeleton-regions))
225 ;; copy skeleton-regions - 1 elements from `mark-ring'
f3611c70
KH
226 (let ((l1 (cons (mark-marker) mark-ring))
227 (l2 (list (point-marker))))
4bfd70e9 228 (while (and l1 (> skeleton-regions 0))
f3611c70 229 (setq l2 (cons (car l1) l2)
4bfd70e9 230 skeleton-regions (1- skeleton-regions)
f3611c70
KH
231 l1 (cdr l1)))
232 (sort l2 '<))))
4bfd70e9
KH
233 (goto-char (car skeleton-regions))
234 (setq skeleton-regions (cdr skeleton-regions)))
773500ab 235 (let ((beg (point))
4bfd70e9 236 skeleton-modified skeleton-point resume: help input v1 v2)
f3611c70 237 (unwind-protect
773500ab 238 (eval `(let ,skeleton-further-elements
4bfd70e9
KH
239 (skeleton-internal-list skeleton str)))
240 (run-hooks 'skeleton-end-hook)
773500ab
KH
241 (sit-for 0)
242 (or (pos-visible-in-window-p beg)
243 (progn
244 (goto-char beg)
245 (recenter 0)))
4bfd70e9
KH
246 (if skeleton-point
247 (goto-char skeleton-point)))))
f3611c70 248
f3611c70 249(defun skeleton-read (str &optional initial-input recursive)
773500ab 250 "Function for reading a string from the minibuffer within skeletons.
f3611c70 251PROMPT may contain a `%s' which will be replaced by `skeleton-subprompt'.
773500ab
KH
252If non-`nil' second arg INITIAL-INPUT or variable `input' is a string or
253cons with index to insert before reading. If third arg RECURSIVE is non-`nil'
254i.e. we are handling the iterator of a subskeleton, returns empty string if
255user didn't modify input.
256While reading, the value of `minibuffer-help-form' is variable `help' if that
257is non-`nil' or a default string."
da6a884f
KH
258 (let ((minibuffer-help-form (or (if (boundp 'help) (symbol-value 'help))
259 (if recursive "\
ac59aed8
RS
260As long as you provide input you will insert another subskeleton.
261
262If you enter the empty string, the loop inserting subskeletons is
263left, and the current one is removed as far as it has been entered.
264
265If you quit, the current subskeleton is removed as far as it has been
266entered. No more of the skeleton will be inserted, except maybe for a
f3611c70 267syntactically necessary termination."
773500ab 268 "\
f3611c70 269You are inserting a skeleton. Standard text gets inserted into the buffer
da6a884f
KH
270automatically, and you are prompted to fill in the variable parts.")))
271 (eolp (eolp)))
272 ;; since Emacs doesn't show main window's cursor, do something noticeable
273 (or eolp
274 (open-line 1))
275 (unwind-protect
276 (setq str (if (stringp str)
277 (read-string (format str skeleton-subprompt)
278 (setq initial-input
279 (or initial-input
280 (symbol-value 'input))))
281 (eval str)))
282 (or eolp
283 (delete-char 1))))
773500ab
KH
284 (if (and recursive
285 (or (null str)
286 (string= str "")
287 (equal str initial-input)
288 (equal str (car-safe initial-input))))
ac59aed8
RS
289 (signal 'quit t)
290 str))
291
4bfd70e9 292(defun skeleton-internal-list (skeleton &optional str recursive)
f3611c70
KH
293 (let* ((start (save-excursion (beginning-of-line) (point)))
294 (column (current-column))
295 (line (buffer-substring start
296 (save-excursion (end-of-line) (point))))
297 opoint)
4bfd70e9
KH
298 (or str
299 (setq str `(setq str (skeleton-read ',(car skeleton) nil ,recursive))))
300 (while (setq skeleton-modified (eq opoint (point))
773500ab
KH
301 opoint (point)
302 skeleton (cdr skeleton))
303 (condition-case quit
304 (skeleton-internal-1 (car skeleton))
305 (quit
306 (if (eq (cdr quit) 'recursive)
4bfd70e9
KH
307 (setq recursive 'quit
308 skeleton (memq 'resume: skeleton))
773500ab
KH
309 ;; remove the subskeleton as far as it has been shown
310 ;; the subskeleton shouldn't have deleted outside current line
da6a884f 311 (end-of-line)
773500ab
KH
312 (delete-region start (point))
313 (insert line)
314 (move-to-column column)
315 (if (cdr quit)
316 (setq skeleton ()
317 recursive nil)
318 (signal 'quit 'recursive)))))))
319 ;; maybe continue loop or go on to next outer resume: section
320 (if (eq recursive 'quit)
321 (signal 'quit 'recursive)
322 recursive))
f3611c70
KH
323
324
325(defun skeleton-internal-1 (element &optional literal)
4bfd70e9
KH
326 (cond ((char-or-string-p element)
327 (if (and (integerp element) ; -num
328 (< element 0))
329 (if skeleton-untabify
330 (backward-delete-char-untabify (- element))
331 (delete-backward-char (- element)))
332 (insert-before-markers (if (and skeleton-transformation
333 (not literal))
334 (funcall skeleton-transformation element)
335 element))))
c93d212a 336 ((eq element '\n) ; actually (eq '\n 'n)
4bfd70e9
KH
337 (if (and skeleton-regions
338 (eq (nth 1 skeleton) '_))
339 (progn
340 (or (eolp)
341 (newline))
342 (indent-region (point) (car skeleton-regions) nil))
343 (if skeleton-newline-indent-rigidly
344 (indent-to (prog1 (current-indentation)
345 (newline)))
346 (newline)
347 (indent-according-to-mode))))
c93d212a 348 ((eq element '>)
4bfd70e9 349 (if (and skeleton-regions
f3611c70 350 (eq (nth 1 skeleton) '_))
4bfd70e9
KH
351 (indent-region (point) (car skeleton-regions) nil)
352 (indent-according-to-mode)))
c93d212a 353 ((eq element '_)
4bfd70e9 354 (if skeleton-regions
f3611c70 355 (progn
4bfd70e9 356 (goto-char (car skeleton-regions))
da6a884f
KH
357 (setq skeleton-regions (cdr skeleton-regions))
358 (and (<= (current-column) (current-indentation))
359 (eq (nth 1 skeleton) '\n)
360 (end-of-line 0)))
4bfd70e9
KH
361 (or skeleton-point
362 (setq skeleton-point (point)))))
c93d212a 363 ((eq element '&)
4bfd70e9 364 (if skeleton-modified
f3611c70 365 (setq skeleton (cdr skeleton))))
c93d212a 366 ((eq element '|)
4bfd70e9 367 (or skeleton-modified
f3611c70 368 (setq skeleton (cdr skeleton))))
4bfd70e9 369 ((eq 'quote (car-safe element))
f3611c70 370 (eval (nth 1 element)))
4bfd70e9
KH
371 ((or (stringp (car-safe element))
372 (consp (car-safe element)))
373 (if (symbolp (car-safe (car element)))
374 (while (skeleton-internal-list element nil t))
375 (setq literal (car element))
376 (while literal
377 (skeleton-internal-list element (car literal))
378 (setq literal (cdr literal)))))
f3611c70
KH
379 ((null element))
380 ((skeleton-internal-1 (eval element) t))))
ac59aed8 381
4bfd70e9 382
f3611c70
KH
383;; Maybe belongs into simple.el or elsewhere
384
a013d266
RS
385;;;(define-skeleton local-variables-section
386;;; "Insert a local variables section. Use current comment syntax if any."
387;;; ()
388;;; '(save-excursion
389;;; (if (re-search-forward page-delimiter nil t)
390;;; (error "Not on last page.")))
391;;; comment-start "Local Variables:" comment-end \n
392;;; comment-start "mode: "
393;;; (completing-read "Mode: " obarray
394;;; (lambda (symbol)
395;;; (if (commandp symbol)
396;;; (string-match "-mode$" (symbol-name symbol))))
397;;; t)
398;;; & -5 | '(kill-line 0) & -1 | comment-end \n
399;;; ( (completing-read (format "Variable, %s: " skeleton-subprompt)
400;;; obarray
401;;; (lambda (symbol)
402;;; (or (eq symbol 'eval)
403;;; (user-variable-p symbol)))
404;;; t)
405;;; comment-start str ": "
406;;; (read-from-minibuffer "Expression: " nil read-expression-map nil
407;;; 'read-expression-history) | _
408;;; comment-end \n)
409;;; resume:
410;;; comment-start "End:" comment-end)
ac59aed8 411\f
bc35d5b3 412;; Variables and command for automatically inserting pairs like () or "".
ac59aed8 413
bc35d5b3 414(defvar skeleton-pair nil
ac59aed8 415 "*If this is nil pairing is turned off, no matter what else is set.
bc35d5b3
RS
416Otherwise modes with `skeleton-pair-insert-maybe' on some keys
417will attempt to insert pairs of matching characters.")
ac59aed8
RS
418
419
bc35d5b3
RS
420(defvar skeleton-pair-on-word nil
421 "*If this is nil, paired insertion is inhibited before or inside a word.")
ac59aed8
RS
422
423
bc35d5b3
RS
424(defvar skeleton-pair-filter (lambda ())
425 "Attempt paired insertion if this function returns nil, before inserting.
ac59aed8
RS
426This allows for context-sensitive checking whether pairing is appropriate.")
427
428
bc35d5b3
RS
429(defvar skeleton-pair-alist ()
430 "An override alist of pairing partners matched against `last-command-char'.
431Each alist element, which looks like (ELEMENT ...), is passed to
432`skeleton-insert' with no interactor. Variable `str' does nothing.
ac59aed8 433
f3611c70 434Elements might be (?` ?` _ \"''\"), (?\\( ? _ \" )\") or (?{ \\n > _ \\n ?} >).")
ac59aed8
RS
435
436
ac59aed8 437;;;###autoload
bc35d5b3 438(defun skeleton-pair-insert-maybe (arg)
ac59aed8
RS
439 "Insert the character you type ARG times.
440
bc35d5b3
RS
441With no ARG, if `skeleton-pair' is non-nil, and if
442`skeleton-pair-on-word' is non-nil or we are not before or inside a
443word, and if `skeleton-pair-filter' returns nil, pairing is performed.
ac59aed8 444
bc35d5b3 445If a match is found in `skeleton-pair-alist', that is inserted, else
ac59aed8
RS
446the defaults are used. These are (), [], {}, <> and `' for the
447symmetrical ones, and the same character twice for the others."
448 (interactive "*P")
449 (if (or arg
773500ab 450 overwrite-mode
bc35d5b3
RS
451 (not skeleton-pair)
452 (if (not skeleton-pair-on-word) (looking-at "\\w"))
453 (funcall skeleton-pair-filter))
ac59aed8 454 (self-insert-command (prefix-numeric-value arg))
f3611c70
KH
455 (self-insert-command 1)
456 (if skeleton-abbrev-cleanup
457 ()
458 ;; (preceding-char) is stripped of any Meta-stuff in last-command-char
bc35d5b3 459 (if (setq arg (assq (preceding-char) skeleton-pair-alist))
da6a884f
KH
460 ;; typed char is inserted (car is no real interactor)
461 (let (skeleton-end-hook)
462 (skeleton-insert arg))
f3611c70
KH
463 (save-excursion
464 (insert (or (cdr (assq (preceding-char)
465 '((?( . ?))
466 (?[ . ?])
467 (?{ . ?})
468 (?< . ?>)
469 (?` . ?'))))
470 last-command-char)))))))
ac59aed8
RS
471
472\f
bc35d5b3
RS
473;;; ;; A more serious example can be found in sh-script.el
474;;; ;; The quote before (defun prevents this from being byte-compiled.
475;;;(defun mirror-mode ()
476;;; "This major mode is an amusing little example of paired insertion.
477;;;All printable characters do a paired self insert, while the other commands
478;;;work normally."
479;;; (interactive)
480;;; (kill-all-local-variables)
481;;; (make-local-variable 'pair)
482;;; (make-local-variable 'pair-on-word)
483;;; (make-local-variable 'pair-filter)
484;;; (make-local-variable 'pair-alist)
485;;; (setq major-mode 'mirror-mode
486;;; mode-name "Mirror"
487;;; pair-on-word t
488;;; ;; in the middle column insert one or none if odd window-width
489;;; pair-filter (lambda ()
490;;; (if (>= (current-column)
491;;; (/ (window-width) 2))
492;;; ;; insert both on next line
493;;; (next-line 1)
494;;; ;; insert one or both?
495;;; (= (* 2 (1+ (current-column)))
496;;; (window-width))))
497;;; ;; mirror these the other way round as well
498;;; pair-alist '((?) _ ?()
499;;; (?] _ ?[)
500;;; (?} _ ?{)
501;;; (?> _ ?<)
502;;; (?/ _ ?\\)
503;;; (?\\ _ ?/)
504;;; (?` ?` _ "''")
505;;; (?' ?' _ "``"))
506;;; ;; in this mode we exceptionally ignore the user, else it's no fun
507;;; pair t)
508;;; (let ((map (make-keymap))
509;;; (i ? ))
510;;; (use-local-map map)
511;;; (setq map (car (cdr map)))
512;;; (while (< i ?\^?)
513;;; (aset map i 'skeleton-pair-insert-maybe)
514;;; (setq i (1+ i))))
515;;; (run-hooks 'mirror-mode-hook))
ac59aed8 516
2aea64fb
RS
517(provide 'skeleton)
518
ac59aed8 519;; skeleton.el ends here