Initial revision
[bpt/emacs.git] / lisp / expand.el
CommitLineData
e26f93cc
RS
1;; expand.el --- minor mode to make abbreviations more usable.
2
3;; Copyright (C) 1995, 1996 Free Software Foundation, Inc.
4
5;; Author: Frederic Lepied <Frederic.Lepied@sugix.frmug.org>
6;; Maintainer: Frederic Lepied <Frederic.Lepied@sugix.frmug.org>
7;; Keywords: abbrev
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
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.
25
26;;; Commentary:
27;;
28;; Purpose of this package:
29;; 1. Expand abbreviations only when they are at the end of a line and not
30;; in a comment or in a string.
31;; 2. Position the cursor after expansion to a place specified by advance.
32;; 3. Indent the expanded region.
33;; 4. If a list of points as been provided with the abbreviation definition,
34;; the functions expand-jump-to-previous-mark and expand-jump-to-next-mark
35;; moved from mark to mark.
36;;
37;; Installation:
38;; * store this file somewhere in your load-path and byte compile it.
39;; * put (require 'expand) in your .emacs or in site-start.el or generate
40;; autoloads.
41;; * and according to the mode install your expansion table.
42;;
43;; Look at the Sample: section for emacs-lisp, perl and c expand lists.
44;; For example for c-mode, you could declare your abbrev table with :
45;;
46;; (defconst c-expand-list
47;; '(("if" "if () {\n \n} else {\n \n}" (5 10 21))
48;; ("ifn" "if () {}" (5 8))
49;; ("uns" "unsigned ")
50;; ("for" "for(; ; ) {\n\n}" (5 7 9 13))
51;; ("switch" "switch () {\n\n}" (9 13))
52;; ("case" "case :\n\nbreak;\n" (6 8 16))
53;; ("do" "do {\n\n} while ();" (6 16))
54;; ("while" "while () {\n\n}" (8 12))
55;; ("default" "default:\n\nbreak;" 10)
56;; ("main" "int\nmain(int argc, char * argv[])\n{\n\n}\n" 37))
57;; "Expansions for C mode")
58;;
59;; and enter Expand mode with the following hook :
60;;
61;; (add-hook 'c-mode-hook (function (lambda()
62;; (expand-add-abbrevs c-mode-abbrev-table c-expand-list)
63;; (expand-mode))))
64;;
65;; you can also bind jump functions to some keys and init some post-process
66;; hooks :
67;;
68;; (add-hook 'expand-mode-load-hook
69;; (function
70;; (lambda()
71;; (add-hook 'expand-expand-hook 'indent-according-to-mode)
72;; (add-hook 'expand-jump-hook 'indent-according-to-mode)
73;; (define-key expand-map '[(control tab)] 'expand-jump-to-next-mark)
74;; (define-key expand-map '[(control shift tab)] 'expand-jump-to-previous-mark))))
75;;
76;; Remarks:
77;;
78;; Has been tested under emacs 19.28-19.34 and XEmacs 19.11.
79;; Many thanks to Heddy Boubaker <boubaker@cenatls.cena.dgac.fr>,
80;; Jerome Santini <santini@chambord.univ-orleans.fr>,
81;; Jari Aalto <jaalto@tre.tele.nokia.fi>.
82;;
83;; Please send me a word to give me your feeling about this mode or
84;; to explain me how you use it (your expansions table for example) using
85;; the function expand-mode-submit-report.
86\f
87;; Expand mode is not a replacement for abbrev it is just a layer above it.
88
89;;; Constants:
90
91(defconst expand-mode-version "$Id: expand.el,v 1.13 1996/11/23 18:59:53 fred Exp $"
92 "Version tag for expand.el.")
93
94(defconst expand-mode-help-address "expand-help@sugix.frmug.org"
95 "Email address to send requests, comments or bug reports.")
96
97(defvar expand-mode nil
98 "Status variable for Expand mode.")
99(make-variable-buffer-local 'expand-mode)
100
101(defvar expand-mode-name " Expand"
102 "Name of mode displayed in the modeline for Expand mode.")
103
104(defvar expand-mode-hook nil
105 "Hooks run when Expand mode is enabled.")
106
107(defvar expand-mode-load-hook nil
108 "Hooks run when expand is loaded.")
109
110(defvar expand-expand-hook nil
111 "Hooks run when expansion is done.")
112
113(defvar expand-jump-hook nil
114 "Hooks run when jump to mark occurs.")
115
116;;; Samples:
117
118(define-skeleton expand-c-for-skeleton "For loop skeleton"
119 "Loop var: "
120 "for(" str _ @ "=0; " str @ "; " str @ ") {" \n
121 @ _ \n
122 "}" >
123 )
124
125(defconst expand-c-sample-expand-list
126 '(("if" "if () {\n \n} else {\n \n}" (5 10 21))
127 ("ifn" "if () {}" (5 8))
128 ("uns" "unsigned ")
129 ("for" expand-c-for-skeleton)
130 ("switch" "switch () {\n\n}" (9 13))
131 ("case" "case :\n\nbreak;\n" (6 8 16))
132 ("do" "do {\n\n} while ();" (6 16))
133 ("while" "while () {\n\n}" (8 12))
134 ("default" "default:\n\nbreak;" 10)
135 ("main" "int\nmain(int argc, char * argv[])\n{\n\n}\n" 37))
136 "Expansions for C mode. See `expand-add-abbrevs'.")
137
138;; lisp example from Jari Aalto <jaalto@tre.tele.nokia.fi>
139(defconst expand-sample-lisp-mode-expand-list
140 (list
141 (list
142 "defu"
143 (concat
144 "(defun ()\n"
145 " \"\"\n"
146 " (interactive)\n"
147 " (let* (\n"
148 " )\n"
149 " \n"
150 " ))")
151 (list 8 11 16 32 43 59))
152
153 (list
154 "defs"
155 (concat
156 "(defsubst ()\n"
157 " \"\"\n"
158 " (interactive)\n"
159 " )")
160 (list 11 14 19 23 39))
161
162 (list
163 "defm"
164 (concat
165 "(defmacro ()\n"
166 " \"\"\n"
167 " (` \n"
168 " ))")
169 (list 11 13 18 25))
170
171 (list
172 "defa"
173 (concat
174 "(defadvice (around act)\n"
175 " \"\"\n"
176 " \n"
177 " )")
178 (list 12 22 32 36))
179
180 (list
181 "defc"
182 "(defconst nil\n \"\")\n"
183 (list 11 13 20))
184
185 (list
186 "defv"
187 "(defvar nil\n \"\")\n"
188 (list 9 11 18))
189
190 (list
191 "let"
192 "(let* (\n)\n "
193 (list 8 13))
194
195 (list
196 "sav"
197 "(save-excursion\n \n)"
198 (list 18))
199
200 (list
201 "aut"
202 "(autoload ' \"\" t t)\n"
203 (list 12 14))
204
205 )
206 "Expansions for Lisp mode. See `expand-add-abbrevs'.")
207
208;; perl example from Jari Aalto <jaalto@tre.tele.nokia.fi>
209(defconst expand-sample-perl-mode-expand-list
210 (list
211 (list
212 ;; This is default perl4 subroutine template
213 ;;
214 "sub"
215 (concat
216 "#" (make-string 70 ?-) "\n"
217 "sub {\n"
218 " # DESCRIPTION\n"
219 " # \n"
220 " # \n"
221 " # INPUT\n"
222 " # \n"
223 " # \n"
224 " # RETURN\n"
225 " # \n"
226 "\n"
227 " local( $f ) = \"$lib.\";\n" ;; Function name AFTER period
228 " local() = @_;\n" ;; func arguments here
229 " \n"
230 " \n}\n"
231 )
232 (list 77 88 120 146 159 176))
233
234 (list
235 "for" ; foreach
236 (concat
237 "for ( )\n"
238 "{\n\n\}"
239 )
240 (list 7 12))
241
242 (list
243 "whi" ; foreach
244 (concat
245 "while ( )\n"
246 "{\n\n\}"
247 )
248 (list 9 15))
249
250
251 ;; The normal "if" can be used like
252 ;; print $F "xxxxxx" if defined @arr;
253 ;;
254 (list
255 "iff"
256 (concat
257 "if ( )\n"
258 "{\n\n\}"
259 )
260 (list 6 12))
261
262 (list "loc" "local( $ );" (list 9))
263 (list "my" "my( $ );" (list 6))
264 (list "ope" "open(,\"\")\t|| die \"$f: Can't open [$]\";" (list 6 8 36))
265 (list "clo" "close ;" 7)
266 (list "def" "defined " (list 9))
267 (list "und" "undef ;" (list 7))
268
269 ;; There is no ending colon, because they can be in statement
270 ;; defined $REXP_NOT_NEW && (print "xxxxx" );
271 ;;
272 (list "pr" "print " 7)
273 (list "pf" "printf " 8)
274
275
276 (list "gre" "grep( //, );" (list 8 11))
277 (list "pus" "push( , );" (list 7 9))
278 (list "joi" "join( '', );" (list 7 11))
279 (list "rtu" "return ;" (list 8))
280
281 )
282 "Expansions for Perl mode. See `expand-add-abbrevs'.")
283
284;;; Code:
285
286;;;###autoload
287(defun expand-mode(&optional arg)
288 "Toggle Expand mode.
289With argument ARG, turn Expand mode on if ARG is positive.
290In Expand mode, inserting an abbreviation at the end of a line
291causes it to expand and be replaced by its expansion."
292 (interactive "P")
293 (setq expand-mode (if (null arg) (not expand-mode)
294 (> (prefix-numeric-value arg) 0)))
295 (if expand-mode
296 (progn
297 (setq abbrev-mode nil)
298 (run-hooks 'expand-mode-hook))))
299
300;;;###autoload
301(defvar expand-map (make-sparse-keymap)
302 "Key map used in Expand mode.")
303(define-key expand-map " " 'expand-template-abbreviation)
304
305(or (assq 'expand-mode minor-mode-alist)
306 (setq minor-mode-alist (cons (list 'expand-mode expand-mode-name)
307 minor-mode-alist)))
308
309(or (assq 'expand-mode minor-mode-map-alist)
310 (setq minor-mode-map-alist (cons (cons 'expand-mode expand-map)
311 minor-mode-map-alist)))
312
313;;;###autoload
314(defun expand-add-abbrevs (table abbrevs)
315 "Add a list of abbrev to the table.
316Each abbrev description entry has the following format :
317 (abbrev expansion arg)
318where
319 abbrev is the abbreviation to replace.
320 expansion is the replacement string or a function which will make
321the expansion. For example you could use the DMacros or skeleton packages
322to generate such functions.
323 arg is an optional element which can be a number or a list of
324numbers. If arg is a number, the cursor will be placed at arg chars
325from the beginning of the expanded text. If expansion is a list of
326numbers the cursor will be placed according to the first number of the
327list from the beginning of the expanded text and marks will be placed
328and you will be able to visit them cyclicaly with the functions
329expand-jump-to-previous-mark and expand-jump-to-next-mark. If arg is
330omitted, the cursor will be placed at the end of the expanded text."
331 (if (null abbrevs)
332 table
333 (expand-add-abbrev table (nth 0 (car abbrevs)) (nth 1 (car abbrevs))
334 (nth 2 (car abbrevs)))
335 (expand-add-abbrevs table (cdr abbrevs))))
336
337(defvar expand-list nil "Temporary variable used by Expand mode.")
338
339(defvar expand-pos nil
340 "If non nil, stores a vector containing markers to positions defined by the last expansion.
341This variable is local to a buffer.")
342(make-variable-buffer-local 'expand-pos)
343
344(defvar expand-index 0
345 "Index of the last marker used in expand-pos.
346This variable is local to a buffer.")
347(make-variable-buffer-local 'expand-index)
348
349(defvar expand-point nil
350 "End of the expanded region.
351This variable is local to a buffer.")
352(make-variable-buffer-local 'expand-point)
353
354(defun expand-add-abbrev (table abbrev expansion arg)
355 "Add one abbreviation and provide the hook to move to the specified positions."
356 (let* ((string-exp (if (and (symbolp expansion) (fboundp expansion))
357 nil
358 expansion))
359 (position (if (and arg string-exp)
360 (if (listp arg)
361 (- (length expansion) (1- (car arg)))
362 (- (length expansion) (1- arg)))
363 0)))
364 (define-abbrev
365 table
366 abbrev
367 (vector string-exp
368 position
369 (if (and (listp arg)
370 (not (null arg)))
371 (cons (length string-exp) arg)
372 nil)
373 (if (and (symbolp expansion) (fboundp expansion))
374 expansion
375 nil)
376 )
377 'expand-abbrev-hook)))
378
379;;;###autoload
380(defun expand-template-abbreviation(arg)
381 "Do the expansion job if we are at the end of a line or insert space."
382 (interactive "p")
383 (or (expand-try-to-expand)
384 (self-insert-command arg)))
385
386;; Try to expand an abbrev. On success check if it is an Expand mode abbrev
387;; else undo the expansion.
388(defun expand-try-to-expand()
389 (if (not (expand-abbrev))
390 nil
391 (if (not (stringp (symbol-value last-abbrev)))
392 t
393 (unexpand-abbrev)
394 nil)))
395
396(defun expand-abbrev-hook()
397 "Abbrev hook used to do the expansion job of expand abbrevs. See `expand-add-abbrevs'."
398 ;; Expand only at the end of a line if we are near a word that has
399 ;; an abbrev built from expand-add-abbrev.
400 (if (and (eolp)
401 (not (expand-in-literal)))
402 (let ((p (point)))
403 (setq expand-point nil)
404 ;; don't expand if the preceding char isn't a word constituent
405 (if (and (eq (char-syntax (preceding-char))
406 ?w)
407 (expand-do-expansion))
408 (progn
409 ;; expand-point tells us if we have inserted the text
410 ;; ourself or if it is the hook which has done the job.
411 (if expand-point
412 (progn
413 (if (vectorp expand-list)
414 (expand-build-marks expand-point))
415 (indent-region p expand-point nil))
416 ;; an outside function can set expand-list to a list of
417 ;; markers in reverse order.
418 (if (listp expand-list)
419 (setq expand-index 0
420 expand-pos (expand-list-to-markers expand-list)
421 expand-list nil)))
422 (run-hooks 'expand-expand-hook)
423 t))))
424 )
425
426(defun expand-do-expansion()
427 (delete-backward-char (length last-abbrev-text))
428 (let* ((vect (symbol-value last-abbrev))
429 (text (aref vect 0))
430 (position (aref vect 1))
431 (jump-args (aref vect 2))
432 (hook (aref vect 3)))
433 (cond (text
434 (insert text)
435 (setq expand-point (point))))
436 (if jump-args
437 (funcall 'expand-build-list (car jump-args) (cdr jump-args)))
438 (if position
439 (backward-char position))
440 (if hook
441 (funcall hook))
442 t)
443 )
444
445(defun expand-abbrev-from-expand(word)
446 "Test if an abbrev has a hook."
447 (or
448 (and (intern-soft word local-abbrev-table)
449 (symbol-function (intern-soft word local-abbrev-table)))
450 (and (intern-soft word global-abbrev-table)
451 (symbol-function (intern-soft word global-abbrev-table)))))
452
453(defun expand-previous-word ()
454 "Return the previous word."
455 (save-excursion
456 (let ((p (point)))
457 (backward-word 1)
458 (buffer-substring p (point)))))
459
460(defun expand-jump-to-previous-mark()
461 "Move the cursor to previous mark created by the expansion."
462 (interactive)
463 (if expand-pos
464 (progn
465 (setq expand-index (1- expand-index))
466 (if (< expand-index 0)
467 (setq expand-index (1- (length expand-pos))))
468 (goto-char (aref expand-pos expand-index))
469 (run-hooks 'expand-jump-hook))))
470
471(defun expand-jump-to-next-mark()
472 "Move the cursor to next mark created by the expansion."
473 (interactive)
474 (if expand-pos
475 (progn
476 (setq expand-index (1+ expand-index))
477 (if (>= expand-index (length expand-pos))
478 (setq expand-index 0))
479 (goto-char (aref expand-pos expand-index))
480 (run-hooks 'expand-jump-hook))))
481
482(defun expand-build-list (len l)
483 "Build a vector of offset positions from the list of positions."
484 (expand-clear-markers)
485 (setq expand-list (vconcat l))
486 (let ((i 0)
487 (lenlist (length expand-list)))
488 (while (< i lenlist)
489 (aset expand-list i (- len (1- (aref expand-list i))))
490 (setq i (1+ i))))
491 )
492
493(defun expand-build-marks (p)
494 "Transform the offsets vector into a marker vector."
495 (if expand-list
496 (progn
497 (setq expand-index 0)
498 (setq expand-pos (make-vector (length expand-list) nil))
499 (let ((i (1- (length expand-list))))
500 (while (>= i 0)
501 (aset expand-pos i (copy-marker (- p (aref expand-list i))))
502 (setq i (1- i))))
503 (setq expand-list nil))))
504
505(defun expand-clear-markers ()
506 "Make the markers point nowhere."
507 (if expand-pos
508 (progn
509 (let ((i (1- (length expand-pos))))
510 (while (>= i 0)
511 (set-marker (aref expand-pos i) nil)
512 (setq i (1- i))))
513 (setq expand-pos nil))))
514
515(defun expand-in-literal ()
516 "Test if we are in a comment or in a string."
517 (save-excursion
518 (let* ((lim (or (save-excursion
519 (beginning-of-defun)
520 (point))
521 (point-min)))
522 (here (point))
523 (state (parse-partial-sexp lim (point))))
524 (cond
525 ((nth 3 state) 'string)
526 ((nth 4 state) 'comment)
527 (t nil)))))
528
529(defun expand-mode-submit-report ()
530 "Report a problem, a suggestion or a comment about Expand mode."
531 (interactive)
532 (require 'reporter)
533 (reporter-submit-bug-report
534 expand-mode-help-address
535 (concat "expand.el " expand-mode-version)
536 '(expand-mode-name
537 expand-mode-hook
538 expand-mode-load-hook
539 expand-map
540 )
541 nil
542 nil
543 "Dear expand.el maintainer,"))
544
545;; support functions to add marks to jump from outside function
546
547(defun expand-list-to-markers (l)
548 "Transform a list of markers in reverse order into a vector in the correct order."
549 (let* ((len (1- (length l)))
550 (loop len)
551 (v (make-vector (+ len 1) nil)))
552 (while (>= loop 0)
553 (aset v loop (if (markerp (car l)) (car l) (copy-marker (car l))))
554 (setq l (cdr l)
555 loop (1- loop)))
556 v))
557
558;; integration with skeleton.el
559(defun expand-skeleton-end-hook ()
560 "`skeleton-end-hook' to enable expand marks jumps for @ skeleton tags see `skeleton-insert'."
561 (if skeleton-marks
562 (setq expand-list skeleton-marks)))
563
564(add-hook 'skeleton-end-hook (function expand-skeleton-end-hook))
565
566(provide 'expand)
567
568;; run load hooks
569(run-hooks 'expand-mode-load-hook)
570
571;;; expand.el ends here