aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/progmodes/sh-script.el895
-rw-r--r--lisp/skeleton.el328
2 files changed, 1223 insertions, 0 deletions
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
new file mode 100644
index 00000000000..b0fd04d4a13
--- /dev/null
+++ b/lisp/progmodes/sh-script.el
@@ -0,0 +1,895 @@
1;;; sh-script.el --- shell-script editing commands for Emacs
2;; Copyright (C) 1993 Free Software Foundation, Inc.
3
4;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
5;; Maintainer: FSF
6;; Keywords: shell programming
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to
22;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24;;; Commentary:
25
26;; Major mode for editing shell scripts. Currently sh, ksh, bash and csh,
27;; tcsh are supported. Structured statements can be inserted with one
28;; command.
29
30;;; Code:
31
32;; page 1: variables and settings
33;; page 2: mode-command and utility functions
34;; page 3: statement syntax-commands for various shells
35;; page 4: various other commands
36
37
38;;;###autoload
39(setq auto-mode-alist
40 ;; matches files
41 ;; - who's path contains /bin/, but not directories
42 (cons '("/bin/" . sh-or-other-mode)
43 ;; - that have a suffix .sh or .shar (shell archive)
44 ;; - that contain ressources for the various shells
45 ;; - startup files for X11
46 (cons '("\\.sh$\\|\\.shar$\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)$" . sh-mode)
47 auto-mode-alist)))
48
49
50(defvar sh-mode-syntax-table
51 (let ((table (copy-syntax-table)))
52 (modify-syntax-entry ?\# "<" table)
53 (modify-syntax-entry ?\^l ">#" table)
54 (modify-syntax-entry ?\n ">#" table)
55 (modify-syntax-entry ?\" "\"\"" table)
56 (modify-syntax-entry ?\' "\"'" table)
57 (modify-syntax-entry ?\` "$`" table)
58 (modify-syntax-entry ?$ "_" table)
59 (modify-syntax-entry ?! "_" table)
60 (modify-syntax-entry ?% "_" table)
61 (modify-syntax-entry ?: "_" table)
62 (modify-syntax-entry ?. "_" table)
63 (modify-syntax-entry ?^ "_" table)
64 (modify-syntax-entry ?~ "_" table)
65 table)
66 "Syntax table in use in Shell-Script mode.")
67
68
69
70(defvar sh-use-prefix nil
71 "If non-nil when loading, `$' and `<' will be C-c $ and C-c < .")
72
73(defvar sh-mode-map
74 (let ((map (make-sparse-keymap)))
75 (define-key map "\C-c(" 'sh-function)
76 (define-key map "\C-c\C-w" 'sh-while)
77 (define-key map "\C-c\C-u" 'sh-until)
78 (define-key map "\C-c\C-s" 'sh-select)
79 (define-key map "\C-c\C-l" 'sh-indexed-loop)
80 (define-key map "\C-c\C-i" 'sh-if)
81 (define-key map "\C-c\C-f" 'sh-for)
82 (define-key map "\C-c\C-c" 'sh-case)
83
84 (define-key map (if sh-use-prefix "\C-c$" "$")
85 'sh-query-for-variable)
86 (define-key map "=" 'sh-assignment)
87 (define-key map "\C-c+" 'sh-add)
88 (define-key map (if sh-use-prefix "\C-c<" "<")
89 'sh-maybe-here-document)
90 (define-key map "(" 'pair-insert-maybe)
91 (define-key map "{" 'pair-insert-maybe)
92 (define-key map "[" 'pair-insert-maybe)
93 (define-key map "'" 'pair-insert-maybe)
94 (define-key map "`" 'pair-insert-maybe)
95 (define-key map "\"" 'pair-insert-maybe)
96
97 (define-key map "\t" 'sh-indent-line)
98 (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename
99 map (current-global-map))
100 (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
101 map (current-global-map))
102 ;; Now that tabs work properly, this might be unwanted.
103 (substitute-key-definition 'delete-backward-char
104 'backward-delete-char-untabify
105 map (current-global-map))
106 (define-key map "\C-c:" 'sh-set-shell)
107 (substitute-key-definition 'beginning-of-defun
108 'sh-beginning-of-compound-command
109 map (current-global-map))
110 (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
111 map (current-global-map))
112 (substitute-key-definition 'forward-sentence 'sh-end-of-command
113 map (current-global-map))
114 (substitute-key-definition 'manual-entry 'sh-manual-entry
115 map (current-global-map))
116 (define-key map [menu-bar insert]
117 (cons "Insert" (make-sparse-keymap "Insert")))
118 (define-key map [menu-bar insert sh-while]
119 '("While loop" . sh-while))
120 (define-key map [menu-bar insert sh-until]
121 '("Until loop" . sh-until))
122 (define-key map [menu-bar insert sh-select]
123 '("Select statement" . sh-select))
124 (define-key map [menu-bar insert sh-indexed-loop]
125 '("Indexed loop" . sh-indexed-loop))
126 (define-key map [menu-bar insert sh-if]
127 '("If statement" . sh-if))
128 (define-key map [menu-bar insert sh-for]
129 '("For loop" . sh-for))
130 (define-key map [menu-bar insert sh-case]
131 '("Case statement" . sh-case))
132 map)
133 "Keymap used in Shell-Script mode.")
134
135
136
137(defvar sh-find-file-modifies t
138 "*What to do when newly found file has no magic number:
139 nil do nothing
140 t insert magic number
141 other insert magic number, but mark as unmodified.")
142
143
144(defvar sh-query-for-magic t
145 "*If non-nil, ask user before changing or inserting magic number.")
146
147
148(defvar sh-magicless-file-regexp "/\\.[^/]+$"
149 "*On files with this kind of name no magic is inserted or changed.")
150
151
152;; someone who understands /etc/magic better than me should beef this up
153;; this currently covers only SCO Unix and Sinix executables
154;; the elegant way would be to read /etc/magic
155(defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode)
156 ("#!.*perl" . perl-mode))
157 "A regexp to match the magic number of a found file.
158Currently this is only used by function `sh-or-other-mode'.")
159
160
161(defvar sh-executable ".* is \\([^ \t]*\\)\n"
162 "*Regexp to match the output of sh builtin `type' command on your machine.
163The regexp must match the whole output, and must contain a \\(something\\)
164construct which matches the actual executable.")
165
166
167
168(defvar sh-chmod-argument "755"
169 "*After saving, if the file is not executable, set this mode.
170The mode can be absolute \"511\" or relative \"u+x\". Do nothing if this is nil.")
171
172
173(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
174 "*The executable of the shell being programmed.")
175
176(defvar sh-shell-argument nil
177 "*A single argument for the magic number, or nil.")
178
179(defvar sh-shell nil
180 "The shell being programmed. This is set by \\[sh-set-shell].")
181
182(defvar sh-shell-is-csh nil
183 "The shell being programmed. This is set by \\[sh-set-shell].")
184
185(defvar sh-tab-width 4
186 "The default value for `tab-width' in Shell-Script mode.
187This is the width of tab stops after the indentation of the preceeding line.")
188
189(defvar sh-remember-variable-min 3
190 "*Don't remember variables less than this length for completing reads.")
191
192
193(defvar sh-beginning-of-command
194 "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)"
195 "*Regexp to determine the beginning of a shell command.
196The actual command starts at the beginning of the second \\(grouping\\).")
197
198(defvar sh-end-of-command
199 "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
200 "*Regexp to determine the end of a shell command.
201The actual command ends at the end of the first \\(grouping\\).")
202
203
204
205(defvar sh-assignment-space '(csh tcsh)
206 "List of shells that allow spaces around the assignment =.")
207
208(defvar sh-here-document-word "+"
209 "Word to delimit here documents.")
210
211
212;process-environment
213(defvar sh-variables
214 '(("addsuffix" tcsh) ("allow_null_glob_expansion" bash)
215 ("ampm" tcsh) ("argv" csh tcsh)
216 ("autocorrect" tcsh) ("autoexpand" tcsh)
217 ("autolist" tcsh) ("autologout" tcsh)
218 ("auto_resume" bash) ("BASH" bash)
219 ("BASH_VERSION" bash) ("cdable_vars" bash)
220 ("cdpath" csh tcsh) ("CDPATH" sh ksh bash)
221 ("chase_symlinks" tcsh) ("child" csh tcsh)
222 ("COLUMNS" ksh tcsh) ("correct" tcsh)
223 ("dextract" tcsh) ("echo" csh tcsh)
224 ("edit" tcsh) ("EDITOR")
225 ("el" tcsh) ("ENV" ksh bash)
226 ("ERRNO" ksh) ("EUID" bash)
227 ("FCEDIT" ksh bash) ("FIGNORE" bash)
228 ("fignore" tcsh) ("FPATH" ksh)
229 ("gid" tcsh) ("glob_dot_filenames" bash)
230 ("histchars" bash csh tcsh) ("HISTFILE" ksh bash)
231 ("HISTFILESIZE" bash) ("histlit" tcsh)
232 ("history" csh tcsh) ("history_control" bash)
233 ("HISTSIZE" bash) ("home" csh tcsh)
234 ("HOME") ("HOST" tcsh)
235 ("hostname_completion_file" bash) ("HOSTTYPE" bash tcsh)
236 ("HPATH" tcsh) ("HUSHLOGIN")
237 ("IFS" sh ksh bash) ("ignoreeof" bash csh tcsh)
238 ("IGNOREEOF" bash) ("ignore_symlinks" tcsh)
239 ("LANG") ("LC_COLLATE")
240 ("LC_CTYPE") ("LC_MESSAGES")
241 ("LC_MONETARY") ("LC_NUMERIC")
242 ("LC_TIME") ("LINENO" ksh bash)
243 ("LINES" ksh tcsh) ("listjobs" tcsh)
244 ("listlinks" tcsh) ("listmax" tcsh)
245 ("LOGNAME") ("mail" csh tcsh)
246 ("MAIL") ("MAILCHECK")
247 ("MAILPATH") ("MAIL_WARNING" bash)
248 ("matchbeep" tcsh) ("nobeep" tcsh)
249 ("noclobber" bash csh tcsh) ("noglob" csh tcsh)
250 ("nolinks" bash) ("nonomatch" csh tcsh)
251 ("NOREBIND" tcsh) ("notify" bash)
252 ("no_exit_on_failed_exec" bash) ("NO_PROMPT_VARS" bash)
253 ("oid" tcsh) ("OLDPWD" ksh bash)
254 ("OPTARG" sh ksh bash) ("OPTERR" bash)
255 ("OPTIND" sh ksh bash) ("PAGER")
256 ("path" csh tcsh) ("PATH")
257 ("PPID" ksh bash) ("printexitvalue" tcsh)
258 ("prompt" csh tcsh) ("prompt2" tcsh)
259 ("prompt3" tcsh) ("PROMPT_COMMAND" bash)
260 ("PS1" sh ksh bash) ("PS2" sh ksh bash)
261 ("PS3" ksh) ("PS4" ksh bash)
262 ("pushdsilent" tcsh) ("pushdtohome" tcsh)
263 ("pushd_silent" bash) ("PWD" ksh bash)
264 ("RANDOM" ksh bash) ("recexact" tcsh)
265 ("recognize_only_executables" tcsh) ("REPLY" ksh bash)
266 ("rmstar" tcsh) ("savehist" tcsh)
267 ("SECONDS" ksh bash) ("shell" csh tcsh)
268 ("SHELL") ("SHLVL" bash tcsh)
269 ("showdots" tcsh) ("sl" tcsh)
270 ("status" csh tcsh) ("SYSTYPE" tcsh)
271 ("tcsh" tcsh) ("term" tcsh)
272 ("TERM") ("TERMCAP")
273 ("time" csh tcsh) ("TMOUT" ksh bash)
274 ("tperiod" tcsh) ("tty" tcsh)
275 ("UID" bash) ("uid" tcsh)
276 ("verbose" csh tcsh) ("version" tcsh)
277 ("visiblebell" tcsh) ("VISUAL")
278 ("watch" tcsh) ("who" tcsh)
279 ("wordchars" tcsh))
280 "Alist of all environment and shell variables used for completing read.
281Variables only understood by some shells are associated to a list of those.")
282
283
284
285(defvar sh-font-lock-keywords
286 '(("[ \t]\\(#.*\\)" 1 font-lock-comment-face)
287 ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face))
288 "*Rules for highlighting shell scripts.
289This variable is included into the various variables
290`sh-SHELL-font-lock-keywords'. If no such variable exists for some shell,
291this one is used.")
292
293
294(defvar sh-sh-font-lock-keywords
295 (append sh-font-lock-keywords
296 '(("\\(^\\|[^-._a-z0-9]\\)\\(case\\|do\\|done\\|elif\\|else\\|esac\\|fi\\|for\\|if\\|in\\|then\\|until\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
297 "*Rules for highlighting Bourne shell scripts.")
298
299(defvar sh-ksh-font-lock-keywords
300 (append sh-sh-font-lock-keywords
301 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
302 "*Rules for highlighting Korn shell scripts.")
303
304(defvar sh-bash-font-lock-keywords
305 (append sh-sh-font-lock-keywords
306 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
307 "*Rules for highlighting Bourne again shell scripts.")
308
309
310(defvar sh-csh-font-lock-keywords
311 (append sh-font-lock-keywords
312 '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
313 "*Rules for highlighting C shell scripts.")
314
315(defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
316 "*Rules for highlighting Toronto C shell scripts.")
317
318
319
320;; mode-command and utility functions
321
322;;;###autoload
323(defun sh-or-other-mode ()
324 "Decide whether this is a compiled executable or a script.
325Usually the file-names of scripts and binaries cannot be automatically
326distinguished, so the presence of an executable's magic number is used."
327 (funcall (or (let ((l magic-number-alist))
328 (while (and l
329 (not (looking-at (car (car l)))))
330 (setq l (cdr l)))
331 (cdr (car l)))
332 'sh-mode)))
333
334
335;;;###autoload
336(defun sh-mode ()
337 "Major mode for editing shell scripts.
338This mode works for many shells, since they all have roughly the same syntax,
339as far as commands, arguments, variables, pipes, comments etc. are concerned.
340Unless the file's magic number indicates the shell, your usual shell is
341assumed. Since filenames rarely give a clue, they are not further analyzed.
342
343The syntax of the statements varies with the shell being used. The syntax of
344statements can be modified by putting a property on the command or new ones
345defined with `define-sh-skeleton'. For example
346
347 (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
348or
349 (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
350
351where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
352
353The following commands are available, based on the current shell's syntax:
354
355\\[sh-case] case statement
356\\[sh-for] for loop
357\\[sh-function] function definition
358\\[sh-if] if statement
359\\[sh-indexed-loop] indexed loop from 1 to n
360\\[sh-select] select statement
361\\[sh-until] until loop
362\\[sh-while] while loop
363
364\\[backward-delete-char-untabify] Delete backward one position, even if it was a tab.
365\\[sh-newline-and-indent] Delete unquoted space and indent new line same as this one.
366\\[sh-end-of-command] Go to end of successive commands.
367\\[sh-beginning-of-command] Go to beginning of successive commands.
368\\[sh-set-shell] Set this buffer's shell, and maybe its magic number.
369\\[sh-manual-entry] Display the Unix manual entry for the current command or shell.
370
371\\[sh-query-for-variable] Unless quoted with \\, query for a variable with completions offered.
372\\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document.
373{, (, [, ', \", `
374 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
375 (interactive)
376 (kill-all-local-variables)
377 (set-syntax-table sh-mode-syntax-table)
378 (use-local-map sh-mode-map)
379 (make-local-variable 'indent-line-function)
380 (make-local-variable 'comment-start)
381 (make-local-variable 'comment-start-skip)
382 (make-local-variable 'after-save-hook)
383 (make-local-variable 'require-final-newline)
384 (make-local-variable 'sh-shell-path)
385 (make-local-variable 'sh-shell)
386 (make-local-variable 'sh-shell-is-csh)
387 (make-local-variable 'pair-alist)
388 (make-local-variable 'pair-filter)
389 (make-local-variable 'font-lock-keywords)
390 (make-local-variable 'font-lock-keywords-case-fold-search)
391 (make-local-variable 'sh-variables)
392 (setq major-mode 'sh-mode
393 mode-name "Shell-script"
394 ;; Why can't Emacs have one standard function with some parameters?
395 ;; Only few modes actually analyse the previous line's contents
396 indent-line-function 'sh-indent-line
397 comment-start "# "
398 after-save-hook 'sh-chmod
399 tab-width sh-tab-width
400 ;; C shells do
401 require-final-newline t
402 font-lock-keywords-case-fold-search nil
403 pair-alist '((?` _ ?`))
404 pair-filter 'sh-quoted-p)
405 ; parse or insert magic number for exec()
406 (goto-char (point-min))
407 (sh-set-shell
408 (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
409 (buffer-substring (match-beginning 1) (match-end 1))
410 sh-shell-path))
411 ;; find-file is set by `normal-mode' when called by `after-find-file'
412 (and (boundp 'find-file) find-file
413 (or (eq sh-find-file-modifies t)
414 (set-buffer-modified-p nil)))
415 (run-hooks 'sh-mode-hook))
416;;;###autoload
417(defalias 'shell-script-mode 'sh-mode)
418
419
420
421(defmacro define-sh-skeleton (command documentation &rest definitions)
422 "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ...
423Prior definitions (e.g. from ~/.emacs) are maintained.
424Each definition is built up as (SHELL PROMPT ELEMENT ...). Alternately
425a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
426
427For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
428Each DEFINITION is actually stored as
429 (put COMMAND SHELL (PROMPT ELEMENT ...)),
430which you can also do yourself."
431 (or (stringp documentation)
432 (setq definitions (cons documentation definitions)
433 documentation ""))
434 ;; The compiled version doesn't.
435 (require 'backquote)
436 (`(progn
437 (let ((definitions '(, definitions)))
438 (while definitions
439 ;; skeleton need not be loaded to define these
440 (or (and (not (if (boundp 'skeleton-debug) skeleton-debug))
441 (get '(, command) (car (car definitions))))
442 (put '(, command) (car (car definitions))
443 (if (symbolp (cdr (car definitions)))
444 (get '(, command) (cdr (car definitions)))
445 (cdr (car definitions)))))
446 (setq definitions (cdr definitions))))
447 (put '(, command) 'menu-enable '(get '(, command) sh-shell))
448 (defun (, command) ()
449 (, documentation)
450 (interactive)
451 (skeleton-insert
452 (or (get '(, command) sh-shell)
453 (error "%s statement syntax not defined for shell %s."
454 '(, command) sh-shell)))))))
455
456
457
458(defun sh-indent-line ()
459 "Indent as far as preceding line, then by steps of `tab-width'.
460If previous line starts with a comment, it's considered empty."
461 (interactive)
462 (let ((previous (save-excursion
463 (line-move -1)
464 (back-to-indentation)
465 (if (looking-at comment-start-skip)
466 0
467 (current-column)))))
468 (save-excursion
469 (indent-to (if (eq this-command 'newline-and-indent)
470 previous
471 (if (< (current-column)
472 (progn (back-to-indentation)
473 (current-column)))
474 (if (eolp) previous 0)
475 (if (eolp)
476 (max previous (* (1+ (/ (current-column) tab-width))
477 tab-width))
478 (* (1+ (/ (current-column) tab-width)) tab-width))))))
479 (if (< (current-column) (current-indentation))
480 (skip-chars-forward " \t"))))
481
482
483(defun sh-remember-variable (var)
484 "Make VARIABLE available for future completing reads in this buffer."
485 (or (< (length var) sh-remember-variable-min)
486 (assoc var sh-variables)
487 (setq sh-variables (cons (list var) sh-variables)))
488 var)
489
490
491;; Augment the standard variables by those found in the environment.
492(if (boundp 'process-environment)(let ((l process-environment))
493 (while l
494 (sh-remember-variable (substring (car l)
495 0 (string-match "=" (car l))))
496 (setq l (cdr l)))))
497
498
499
500(defun sh-quoted-p ()
501 "Is point preceded by an odd number of backslashes?"
502 (eq 1 (% (- (point) (save-excursion
503 (skip-chars-backward "\\\\")
504 (point)))
505 2)))
506
507
508
509(defun sh-executable (command)
510 "If COMMAND is an executable in $PATH its full name is returned. Else nil."
511 (let ((point (point))
512 (buffer-modified-p (buffer-modified-p))
513 buffer-read-only after-change-function)
514 (call-process "sh" nil t nil "-c" (concat "type " command))
515 (setq point (prog1 (point)
516 (goto-char point)))
517 (prog1
518 (and (looking-at sh-executable)
519 (eq point (match-end 0))
520 (buffer-substring (match-beginning 1) (match-end 1)))
521 (delete-region (point) point)
522 (set-buffer-modified-p buffer-modified-p))))
523
524
525
526(defun sh-chmod ()
527 "This gets called after saving a file to assure that it be executable.
528You can set the absolute or relative mode with `sh-chmod-argument'."
529 (if sh-chmod-argument
530 (or (file-executable-p buffer-file-name)
531 (shell-command (concat "chmod " sh-chmod-argument
532 " " buffer-file-name)))))
533
534;; statement syntax-commands for various shells
535
536;; You are welcome to add the syntax or even completely new statements as
537;; appropriate for your favorite shell.
538
539(define-sh-skeleton sh-case
540 "Insert a case/switch statement in the current shell's syntax."
541 (sh "expression: "
542 "case " str " in" \n
543 > (read-string "pattern: ") ?\) \n
544 > _ \n
545 ";;" \n
546 ( "other pattern, %s: "
547 < str ?\) \n
548 > \n
549 ";;" \n)
550 < "*)" \n
551 > \n
552 resume:
553 < < "esac")
554 (ksh . sh)
555 (bash . sh)
556 (csh "expression: "
557 "switch( " str " )" \n
558 > "case " (read-string "pattern: ") ?: \n
559 > _ \n
560 "breaksw" \n
561 ( "other pattern, %s: "
562 < "case " str ?: \n
563 > \n
564 "breaksw" \n)
565 < "default:" \n
566 > \n
567 resume:
568 < < "endsw")
569 (tcsh . csh))
570
571
572
573(define-sh-skeleton sh-for
574 "Insert a for loop in the current shell's syntax."
575 (sh "Index variable: "
576 "for " str " in " _ "; do" \n
577 > ?$ (sh-remember-variable str) \n
578 < "done")
579 (ksh . sh)
580 (bash . sh)
581 (csh "Index variable: "
582 "foreach " str " ( " _ " )" \n
583 > ?$ (sh-remember-variable str) \n
584 < "end")
585 (tcsh . csh))
586
587
588
589(define-sh-skeleton sh-indexed-loop
590 "Insert an indexed loop from 1 to n in the current shell's syntax."
591 (sh "Index variable: "
592 str "=1" \n
593 "while [ $" str " -le "
594 (read-string "upper limit: ")
595 " ]; do" \n
596 > _ ?$ str \n
597 str ?= (sh-add (sh-remember-variable str) 1) \n
598 < "done")
599 (ksh . sh)
600 (bash . sh)
601 (csh "Index variable: "
602 "@ " str " = 1" \n
603 "while( $" str " <= "
604 (read-string "upper limit: ")
605 " )" \n
606 > _ ?$ (sh-remember-variable str) \n
607 "@ " str "++" \n
608 < "end")
609 (tcsh . csh))
610
611
612
613(defun sh-add (var delta)
614 "Insert an addition of VAR and prefix DELTA for Bourne type shells."
615 (interactive
616 (list (sh-remember-variable
617 (completing-read "Variable: " sh-variables
618 (lambda (element)
619 (or (not (cdr element))
620 (memq sh-shell (cdr element))))))
621 (prefix-numeric-value current-prefix-arg)))
622 (setq delta (concat (if (< delta 0) " - " " + ")
623 (abs delta)))
624 (skeleton-insert
625 (assq sh-shell
626 '((sh "`expr $" var delta "`")
627 (ksh "$(( $" var delta " ))")
628 (bash "$[ $" var delta " ]")))
629 t))
630
631
632
633(define-sh-skeleton sh-function
634 "Insert a function definition in the current shell's syntax."
635 (sh ()
636 "() {" \n
637 > _ \n
638 < "}")
639 (ksh "name: "
640 "function " str " {" \n
641 > _ \n
642 < "}")
643 (bash "name: "
644 "function " str "() {" \n
645 > _ \n
646 < "}"))
647
648
649
650(define-sh-skeleton sh-if
651 "Insert an if statement in the current shell's syntax."
652 (sh "condition: "
653 "if [ " str " ]; then" \n
654 > _ \n
655 ( "other condition, %s: "
656 < "elif [ " str " ]; then" \n
657 > \n)
658 < "else" \n
659 > \n
660 resume:
661 < "fi")
662 (ksh . sh)
663 (bash . sh)
664 (csh "condition: "
665 "if( " str " ) then" \n
666 > _ \n
667 ( "other condition, %s: "
668 < "else if ( " str " ) then" \n
669 > \n)
670 < "else" \n
671 > \n
672 resume:
673 < "endif")
674 (tcsh . csh))
675
676
677
678(define-sh-skeleton sh-select
679 "Insert a select statement in the current shell's syntax."
680 (ksh "Index variable: "
681 "select " str " in " _ "; do" \n
682 > ?$ str \n
683 < "done"))
684(put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
685
686
687
688(define-sh-skeleton sh-until
689 "Insert an until loop in the current shell's syntax."
690 (sh "condition: "
691 "until [ " str " ]; do" \n
692 > _ \n
693 < "done")
694 (ksh . sh)
695 (bash . sh))
696(put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
697
698
699(define-sh-skeleton sh-while
700 "Insert a while loop in the current shell's syntax."
701 (sh "condition: "
702 "while [ " str " ]; do" \n
703 > _ \n
704 < "done")
705 (ksh . sh)
706 (bash . sh)
707 (csh "condition: "
708 "while( " str " )" \n
709 > _ \n
710 < "end")
711 (tcsh . csh))
712
713
714
715(defun sh-query-for-variable (arg)
716 "Unless quoted with `\\', query for variable-name with completions.
717Prefix arg 0 means don't insert `$' before the variable.
718Prefix arg 2 or more means only do self-insert that many times.
719 If { is pressed as the first character, it will surround the variable name."
720 (interactive "*p")
721 (or (prog1 (or (> arg 1)
722 (sh-quoted-p))
723 (self-insert-command arg))
724 (let (completion-ignore-case
725 (minibuffer-local-completion-map
726 (or (get 'sh-query-for-variable 'keymap)
727 (put 'sh-query-for-variable 'keymap
728 (copy-keymap minibuffer-local-completion-map))))
729 (buffer (current-buffer)))
730 ;; local function that depends on `arg' and `buffer'
731 (define-key minibuffer-local-completion-map "{"
732 (lambda () (interactive)
733 (if (or arg (> (point) 1))
734 (beep)
735 (save-window-excursion
736 (setq arg t)
737 (switch-to-buffer-other-window buffer)
738 (insert "{}")))))
739 (insert
740 (prog1
741 (sh-remember-variable
742 (completing-read "Variable: " sh-variables
743 (lambda (element)
744 (or (not (cdr element))
745 (memq sh-shell (cdr element))))))
746 (if (eq t arg) (forward-char 1))))
747 (if (eq t arg) (forward-char 1)))))
748
749
750
751(defun sh-assignment (arg)
752 "Insert self. Remember previous identifier for future completing read."
753 (interactive "p")
754 (if (eq arg 1)
755 (sh-remember-variable
756 (save-excursion
757 (buffer-substring
758 (progn
759 (if (memq sh-shell sh-assignment-space)
760 (skip-chars-backward " \t"))
761 (point))
762 (progn
763 (skip-chars-backward "a-zA-Z0-9_")
764 (point))))))
765 (self-insert-command arg))
766
767
768
769(defun sh-maybe-here-document (arg)
770 "Inserts self. Without prefix, following unquoted `<' inserts here document.
771The document is bounded by `sh-here-document-word'."
772 (interactive "*P")
773 (self-insert-command (prefix-numeric-value arg))
774 (or arg
775 (not (eq (char-after (- (point) 2)) last-command-char))
776 (save-excursion
777 (goto-char (- (point) 2))
778 (sh-quoted-p))
779 (progn
780 (insert sh-here-document-word)
781 (or (looking-at "[ \t\n]") (insert ? ))
782 (end-of-line 1)
783 (newline)
784 (save-excursion (insert ?\n sh-here-document-word)))))
785
786
787;; various other commands
788
789(autoload 'comint-dynamic-complete-filename "comint"
790 "Dynamically complete the filename at point." t)
791
792
793
794(defun sh-newline-and-indent (&optional arg)
795 "Strip unquoted whitespace, insert newline, and indent like current line.
796Unquoted whitespace is stripped from the current line's end, unless a
797prefix ARG is given."
798 (interactive "*P")
799 (let ((previous (current-indentation))
800 (end-of-line (point)))
801 (if arg ()
802 (skip-chars-backward " \t")
803 (and (< (point) end-of-line)
804 (sh-quoted-p)
805 (forward-char 1))
806 (delete-region (point) end-of-line))
807 (newline)
808 (indent-to previous)))
809
810
811
812(defun sh-set-shell (shell)
813 "Set this buffer's shell to SHELL (a string).
814Calls the value of `sh-set-shell-hook' if set."
815 (interactive "sName or path of shell: ")
816 (save-excursion
817 (goto-char (point-min))
818 (setq sh-shell-path (if (file-name-absolute-p shell)
819 shell
820 (or (sh-executable shell)
821 (error "Cannot find %s." shell)))
822 sh-shell (intern (file-name-nondirectory sh-shell-path))
823 sh-shell-is-csh (memq sh-shell '(csh tcsh))
824 font-lock-keywords
825 (intern-soft (format "sh-%s-font-lock-keywords" sh-shell))
826 font-lock-keywords (if (and font-lock-keywords
827 (boundp font-lock-keywords))
828 (symbol-value font-lock-keywords)
829 sh-font-lock-keywords)
830 comment-start-skip (if sh-shell-is-csh
831 "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*"
832 "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*")
833 mode-line-process (format ": %s" sh-shell)
834 shell (concat sh-shell-path
835 (and sh-shell-argument " ")
836 sh-shell-argument))
837 (and (not buffer-read-only)
838 (not (if buffer-file-name
839 (string-match sh-magicless-file-regexp buffer-file-name)))
840 ;; find-file is set by `normal-mode' when called by `after-find-file'
841 (if (and (boundp 'find-file) find-file) sh-find-file-modifies t)
842 (if (looking-at "#!")
843 (and (skip-chars-forward "#! \t")
844 (not (string= shell
845 (buffer-substring (point)
846 (save-excursion (end-of-line)
847 (point)))))
848 (if sh-query-for-magic
849 (y-or-n-p (concat "Replace magic number by ``#! "
850 shell "''? "))
851 (message "Magic number ``%s'' replaced."
852 (buffer-substring (point-min) (point))))
853 (not (delete-region (point) (progn (end-of-line) (point))))
854 (insert shell))
855 (insert "#! " shell ?\n))))
856 (run-hooks 'sh-set-shell-hook))
857
858
859
860(defun sh-beginning-of-command ()
861 "Move point to successive beginnings of commands."
862 (interactive)
863 (if (re-search-backward sh-beginning-of-command nil t)
864 (goto-char (match-beginning 2))))
865
866
867
868(defun sh-end-of-command ()
869 "Move point to successive ends of commands."
870 (interactive)
871 (if (re-search-forward sh-end-of-command nil t)
872 (goto-char (match-end 1))))
873
874
875
876(defun sh-manual-entry (arg)
877 "Display the Unix manual entry for the current command or shell.
878Universal argument ARG, is passed to `Man-getpage-in-background'."
879 (interactive "P")
880 (let ((command (save-excursion
881 (sh-beginning-of-command)
882 (sh-executable
883 (buffer-substring (point)
884 (progn (forward-sexp) (point)))))))
885 (setq command (read-input (concat "Manual entry (default "
886 (symbol-name sh-shell)
887 "): ")
888 (if command
889 (file-name-nondirectory command))))
890 (manual-entry (if (string= command "")
891 (symbol-name sh-shell)
892 command)
893 arg)))
894
895;; sh-script.el ends here
diff --git a/lisp/skeleton.el b/lisp/skeleton.el
new file mode 100644
index 00000000000..548644f0fe4
--- /dev/null
+++ b/lisp/skeleton.el
@@ -0,0 +1,328 @@
1;;; skeleton.el --- Metalanguage for writing statement skeletons
2;; Copyright (C) 1993 by Free Software Foundation, Inc.
3
4;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
5;; Maintainer: FSF
6;; Keywords: shell programming
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to
22;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24;;; Commentary:
25
26;; A very concise metalanguage for writing structured statement
27;; skeleton insertion commands for programming language modes. This
28;; originated in shell-script mode and was applied to ada-mode's
29;; commands which shrunk to one third. And these commands are now
30;; user configurable.
31
32;;; Code:
33
34;; page 1: statement skeleton metalanguage definition & interpreter
35;; page 2: paired insertion
36;; page 3: mirror-mode, an example for setting up paired insertion
37
38
39(defvar skeleton-transformation nil
40 "*If non-nil, function applied to strings before they are inserted.
41It should take strings and characters and return them transformed, or nil
42which means no transformation.
43Typical examples might be `upcase' or `capitalize'.")
44
45; this should be a fourth argument to defvar
46(put 'skeleton-transformation 'variable-interactive
47 "aTransformation function: ")
48
49
50
51(defvar skeleton-subprompt
52 (substitute-command-keys
53 "RET, \\<minibuffer-local-map>\\[abort-recursive-edit] or \\[help-command]")
54 "*Replacement for %s in prompts of recursive skeleton definitions.")
55
56
57
58(defvar skeleton-debug nil
59 "*If non-nil `define-skeleton' will override previous definition.")
60
61
62
63;;;###autoload
64(defmacro define-skeleton (command documentation &rest definition)
65 "Define a user-configurable COMMAND that enters a statement skeleton.
66DOCUMENTATION is that of the command, while the variable of the same name,
67which contains the definition, has a documentation to that effect.
68PROMPT and ELEMENT ... are as defined under `skeleton-insert'."
69 (if skeleton-debug
70 (set command definition))
71 (require 'backquote)
72 (`(progn
73 (defvar (, command) '(, definition)
74 (, (concat "*Definition for the "
75 (symbol-name command)
76 " skeleton command.
77See function `skeleton-insert' for meaning.")) )
78 (defun (, command) ()
79 (, documentation)
80 (interactive)
81 ;; Don't use last-command to guarantee command does the same thing,
82 ;; whatever other name it is given.
83 (skeleton-insert (, command))))))
84
85
86
87;;;###autoload
88(defun skeleton-insert (definition &optional no-newline)
89 "Insert the complex statement skeleton DEFINITION describes very concisely.
90If optional NO-NEWLINE is nil the skeleton will end on a line of its own.
91
92DEFINITION is made up as (PROMPT ELEMENT ...). PROMPT may be nil if not
93needed, a prompt-string or an expression for complex read functions.
94
95If ELEMENT is a string or a character it gets inserted (see also
96`skeleton-transformation'). Other possibilities are:
97
98 \\n go to next line and align cursor
99 > indent according to major mode
100 < undent tab-width spaces but not beyond beginning of line
101 _ cursor after termination
102 & skip next ELEMENT if previous didn't move point
103 | skip next ELEMENT if previous moved point
104 -num delete num preceding characters
105 resume: skipped, continue here if quit is signaled
106 nil skipped
107
108ELEMENT may itself be DEFINITION with a PROMPT. The user is prompted
109repeatedly for different inputs. The DEFINITION is processed as often
110as the user enters a non-empty string. \\[keyboard-quit] terminates
111skeleton insertion, but continues after `resume:' and positions at `_'
112if any. If PROMPT in such a sub-definition contains a \".. %s ..\" it
113is replaced by `skeleton-subprompt'.
114
115Other lisp-expressions are evaluated and the value treated as above.
116The following local variables are available:
117
118 str first time: read a string prompting with PROMPT and insert it
119 if PROMPT is not a string it is evaluated instead
120 then: insert previously read string once more
121 quit non-nil when resume: section is entered by keyboard quit
122 v1, v2 local variables for memorising anything you want"
123 (let (modified opoint point resume: quit v1 v2)
124 (skeleton-internal-list definition (car definition))
125 (or no-newline
126 (eolp)
127 (newline)
128 (indent-relative t))
129 (if point
130 (goto-char point))))
131
132
133
134(defun skeleton-internal-read (str)
135 (let ((minibuffer-help-form "\
136As long as you provide input you will insert another subskeleton.
137
138If you enter the empty string, the loop inserting subskeletons is
139left, and the current one is removed as far as it has been entered.
140
141If you quit, the current subskeleton is removed as far as it has been
142entered. No more of the skeleton will be inserted, except maybe for a
143syntactically necessary termination."))
144 (setq str (if (stringp str)
145 (read-string
146 (format str skeleton-subprompt))
147 (eval str))))
148 (if (string= str "")
149 (signal 'quit t)
150 str))
151
152
153(defun skeleton-internal-list (definition &optional str recursive start line)
154 (condition-case quit
155 (progn
156 (setq start (save-excursion (beginning-of-line) (point))
157 column (current-column)
158 line (buffer-substring start
159 (save-excursion (end-of-line) (point)))
160 str (list 'setq 'str
161 (if recursive
162 (list 'skeleton-internal-read (list 'quote str))
163 (list (if (stringp str)
164 'read-string
165 'eval)
166 str))))
167 (while (setq modified (eq opoint (point))
168 opoint (point)
169 definition (cdr definition))
170 (skeleton-internal-1 (car definition)))
171 ;; maybe continue loop
172 recursive)
173 (quit ;; remove the subskeleton as far as it has been shown
174 (if (eq (cdr quit) 'recursive)
175 ()
176 ;; the subskeleton shouldn't have deleted outside current line
177 (end-of-line)
178 (delete-region start (point))
179 (insert line)
180 (move-to-column column))
181 (if (eq (cdr quit) t)
182 ;; empty string entered
183 nil
184 (while (if definition
185 (not (eq (car (setq definition (cdr definition)))
186 'resume:))))
187 (if definition
188 (skeleton-internal-list definition)
189 ;; propagate signal we can't handle
190 (if recursive (signal 'quit 'recursive)))))))
191
192
193
194(defun skeleton-internal-1 (element)
195 (cond ( (and (integerp element)
196 (< element 0))
197 (delete-char element))
198 ( (char-or-string-p element)
199 (insert (if skeleton-transformation
200 (funcall skeleton-transformation element)
201 element)) )
202 ( (eq element '\n) ; actually (eq '\n 'n)
203 (newline)
204 (indent-relative t) )
205 ( (eq element '>)
206 (indent-for-tab-command) )
207 ( (eq element '<)
208 (backward-delete-char-untabify (min tab-width (current-column))) )
209 ( (eq element '_)
210 (or point
211 (setq point (point))) )
212 ( (eq element '&)
213 (if modified
214 (setq definition (cdr definition))) )
215 ( (eq element '|)
216 (or modified
217 (setq definition (cdr definition))) )
218 ( (if (consp element)
219 (or (stringp (car element))
220 (consp (car element))))
221 (while (skeleton-internal-list element (car element) t)) )
222 ( (null element) )
223 ( (skeleton-internal-1 (eval element)) )))
224
225
226;; variables and command for automatically inserting pairs like () or ""
227
228(defvar pair nil
229 "*If this is nil pairing is turned off, no matter what else is set.
230Otherwise modes with `pair-insert-maybe' on some keys will attempt this.")
231
232
233(defvar pair-on-word nil
234 "*If this is nil pairing is not attempted before or inside a word.")
235
236
237(defvar pair-filter (lambda ())
238 "Attempt pairing if this function returns nil, before inserting.
239This allows for context-sensitive checking whether pairing is appropriate.")
240
241
242(defvar pair-alist ()
243 "An override alist of pairing partners matched against
244`last-command-char'. Each alist element, which looks like (ELEMENT
245...), is passed to `skeleton-insert' with no prompt. Variable `str'
246does nothing.
247
248Elements might be (?` ?` _ \"''\"), (?\\( ? _ \" )\") or (?{ \\n > _ \\n < ?}).")
249
250
251
252;;;###autoload
253(defun pair-insert-maybe (arg)
254 "Insert the character you type ARG times.
255
256With no ARG, if `pair' is non-nil, and if
257`pair-on-word' is non-nil or we are not before or inside a
258word, and if `pair-filter' returns nil, pairing is performed.
259
260If a match is found in `pair-alist', that is inserted, else
261the defaults are used. These are (), [], {}, <> and `' for the
262symmetrical ones, and the same character twice for the others."
263 (interactive "*P")
264 (if (or arg
265 (not pair)
266 (if (not pair-on-word) (looking-at "\\w"))
267 (funcall pair-filter))
268 (self-insert-command (prefix-numeric-value arg))
269 (insert last-command-char)
270 (if (setq arg (assq last-command-char pair-alist))
271 ;; typed char is inserted, and car means no prompt
272 (skeleton-insert arg t)
273 (save-excursion
274 (insert (or (cdr (assq last-command-char
275 '((?( . ?))
276 (?[ . ?])
277 (?{ . ?})
278 (?< . ?>)
279 (?` . ?'))))
280 last-command-char))))))
281
282
283
284;;;###autoload
285;; a more serious example can be found in shell-script.el
286(defun mirror-mode ()
287 "This major mode is an amusing little example of paired insertion.
288All printable characters do a paired self insert, while the other commands
289work normally."
290 (interactive)
291 (kill-all-local-variables)
292 (make-local-variable 'pair)
293 (make-local-variable 'pair-on-word)
294 (make-local-variable 'pair-filter)
295 (make-local-variable 'pair-alist)
296 (setq major-mode 'mirror-mode
297 mode-name "Mirror"
298 pair-on-word t
299 ;; in the middle column insert one or none if odd window-width
300 pair-filter (lambda ()
301 (if (>= (current-column)
302 (/ (window-width) 2))
303 ;; insert both on next line
304 (next-line 1)
305 ;; insert one or both?
306 (= (* 2 (1+ (current-column)))
307 (window-width))))
308 ;; mirror these the other way round as well
309 pair-alist '((?) _ ?()
310 (?] _ ?[)
311 (?} _ ?{)
312 (?> _ ?<)
313 (?/ _ ?\\)
314 (?\\ _ ?/)
315 (?` ?` _ "''")
316 (?' ?' _ "``"))
317 ;; in this mode we exceptionally ignore the user, else it's no fun
318 pair t)
319 (let ((map (make-keymap))
320 (i ? ))
321 (use-local-map map)
322 (setq map (car (cdr map)))
323 (while (< i ?\^?)
324 (aset map i 'pair-insert-maybe)
325 (setq i (1+ i))))
326 (run-hooks 'mirror-mode-hook))
327
328;; skeleton.el ends here