(skeleton-end-hook): Now defvared and responsible for
[bpt/emacs.git] / lisp / progmodes / sh-script.el
CommitLineData
ac59aed8 1;;; sh-script.el --- shell-script editing commands for Emacs
133693bc 2;; Copyright (C) 1993, 1994, 1995 by Free Software Foundation, Inc.
ac59aed8 3
133693bc
KH
4;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
5;; Version: 2.0d
ac59aed8 6;; Maintainer: FSF
133693bc 7;; Keywords: languages, unix
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
22;; along with GNU Emacs; see the file COPYING. If not, write to
23;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25;;; Commentary:
26
133693bc
KH
27;; Major mode for editing shell scripts. Bourne, C and rc shells as well
28;; as various derivatives are supported and easily derived from. Structured
29;; statements can be inserted with one command or abbrev. Completion is
30;; available for filenames, variables known from the script, the shell and
31;; the environment as well as commands.
ac59aed8 32
133693bc
KH
33;;; Known Bugs:
34
35;; - Since GNU Emacs' syntax can't handle the context-sensitive meanings of
36;; the variable/number base/comment symbol `#', that has to be fontified by
37;; regexp. This alas means that a quote `'' or `"' in a comment will
38;; fontify VERY badly. The alternative is to have these frequent constructs
39;; with `#' fontify as comments. Or maybe we intoduce a 'syntax text-
40;; property?
41;; - Variables in `"' strings aren't fontified because there's no way of
42;; syntactically distinguishing those from `'' strings.
e932f2d2 43
ac59aed8
RS
44;;; Code:
45
46;; page 1: variables and settings
47;; page 2: mode-command and utility functions
48;; page 3: statement syntax-commands for various shells
49;; page 4: various other commands
50
133693bc
KH
51(require 'executable)
52
53
4a789a51
RS
54
55;; Autoload cookie deleted here because it made loaddefs.el fail to load.
56;; -rms
133693bc
KH
57(or (assoc "sh" interpreter-mode-alist)
58 (setq auto-mode-alist
59 ;; matches files
60 ;; - that have a suffix .sh, .csh or .shar (shell archive)
61 ;; - that contain ressources for the various shells
62 ;; - startup files for X11
63 (cons '("\\.c?sh\\'\\|\\.shar\\'\\|/\\.\\(z?profile\\|bash_profile\\|z?login\\|bash_login\\|z?logout\\|bash_logout\\|[kz]shrc\\|bashrc\\|t?cshrc\\|esrc\\|rcrc\\|[kz]shenv\\|xinitrc\\|startxrc\\|xsession\\)\\'" . sh-mode)
64 auto-mode-alist)
65 interpreter-mode-alist
66 (nconc '(("ash" . sh-mode)
67 ("bash" . sh-mode)
68 ("csh" . sh-mode)
69 ("dtksh" . sh-mode)
70 ("es" . sh-mode)
71 ("itcsh" . sh-mode)
72 ("jsh" . sh-mode)
73 ("ksh" . sh-mode)
74 ("oash" . sh-mode)
75 ("pdksh" . sh-mode)
76 ("rc" . sh-mode)
77 ("sh" . sh-mode)
78 ("sh5" . sh-mode)
79 ("tcsh" . sh-mode)
80 ("wksh" . sh-mode)
81 ("wsh" . sh-mode)
82 ("zsh" . sh-mode))
83 interpreter-mode-alist)))
84
85
86(defvar sh-ancestor-alist
87 '((ash . sh)
88 (bash . jsh)
89 (dtksh . ksh)
90 (es . rc)
91 (itcsh . tcsh)
92 (jcsh . csh)
93 (jsh . sh)
94 (ksh . ksh88)
95 (ksh88 . jsh)
96 (oash . sh)
97 (pdksh . ksh88)
98 (posix . sh)
99 (tcsh . csh)
100 (wksh . ksh88)
101 (wsh . sh)
102 (zsh . ksh88))
103 "*Alist showing the direct ancestor of various shells.
104This is the basis for `sh-feature'. See also `sh-alias-alist'.
105By default we have the following three hierarchies:
106
107csh C Shell
108 jcsh C Shell with Job Control
109 tcsh Toronto C Shell
110 itcsh ? Toronto C Shell
111rc Plan 9 Shell
112 es Extensible Shell
113sh Bourne Shell
114 ash ? Shell
115 jsh Bourne Shell with Job Control
116 bash GNU Bourne Again Shell
117 ksh88 Korn Shell '88
118 ksh Korn Shell '93
119 dtksh CDE Desktop Korn Shell
120 pdksh Public Domain Korn Shell
121 wksh Window Korn Shell
122 zsh Z Shell
123 oash SCO OA (curses) Shell
124 posix IEEE 1003.2 Shell Standard
125 wsh ? Shell")
126
127
128(defvar sh-alias-alist
129 (nconc (if (eq system-type 'linux)
130 '((csh . tcsh)
131 (ksh . pdksh)
132 (sh . bash)))
133 ;; for the time being
134 '((ksh . ksh88)
135 (sh5 . sh)))
136 "*Alist for transforming shell names to what they really are.
137Use this where the name of the executable doesn't correspond to the type of
138shell it really is.")
139
140
141(defvar sh-shells
142 '(("ash") ("bash") ("csh") ("dtksh") ("es") ("itcsh") ("jsh") ("ksh")
143 ("oash") ("pdksh") ("rc") ("sh") ("tcsh") ("wksh") ("wsh") ("zsh"))
144 "*Alist of shells available for completing read in `sh-set-shell'.")
145
146
147(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
148 "*The executable of the shell being programmed.")
149
150
151(defvar sh-shell-arg
152 '((bash . "-norc")
153 (csh . "-f")
154 (ksh88 eval progn nil (if (file-exists-p "/etc/suid_profile") nil "-p"))
155 (pdksh)
156 (rc . "-p")
157 (wksh . "-motif")
158 (zsh . "-f"))
159 "*Single argument string for the magic number. See `sh-feature'.")
160
161
162
163(defvar sh-shell (or (cdr (assq (intern (file-name-nondirectory sh-shell-path))
164 sh-alias-alist))
165 (intern (file-name-nondirectory sh-shell-path)))
166 "The shell being programmed. This is set by \\[sh-set-shell].")
167
168
169
170(defvar sh-abbrevs
171 '((csh eval sh-abbrevs shell
225f6185
KH
172 "switch" 'sh-case
173 "getopts" 'sh-while-getopts)
133693bc
KH
174
175 (es eval sh-abbrevs shell
176 "function" 'sh-function)
177
178 (ksh88 eval sh-abbrevs sh
179 "select" 'sh-select)
180
181 (rc eval sh-abbrevs shell
182 "case" 'sh-case
183 "function" 'sh-function)
184
185 (sh eval sh-abbrevs shell
186 "case" 'sh-case
187 "function" 'sh-function
188 "until" 'sh-until
189 "getopts" 'sh-while-getopts)
190
191 ;; The next entry is only used for defining the others
192 (shell "for" sh-for
193 "loop" sh-indexed-loop
194 "if" sh-if
195 "tmpfile" sh-tmp-file
196 "while" sh-while)
197
198 (zsh eval sh-abbrevs ksh88
199 "repeat" 'sh-repeat))
200 "Abbrev-table used in Shell-Script mode. See `sh-feature'.
201Due to the internal workings of abbrev tables, the shell name symbol is
202actually defined as the table for the like of \\[edit-abbrevs].")
ac59aed8 203
ac59aed8
RS
204
205
206(defvar sh-mode-syntax-table
133693bc
KH
207 '((csh eval identity sh)
208 (sh eval sh-mode-syntax-table ()
209 ;; #'s meanings depend on context which can't be expressed here
210 ;; ?\# "<"
211 ;; ?\^l ">#"
212 ;; ?\n ">#"
213 ?\" "\"\""
214 ?\' "\"'"
215 ?\` ".`"
216 ?$ "_"
217 ?! "_"
218 ?% "_"
219 ?: "_"
220 ?. "_"
221 ?^ "_"
222 ?~ "_")
223 (rc eval sh-mode-syntax-table sh
224 ?\" "_"
225 ?\` "."))
226 "Syntax-table used in Shell-Script mode. See `sh-feature'.")
227
228
ac59aed8
RS
229
230(defvar sh-mode-map
231 (let ((map (make-sparse-keymap)))
232 (define-key map "\C-c(" 'sh-function)
233 (define-key map "\C-c\C-w" 'sh-while)
234 (define-key map "\C-c\C-u" 'sh-until)
133693bc 235 (define-key map "\C-c\C-t" 'sh-tmp-file)
ac59aed8 236 (define-key map "\C-c\C-s" 'sh-select)
133693bc
KH
237 (define-key map "\C-c\C-r" 'sh-repeat)
238 (define-key map "\C-c\C-o" 'sh-while-getopts)
ac59aed8
RS
239 (define-key map "\C-c\C-l" 'sh-indexed-loop)
240 (define-key map "\C-c\C-i" 'sh-if)
241 (define-key map "\C-c\C-f" 'sh-for)
242 (define-key map "\C-c\C-c" 'sh-case)
133693bc 243
ac59aed8
RS
244 (define-key map "=" 'sh-assignment)
245 (define-key map "\C-c+" 'sh-add)
133693bc
KH
246 (define-key map "\C-c|" 'sh-execute-region)
247 (define-key map "\C-c!" 'executable-interpret)
248 (define-key map "<" 'sh-maybe-here-document)
81ed2d75
KH
249 (define-key map "(" 'skeleton-pair-insert-maybe)
250 (define-key map "{" 'skeleton-pair-insert-maybe)
251 (define-key map "[" 'skeleton-pair-insert-maybe)
252 (define-key map "'" 'skeleton-pair-insert-maybe)
253 (define-key map "`" 'skeleton-pair-insert-maybe)
254 (define-key map "\"" 'skeleton-pair-insert-maybe)
ac59aed8
RS
255
256 (define-key map "\t" 'sh-indent-line)
133693bc 257 (substitute-key-definition 'complete-tag 'comint-dynamic-complete
ac59aed8
RS
258 map (current-global-map))
259 (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
260 map (current-global-map))
ac59aed8
RS
261 (substitute-key-definition 'delete-backward-char
262 'backward-delete-char-untabify
263 map (current-global-map))
264 (define-key map "\C-c:" 'sh-set-shell)
265 (substitute-key-definition 'beginning-of-defun
266 'sh-beginning-of-compound-command
267 map (current-global-map))
268 (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
269 map (current-global-map))
270 (substitute-key-definition 'forward-sentence 'sh-end-of-command
271 map (current-global-map))
ac59aed8
RS
272 (define-key map [menu-bar insert]
273 (cons "Insert" (make-sparse-keymap "Insert")))
274 (define-key map [menu-bar insert sh-while]
133693bc 275 '("While loop" . sh-while))
ac59aed8 276 (define-key map [menu-bar insert sh-until]
133693bc
KH
277 '("Until loop" . sh-until))
278 (define-key map [menu-bar insert sh-tmp-file]
279 '("Temporary file" . sh-tmp-file))
ac59aed8 280 (define-key map [menu-bar insert sh-select]
133693bc
KH
281 '("Select statement" . sh-select))
282 (define-key map [menu-bar insert sh-repeat]
283 '("Repeat loop" . sh-repeat))
284 (define-key map [menu-bar insert sh-while-getopts]
285 '("Options loop" . sh-while-getopts))
ac59aed8 286 (define-key map [menu-bar insert sh-indexed-loop]
133693bc 287 '("Indexed loop" . sh-indexed-loop))
ac59aed8 288 (define-key map [menu-bar insert sh-if]
133693bc 289 '("If statement" . sh-if))
ac59aed8 290 (define-key map [menu-bar insert sh-for]
133693bc 291 '("For loop" . sh-for))
ac59aed8 292 (define-key map [menu-bar insert sh-case]
133693bc 293 '("Case statement" . sh-case))
ac59aed8
RS
294 map)
295 "Keymap used in Shell-Script mode.")
296
297
298
133693bc
KH
299(defvar sh-dynamic-complete-functions
300 '(shell-dynamic-complete-environment-variable
301 shell-dynamic-complete-command
302 comint-dynamic-complete-filename)
303 "*Functions for doing TAB dynamic completion.")
ac59aed8
RS
304
305
133693bc
KH
306(defvar sh-require-final-newline
307 '((csh . t)
308 (pdksh . t)
309 (rc eval . require-final-newline)
310 (sh eval . require-final-newline))
311 "*Value of `require-final-newline' in Shell-Script mode buffers.
312See `sh-feature'.")
ac59aed8
RS
313
314
133693bc
KH
315(defvar sh-comment-prefix
316 '((csh . "\\(^\\|[^$]\\|\\$[^{]\\)")
317 (rc eval identity csh)
318 (sh . "\\(^\\|[ \t|&;()]\\)"))
319 "*Regexp matching what may come before a comment `#'.
320This must contain one \\(grouping\\) since it is the basis for fontifying
321comments as well as for `comment-start-skip'.
322See `sh-feature'.")
ac59aed8
RS
323
324
133693bc
KH
325(defvar sh-assignment-regexp
326 '((csh . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
327 ;; actually spaces are only supported in let/(( ... ))
328 (ksh88 . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=")
329 (rc . "\\<\\([a-zA-Z0-9_*]+\\)[ \t]*=")
330 (sh . "\\<\\([a-zA-Z0-9_]+\\)="))
331 "*Regexp for the variable name and what may follow in an assignment.
332First grouping matches the variable name. This is upto and including the `='
333sign. See `sh-feature'.")
ac59aed8 334
ac59aed8 335
133693bc
KH
336(defvar sh-indentation 4
337 "The width for further indentation in Shell-Script mode.")
ac59aed8 338
ac59aed8
RS
339
340(defvar sh-remember-variable-min 3
341 "*Don't remember variables less than this length for completing reads.")
342
343
133693bc
KH
344(defvar sh-header-marker nil
345 "When non-`nil' is the end of header for prepending by \\[sh-execute-region].
346That command is also used for setting this variable.")
347
348
ac59aed8
RS
349(defvar sh-beginning-of-command
350 "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)"
351 "*Regexp to determine the beginning of a shell command.
352The actual command starts at the beginning of the second \\(grouping\\).")
353
133693bc 354
ac59aed8
RS
355(defvar sh-end-of-command
356 "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
357 "*Regexp to determine the end of a shell command.
358The actual command ends at the end of the first \\(grouping\\).")
359
360
361
133693bc 362(defvar sh-here-document-word "EOF"
ac59aed8
RS
363 "Word to delimit here documents.")
364
225f6185
KH
365(defvar sh-test
366 '((sh "[ ]" . 3)
367 (ksh88 "[[ ]]" . 4))
368 "Initial input in Bourne if, while and until skeletons. See `sh-feature'.")
369
ac59aed8 370
133693bc
KH
371(defvar sh-builtins
372 '((bash eval sh-append sh
373 "alias" "bg" "bind" "builtin" "bye" "command" "declare" "dirs"
374 "enable" "fc" "fg" "function" "help" "history" "jobs" "kill" "let"
375 "local" "logout" "popd" "pushd" "source" "suspend" "typeset"
376 "unalias")
ac59aed8 377
133693bc
KH
378 ;; The next entry is only used for defining the others
379 (bourne eval sh-append shell
380 "do" "done" "elif" "esac" "export" "fi" "for" "getopts" "in"
381 "newgrp" "pwd" "read" "readonly" "return" "times" "trap" "ulimit"
382 "until")
ac59aed8 383
133693bc 384 (csh eval sh-append shell
225f6185 385 "alias" "breaksw" "chdir" "default:" "end" "endif" "endsw" "foreach"
133693bc
KH
386 "glob" "goto" "history" "limit" "logout" "nice" "nohup" "onintr"
387 "rehash" "repeat" "setenv" "source" "switch" "time" "unalias"
388 "unhash")
ac59aed8 389
133693bc
KH
390 (es "access" "apids" "break" "catch" "cd" "echo" "eval" "exec" "exit"
391 "false" "fn" "for" "forever" "fork" "if" "let" "limit" "local"
392 "newpgrp" "result" "return" "throw" "time" "true" "umask"
393 "unwind-protect" "var" "vars" "wait" "whatis" "while")
ac59aed8 394
133693bc
KH
395 (jsh eval sh-append sh
396 "bg" "fg" "jobs" "kill" "stop" "suspend")
ac59aed8 397
133693bc
KH
398 (jcsh eval sh-append csh
399 "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
400
401 (ksh88 eval sh-append bourne
402 "alias" "bg" "false" "fc" "fg" "function" "jobs" "kill" "let"
403 "print" "select" "time" "typeset" "unalias" "whence")
404
405 (oash eval sh-append sh
406 "checkwin" "dateline" "error" "form" "menu" "newwin" "oadeinit"
407 "oaed" "oahelp" "oainit" "pp" "ppfile" "scan" "scrollok" "wattr"
408 "wclear" "werase" "win" "wmclose" "wmmessage" "wmopen" "wmove"
409 "wmtitle" "wrefresh")
410
411 (pdksh eval sh-append ksh88
412 "bind")
413
414 (posix eval sh-append sh
415 "command")
416
417 (rc "break" "builtin" "case" "cd" "echo" "else" "eval" "exec" "exit" "fn"
418 "for" "if" "in" "limit" "newpgrp" "return" "shift" "switch" "umask"
419 "wait" "whatis" "while")
420
421 (sh eval sh-append bourne
422 "hash" "test" "type")
423
424 ;; The next entry is only used for defining the others
425 (shell "break" "case" "cd" "continue" "echo" "else" "eval" "exec" "exit"
426 "if" "set" "shift" "then" "umask" "unset" "wait" "while")
427
428 (zsh eval sh-append ksh88
429 "autoload" "bindkey" "builtin" "bye" "chdir" "compctl" "declare"
430 "dirs" "disable" "disown" "echotc" "enable" "functions" "getln"
431 "hash" "history" "integer" "limit" "local" "log" "logout" "popd"
432 "pushd" "r" "readonly" "rehash" "sched" "setopt" "source" "suspend"
433 "true" "ttyctl" "type" "unfunction" "unhash" "unlimit" "unsetopt"
434 "vared" "which"))
435 "*List of all shell builtins for completing read and fontification.
436Note that on some systems not all builtins are available or some are
437implemented as aliases. See `sh-feature'.")
438
439
440(defvar sh-leading-keywords
441 '((bash eval sh-append sh
442 "builtin" "command" "enable")
443
444 ;; The next entry is only used for defining the others
445 (bourne "do" "elif" "else" "eval" "if" "then" "trap" "until" "while")
446
447 (csh "else")
448
449 (es "eval" "time" "true" "umask"
450 "unwind-protect" "whatis")
451
452 (ksh88 eval sh-append bourne
453 "time" "whence")
454
455 (posix eval sh-append sh
456 "command")
457
458 (rc "builtin" "else" "eval" "whatis")
459
460 (sh eval sh-append bourne
461 "type")
462
463 (zsh eval sh-append ksh88
464 "builtin" "disable" "enable" "type" "unhash" "which"))
465 "*List of keywords that may be immediately followed by a command(-name).
466See `sh-feature'.")
467
468
469
470(defvar sh-variables
471 '((bash eval sh-append sh
472 "allow_null_glob_expansion" "auto_resume" "BASH" "BASH_VERSION"
473 "cdable_vars" "ENV" "EUID" "FCEDIT" "FIGNORE" "glob_dot_filenames"
474 "histchars" "HISTFILE" "HISTFILESIZE" "history_control" "HISTSIZE"
475 "hostname_completion_file" "HOSTTYPE" "IGNOREEOF" "ignoreeof"
476 "LINENO" "MAIL_WARNING" "noclobber" "nolinks" "notify"
477 "no_exit_on_failed_exec" "NO_PROMPT_VARS" "OLDPWD" "OPTERR" "PPID"
478 "PROMPT_COMMAND" "PS4" "pushd_silent" "PWD" "RANDOM" "REPLY"
479 "SECONDS" "SHLVL" "TMOUT" "UID")
480
481 (csh eval sh-append shell
482 "argv" "cdpath" "child" "echo" "histchars" "history" "home"
483 "ignoreeof" "mail" "noclobber" "noglob" "nonomatch" "path" "prompt"
484 "shell" "status" "time" "verbose")
485
486 (es eval sh-append shell
487 "apid" "cdpath" "CDPATH" "history" "home" "ifs" "noexport" "path"
488 "pid" "prompt" "signals")
489
490 (jcsh eval sh-append csh
491 "notify")
492
493 (ksh88 eval sh-append sh
494 "ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO"
495 "OLDPWD" "PPID" "PS3" "PS4" "PWD" "RANDOM" "REPLY" "SECONDS"
496 "TMOUT")
497
498 (oash eval sh-append sh
499 "FIELD" "FIELD_MAX" "LAST_KEY" "OALIB" "PP_ITEM" "PP_NUM")
500
501 (rc eval sh-append shell
502 "apid" "apids" "cdpath" "CDPATH" "history" "home" "ifs" "path" "pid"
503 "prompt" "status")
504
505 (sh eval sh-append shell
506 "CDPATH" "IFS" "OPTARG" "OPTIND" "PS1" "PS2")
507
508 ;; The next entry is only used for defining the others
509 (shell "COLUMNS" "EDITOR" "HOME" "HUSHLOGIN" "LANG" "LC_COLLATE"
510 "LC_CTYPE" "LC_MESSAGES" "LC_MONETARY" "LC_NUMERIC" "LC_TIME"
511 "LINES" "LOGNAME" "MAIL" "MAILCHECK" "MAILPATH" "PAGER" "PATH"
512 "SHELL" "TERM" "TERMCAP" "TERMINFO" "VISUAL")
513
514 (tcsh eval sh-append csh
515 "addsuffix" "ampm" "autocorrect" "autoexpand" "autolist"
516 "autologout" "chase_symlinks" "correct" "dextract" "edit" "el"
517 "fignore" "gid" "histlit" "HOST" "HOSTTYPE" "HPATH"
518 "ignore_symlinks" "listjobs" "listlinks" "listmax" "matchbeep"
519 "nobeep" "NOREBIND" "oid" "printexitvalue" "prompt2" "prompt3"
520 "pushdsilent" "pushdtohome" "recexact" "recognize_only_executables"
521 "rmstar" "savehist" "SHLVL" "showdots" "sl" "SYSTYPE" "tcsh" "term"
522 "tperiod" "tty" "uid" "version" "visiblebell" "watch" "who"
523 "wordchars")
524
525 (zsh eval sh-append ksh88
526 "BAUD" "bindcmds" "cdpath" "DIRSTACKSIZE" "fignore" "FIGNORE" "fpath"
527 "HISTCHARS" "hostcmds" "hosts" "HOSTS" "LISTMAX" "LITHISTSIZE"
528 "LOGCHECK" "mailpath" "manpath" "NULLCMD" "optcmds" "path" "POSTEDIT"
529 "prompt" "PROMPT" "PROMPT2" "PROMPT3" "PROMPT4" "psvar" "PSVAR"
530 "READNULLCMD" "REPORTTIME" "RPROMPT" "RPS1" "SAVEHIST" "SPROMPT"
531 "STTY" "TIMEFMT" "TMOUT" "TMPPREFIX" "varcmds" "watch" "WATCH"
532 "WATCHFMT" "WORDCHARS" "ZDOTDIR"))
533 "List of all shell variables available for completing read.
534See `sh-feature'.")
535
536
537
538(defvar sh-font-lock-keywords
539 '((csh eval sh-append shell
540 '("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1
225f6185 541 font-lock-variable-name-face))
133693bc
KH
542
543 (dtksh eval identity wksh)
544
545 (es eval sh-append executable-font-lock-keywords
546 '("\\$#?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\)" 1
225f6185 547 font-lock-variable-name-face))
133693bc
KH
548
549 (rc eval sh-append es
550 '("\\(^\\|[ \t]\\)\\(else\\( if\\)?\\)\\>" 2
551 font-lock-keyword-face t))
552
553 (sh eval sh-append shell
554 '("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
225f6185 555 font-lock-variable-name-face)
133693bc
KH
556 " in\\([ \t]\\|$\\)")
557
558 ;; The next entry is only used for defining the others
559 (shell eval sh-append executable-font-lock-keywords
560 '("\\\\." 0 font-lock-string-face)
561 '("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1
225f6185 562 font-lock-variable-name-face))
133693bc
KH
563
564 (wksh eval sh-append ksh88
565 '("\\(^\\|[^-._A-Za-z0-9]\\)\\(Xt[A-Z][A-Za-z]*\\)\\($\\|[^-._A-Za-z0-9]\\)" 2 font-lock-keyword-face)))
566 "*Rules for highlighting shell scripts. See `sh-feature'.")
567
568\f
569;; mode-command and utility functions
570
571;;;###autoload
ac59aed8
RS
572(defun sh-mode ()
573 "Major mode for editing shell scripts.
574This mode works for many shells, since they all have roughly the same syntax,
575as far as commands, arguments, variables, pipes, comments etc. are concerned.
576Unless the file's magic number indicates the shell, your usual shell is
577assumed. Since filenames rarely give a clue, they are not further analyzed.
578
133693bc
KH
579This mode adapts to the variations between shells (see `sh-set-shell') by
580means of an inheritance based feature lookup (see `sh-feature'). This
581mechanism applies to all variables (including skeletons) that pertain to
582shell-specific features.
ac59aed8 583
133693bc
KH
584The default style of this mode is that of Rosenblatt's Korn shell book.
585The syntax of the statements varies with the shell being used. The
586following commands are available, based on the current shell's syntax:
ac59aed8
RS
587
588\\[sh-case] case statement
589\\[sh-for] for loop
590\\[sh-function] function definition
591\\[sh-if] if statement
592\\[sh-indexed-loop] indexed loop from 1 to n
133693bc
KH
593\\[sh-while-getopts] while getopts loop
594\\[sh-repeat] repeat loop
595\\[sh-select] select loop
ac59aed8
RS
596\\[sh-until] until loop
597\\[sh-while] while loop
598
599\\[backward-delete-char-untabify] Delete backward one position, even if it was a tab.
600\\[sh-newline-and-indent] Delete unquoted space and indent new line same as this one.
601\\[sh-end-of-command] Go to end of successive commands.
602\\[sh-beginning-of-command] Go to beginning of successive commands.
603\\[sh-set-shell] Set this buffer's shell, and maybe its magic number.
133693bc 604\\[sh-execute-region] Have optional header and region be executed in a subshell.
ac59aed8 605
ac59aed8
RS
606\\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document.
607{, (, [, ', \", `
133693bc
KH
608 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
609
610If you generally program a shell different from your login shell you can
611set `sh-shell-path' accordingly. If your shell's file name doesn't correctly
612indicate what shell it is use `sh-alias-alist' to translate.
613
614If your shell gives error messages with line numbers, you can use \\[executable-interpret]
615with your script for an edit-interpret-debug cycle."
ac59aed8
RS
616 (interactive)
617 (kill-all-local-variables)
ac59aed8
RS
618 (use-local-map sh-mode-map)
619 (make-local-variable 'indent-line-function)
133693bc
KH
620 (make-local-variable 'indent-region-function)
621 (make-local-variable 'paragraph-start)
622 (make-local-variable 'paragraph-separate)
ac59aed8
RS
623 (make-local-variable 'comment-start)
624 (make-local-variable 'comment-start-skip)
ac59aed8 625 (make-local-variable 'require-final-newline)
133693bc 626 (make-local-variable 'sh-header-marker)
ac59aed8
RS
627 (make-local-variable 'sh-shell-path)
628 (make-local-variable 'sh-shell)
81ed2d75
KH
629 (make-local-variable 'skeleton-pair-alist)
630 (make-local-variable 'skeleton-pair-filter)
133693bc
KH
631 (make-local-variable 'font-lock-keywords)
632 (make-local-variable 'comint-dynamic-complete-functions)
633 (make-local-variable 'comint-prompt-regexp)
634 (make-local-variable 'font-lock-keywords-case-fold-search)
635 (make-local-variable 'skeleton-filter)
cd76025c 636 (make-local-variable 'skeleton-newline-indent-rigidly)
133693bc 637 (make-local-variable 'process-environment)
ac59aed8
RS
638 (setq major-mode 'sh-mode
639 mode-name "Shell-script"
ac59aed8 640 indent-line-function 'sh-indent-line
133693bc
KH
641 ;; not very clever, but enables wrapping skeletons around regions
642 indent-region-function (lambda (b e)
643 (indent-rigidly b e sh-indentation))
644 paragraph-start "^$\\|^\f"
645 paragraph-separate paragraph-start
ac59aed8 646 comment-start "# "
133693bc
KH
647 font-lock-keywords-case-fold-search nil
648 comint-dynamic-complete-functions sh-dynamic-complete-functions
649 ;; we can't look if previous line ended with `\'
650 comint-prompt-regexp "^[ \t]*"
81ed2d75
KH
651 skeleton-pair-alist '((?` _ ?`))
652 skeleton-pair-filter 'sh-quoted-p
133693bc
KH
653 skeleton-further-elements '((< '(- (min sh-indentation
654 (current-column)))))
cd76025c
KH
655 skeleton-filter 'sh-feature
656 skeleton-newline-indent-rigidly t)
133693bc
KH
657 ;; parse or insert magic number for exec() and set all variables depending
658 ;; on the shell thus determined
659 (goto-char (point-min))
660 (sh-set-shell
661 (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
662 (buffer-substring (match-beginning 1) (match-end 1))
663 sh-shell-path))
ac59aed8 664 (run-hooks 'sh-mode-hook))
133693bc 665;;;###autoload
ac59aed8
RS
666(defalias 'shell-script-mode 'sh-mode)
667
668
669
133693bc
KH
670(defun sh-set-shell (shell)
671 "Set this buffer's shell to SHELL (a string).
672Makes this script executable via `executable-set-magic'.
673Calls the value of `sh-set-shell-hook' if set."
674 (interactive (list (completing-read "Name or path of shell: " sh-shells)))
675 (if (eq this-command 'sh-set-shell)
676 ;; prevent querying
677 (setq this-command 'executable-set-magic))
678 (setq sh-shell (intern (file-name-nondirectory shell))
679 sh-shell (or (cdr (assq sh-shell sh-alias-alist))
680 sh-shell)
681 sh-shell-path (executable-set-magic shell (sh-feature sh-shell-arg))
682 local-abbrev-table (sh-feature sh-abbrevs)
683 require-final-newline (sh-feature sh-require-final-newline)
684 font-lock-keywords
685 (sh-feature
686 sh-font-lock-keywords
687 (lambda (list)
225f6185
KH
688 `((,(concat (sh-feature sh-comment-prefix) "\\(#.*\\)")
689 2 font-lock-comment-face t)
690 (,(sh-feature sh-assignment-regexp)
691 1 font-lock-variable-name-face)
692 ,@(if font-lock-maximum-decoration
cd76025c 693 `((,(concat "\\(^\\|[|&;()`!]\\)[ \t]*\\(\\(\\("
225f6185
KH
694 (mapconcat 'identity
695 (sh-feature sh-leading-keywords)
696 "\\|")
697 "\\)[ \t]+\\)?\\("
698 (mapconcat 'identity
699 (sh-feature sh-builtins)
700 "\\|")
701 "\\)\\)\\($\\|[ \t|&;()]\\)")
702 2 font-lock-keyword-face 'keep)
703 ,@list)
704 list))))
133693bc
KH
705 comment-start-skip (concat (sh-feature sh-comment-prefix) "#+[\t ]*")
706 mode-line-process (format "[%s]" sh-shell)
707 process-environment (default-value 'process-environment)
708 shell (sh-feature sh-variables))
709 (set-syntax-table (sh-feature sh-mode-syntax-table))
710 (save-excursion
711 (while (search-forward "=" nil t)
712 (sh-assignment 0)))
713 (while shell
714 (sh-remember-variable (car shell))
715 (setq shell (cdr shell)))
716 (and (boundp 'font-lock-mode)
717 font-lock-mode
718 (font-lock-mode (font-lock-mode 0)))
719 (run-hooks 'sh-set-shell-hook))
720
721
ac59aed8 722
133693bc
KH
723(defun sh-feature (list &optional function)
724 "Index ALIST by the current shell.
725If ALIST isn't a list where every element is a cons, it is returned as is.
726Else indexing follows an inheritance logic which works in two ways:
727
728 - Fall back on successive ancestors (see `sh-ancestor-alist') as long as
729 the alist contains no value for the current shell.
730
731 - If the value thus looked up is a list starting with `eval' its `cdr' is
732 first evaluated. If that is also a list and the first argument is a
733 symbol in ALIST it is not evaluated, but rather recursively looked up in
734 ALIST to allow the function called to define the value for one shell to be
735 derived from another shell. While calling the function, is the car of the
736 alist element is the current shell.
737 The value thus determined is physically replaced into the alist.
738
739Optional FUNCTION is applied to the determined value and the result is cached
740in ALIST."
741 (or (if (consp list)
742 (let ((l list))
743 (while (and l (consp (car l)))
744 (setq l (cdr l)))
745 (if l list)))
746 (if function
747 (cdr (assoc (setq function (cons sh-shell function)) list)))
748 (let ((sh-shell sh-shell)
749 elt val)
750 (while (and sh-shell
751 (not (setq elt (assq sh-shell list))))
752 (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist))))
753 (if (and (consp (setq val (cdr elt)))
754 (eq (car val) 'eval))
755 (setcdr elt
756 (setq val
757 (eval (if (consp (setq val (cdr val)))
758 (let ((sh-shell (car (cdr val)))
759 function)
760 (if (assq sh-shell list)
761 (setcar (cdr val)
762 (list 'quote
763 (sh-feature list))))
764 val)
765 val)))))
766 (if function
767 (nconc list
768 (list (cons function
769 (setq sh-shell (car function)
770 val (funcall (cdr function) val))))))
771 val)))
772
773
774
775(defun sh-abbrevs (ancestor &rest list)
776 "Iff it isn't, define the current shell as abbrev table and fill that.
777Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
778table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs
779according to the remaining arguments NAMEi EXPANSIONi ...
780EXPANSION may be either a string or a skeleton command."
781 (or (if (boundp sh-shell)
782 (symbol-value sh-shell))
783 (progn
784 (if (listp ancestor)
785 (nconc list ancestor))
786 (define-abbrev-table sh-shell ())
787 (if (vectorp ancestor)
788 (mapatoms (lambda (atom)
789 (or (eq atom 0)
790 (define-abbrev (symbol-value sh-shell)
791 (symbol-name atom)
792 (symbol-value atom)
793 (symbol-function atom))))
794 ancestor))
795 (while list
796 (define-abbrev (symbol-value sh-shell)
797 (car list)
798 (if (stringp (car (cdr list)))
799 (car (cdr list))
800 "")
801 (if (symbolp (car (cdr list)))
802 (car (cdr list))))
803 (setq list (cdr (cdr list)))))
804 (symbol-value sh-shell)))
805
806
807(defun sh-mode-syntax-table (table &rest list)
808 "Copy TABLE and set syntax for succesive CHARs according to strings S."
809 (setq table (copy-syntax-table table))
810 (while list
811 (modify-syntax-entry (car list) (car (cdr list)) table)
812 (setq list (cdr (cdr list))))
813 table)
814
815
816(defun sh-append (ancestor &rest list)
817 "Return list composed of first argument (a list) physically appended to rest."
818 (nconc list ancestor))
819
820
821(defun sh-modify (skeleton &rest list)
822 "Modify a copy of SKELETON by replacing I1 with REPL1, I2 with REPL2 ..."
823 (setq skeleton (copy-sequence skeleton))
824 (while list
825 (setcar (or (nthcdr (car list) skeleton)
826 (error "Index %d out of bounds" (car list)))
827 (car (cdr list)))
828 (setq list (nthcdr 2 list)))
829 skeleton)
ac59aed8
RS
830
831
832(defun sh-indent-line ()
133693bc
KH
833 "Indent as far as preceding non-empty line, then by steps of `sh-indentation'.
834Lines containing only comments are considered empty."
ac59aed8
RS
835 (interactive)
836 (let ((previous (save-excursion
133693bc
KH
837 (while (progn
838 (line-move -1)
839 (back-to-indentation)
840 (or (eolp)
841 (eq (following-char) ?#))))
842 (current-column)))
843 current)
ac59aed8
RS
844 (save-excursion
845 (indent-to (if (eq this-command 'newline-and-indent)
846 previous
847 (if (< (current-column)
133693bc
KH
848 (setq current (progn (back-to-indentation)
849 (current-column))))
ac59aed8 850 (if (eolp) previous 0)
133693bc
KH
851 (delete-region (point)
852 (progn (beginning-of-line) (point)))
ac59aed8 853 (if (eolp)
133693bc
KH
854 (max previous (* (1+ (/ current sh-indentation))
855 sh-indentation))
856 (* (1+ (/ current sh-indentation)) sh-indentation))))))
857 (if (< (current-column) (current-indentation))
858 (skip-chars-forward " \t"))))
859
860
861(defun sh-execute-region (start end &optional flag)
862 "Pass optional header and region to a subshell for noninteractive execution.
863The working directory is that of the buffer, and only environment variables
864are already set which is why you can mark a header within the script.
865
866With a positive prefix ARG, instead of sending region, define header from
867beginning of buffer to point. With a negative prefix ARG, instead of sending
868region, clear header."
869 (interactive "r\nP")
870 (if flag
871 (setq sh-header-marker (if (> (prefix-numeric-value flag) 0)
872 (point-marker)))
873 (if sh-header-marker
874 (save-excursion
875 (let (buffer-undo-list)
876 (goto-char sh-header-marker)
877 (append-to-buffer (current-buffer) start end)
878 (shell-command-on-region (point-min)
879 (setq end (+ sh-header-marker
880 (- end start)))
881 sh-shell-path)
882 (delete-region sh-header-marker end)))
883 (shell-command-on-region start end (concat sh-shell-path " -")))))
ac59aed8
RS
884
885
886(defun sh-remember-variable (var)
887 "Make VARIABLE available for future completing reads in this buffer."
888 (or (< (length var) sh-remember-variable-min)
133693bc 889 (getenv var)
7874dacd 890 (setq process-environment (cons (concat var "=") process-environment)))
ac59aed8
RS
891 var)
892
893
ac59aed8
RS
894
895(defun sh-quoted-p ()
896 "Is point preceded by an odd number of backslashes?"
133693bc 897 (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2)))
ac59aed8
RS
898\f
899;; statement syntax-commands for various shells
900
901;; You are welcome to add the syntax or even completely new statements as
902;; appropriate for your favorite shell.
903
133693bc
KH
904(define-skeleton sh-case
905 "Insert a case/switch statement. See `sh-feature'."
ac59aed8
RS
906 (csh "expression: "
907 "switch( " str " )" \n
908 > "case " (read-string "pattern: ") ?: \n
909 > _ \n
910 "breaksw" \n
911 ( "other pattern, %s: "
912 < "case " str ?: \n
133693bc 913 > _ \n
ac59aed8
RS
914 "breaksw" \n)
915 < "default:" \n
133693bc 916 > _ \n
ac59aed8
RS
917 resume:
918 < < "endsw")
133693bc
KH
919 (es)
920 (rc "expression: "
921 "switch( " str " ) {" \n
922 > "case " (read-string "pattern: ") \n
923 > _ \n
924 ( "other pattern, %s: "
925 < "case " str \n
926 > _ \n)
927 < "case *" \n
928 > _ \n
929 resume:
930 < < ?})
931 (sh "expression: "
932 "case " str " in" \n
933 > (read-string "pattern: ") ?\) \n
934 > _ \n
935 ";;" \n
936 ( "other pattern, %s: "
937 < str ?\) \n
938 > _ \n
939 ";;" \n)
940 < "*)" \n
941 > _ \n
942 resume:
943 < < "esac"))
944
945
946
947(define-skeleton sh-for
948 "Insert a for loop. See `sh-feature'."
949 (csh eval sh-modify sh
950 1 "foreach "
951 3 " ( "
952 5 " )"
953 15 "end")
954 (es eval sh-modify rc
955 3 " = ")
956 (rc eval sh-modify sh
957 1 "for( "
958 5 " ) {"
959 15 ?})
ac59aed8
RS
960 (sh "Index variable: "
961 "for " str " in " _ "; do" \n
133693bc
KH
962 > _ | ?$ & (sh-remember-variable str) \n
963 < "done"))
ac59aed8
RS
964
965
966
133693bc
KH
967(define-skeleton sh-indexed-loop
968 "Insert an indexed loop from 1 to n. See `sh-feature'."
969 (bash eval identity posix)
ac59aed8
RS
970 (csh "Index variable: "
971 "@ " str " = 1" \n
133693bc
KH
972 "while( $" str " <= " (read-string "upper limit: ") " )" \n
973 > _ ?$ str \n
ac59aed8
RS
974 "@ " str "++" \n
975 < "end")
133693bc
KH
976 (es eval sh-modify rc
977 3 " =")
978 (ksh88 "Index variable: "
979 "integer " str "=0" \n
980 "while (( ( " str " += 1 ) <= "
981 (read-string "upper limit: ")
982 " )); do" \n
983 > _ ?$ (sh-remember-variable str) \n
984 < "done")
985 (posix "Index variable: "
986 str "=1" \n
987 "while [ $" str " -le "
988 (read-string "upper limit: ")
989 " ]; do" \n
990 > _ ?$ str \n
991 str ?= (sh-add (sh-remember-variable str) 1) \n
992 < "done")
993 (rc "Index variable: "
994 "for( " str " in" " `{awk 'BEGIN { for( i=1; i<="
995 (read-string "upper limit: ")
996 "; i++ ) print i }'}) {" \n
997 > _ ?$ (sh-remember-variable str) \n
998 < ?})
999 (sh "Index variable: "
1000 "for " str " in `awk 'BEGIN { for( i=1; i<="
1001 (read-string "upper limit: ")
1002 "; i++ ) print i }'`; do" \n
1003 > _ ?$ (sh-remember-variable str) \n
1004 < "done"))
ac59aed8
RS
1005
1006
1007(defun sh-add (var delta)
133693bc 1008 "Insert an addition of VAR and prefix DELTA for Bourne (type) shell."
ac59aed8 1009 (interactive
133693bc
KH
1010 (list (completing-read "Variable: "
1011 (mapcar (lambda (var)
1012 (substring var 0 (string-match "=" var)))
1013 process-environment))
ac59aed8 1014 (prefix-numeric-value current-prefix-arg)))
133693bc
KH
1015 (insert (sh-feature '((bash . "$[ ")
1016 (ksh88 . "$(( ")
1017 (posix . "$(( ")
1018 (rc . "`{expr $")
1019 (sh . "`expr $")
1020 (zsh . "$[ ")))
1021 (sh-remember-variable var)
1022 (if (< delta 0) " - " " + ")
1023 (number-to-string (abs delta))
1024 (sh-feature '((bash . " ]")
1025 (ksh88 . " ))")
1026 (posix . " ))")
1027 (rc . "}")
1028 (sh . "`")
1029 (zsh . " ]")))))
1030
1031
1032
1033(define-skeleton sh-function
1034 "Insert a function definition. See `sh-feature'."
1035 (bash eval sh-modify ksh88
1036 3 "() {")
1037 (ksh88 "name: "
1038 "function " str " {" \n
1039 > _ \n
1040 < "}")
1041 (rc eval sh-modify ksh88
1042 1 "fn ")
ac59aed8
RS
1043 (sh ()
1044 "() {" \n
1045 > _ \n
133693bc 1046 < "}"))
ac59aed8
RS
1047
1048
1049
133693bc
KH
1050(define-skeleton sh-if
1051 "Insert an if statement. See `sh-feature'."
ac59aed8
RS
1052 (csh "condition: "
1053 "if( " str " ) then" \n
1054 > _ \n
1055 ( "other condition, %s: "
133693bc
KH
1056 < "else if( " str " ) then" \n
1057 > _ \n)
ac59aed8 1058 < "else" \n
133693bc 1059 > _ \n
ac59aed8
RS
1060 resume:
1061 < "endif")
133693bc
KH
1062 (es "condition: "
1063 "if { " str " } {" \n
1064 > _ \n
1065 ( "other condition, %s: "
1066 < "} { " str " } {" \n
1067 > _ \n)
1068 < "} {" \n
1069 > _ \n
1070 resume:
1071 < ?})
1072 (rc eval sh-modify csh
1073 3 " ) {"
1074 8 '( "other condition, %s: "
1075 < "} else if( " str " ) {" \n
1076 > _ \n)
1077 10 "} else {"
1078 17 ?})
1079 (sh "condition: "
225f6185
KH
1080 '(setq input (sh-feature sh-test))
1081 "if " str "; then" \n
133693bc
KH
1082 > _ \n
1083 ( "other condition, %s: "
225f6185 1084 < "elif " str "; then" \n
133693bc
KH
1085 > _ \n)
1086 < "else" \n
1087 > _ \n
1088 resume:
1089 < "fi"))
ac59aed8
RS
1090
1091
1092
133693bc
KH
1093(define-skeleton sh-repeat
1094 "Insert a repeat loop definition. See `sh-feature'."
1095 (es nil
1096 "forever {" \n
1097 > _ \n
1098 < ?})
1099 (zsh "factor: "
1100 "repeat " str "; do"\n
1101 > _ \n
1102 < "done"))
1103(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
1104
1105
1106
1107(define-skeleton sh-select
1108 "Insert a select statement. See `sh-feature'."
1109 (ksh88 "Index variable: "
1110 "select " str " in " _ "; do" \n
1111 > ?$ str \n
1112 < "done"))
1113(put 'sh-select 'menu-enable '(sh-feature sh-select))
1114
1115
1116
1117(define-skeleton sh-tmp-file
1118 "Insert code to setup temporary file handling. See `sh-feature'."
1119 (bash eval identity ksh88)
1120 (csh (file-name-nondirectory (buffer-file-name))
1121 "set tmp = /tmp/" str ".$$" \n
1122 "onintr exit" \n _
1123 (and (goto-char (point-max))
1124 (not (bolp))
1125 ?\n)
1126 "exit:\n"
1127 "rm $tmp* >&/dev/null" >)
1128 (es (file-name-nondirectory (buffer-file-name))
1129 "local( signals = $signals sighup sigint; tmp = /tmp/" str ".$pid ) {" \n
1130 > "catch @ e {" \n
1131 > "rm $tmp^* >[2]/dev/null" \n
1132 "throw $e" \n
1133 < "} {" \n
1134 > _ \n
1135 < ?} \n
1136 < ?})
1137 (ksh88 eval sh-modify sh
1138 6 "EXIT")
1139 (rc (file-name-nondirectory (buffer-file-name))
1140 "tmp = /tmp/" str ".$pid" \n
1141 "fn sigexit { rm $tmp^* >[2]/dev/null }")
1142 (sh (file-name-nondirectory (buffer-file-name))
1143 "TMP=/tmp/" str ".$$" \n
1144 "trap \"rm $TMP* 2>/dev/null\" " ?0))
ac59aed8
RS
1145
1146
1147
133693bc
KH
1148(define-skeleton sh-until
1149 "Insert an until loop. See `sh-feature'."
ac59aed8 1150 (sh "condition: "
225f6185
KH
1151 '(setq input (sh-feature sh-test))
1152 "until " str "; do" \n
ac59aed8 1153 > _ \n
133693bc
KH
1154 < "done"))
1155(put 'sh-until 'menu-enable '(sh-feature sh-until))
1156
1157
1158
1159(define-skeleton sh-while
1160 "Insert a while loop. See `sh-feature'."
1161 (csh eval sh-modify sh
1162 1 "while( "
1163 3 " )"
1164 9 "end")
1165 (es eval sh-modify rc
1166 1 "while { "
1167 3 " } {")
1168 (rc eval sh-modify csh
1169 3 " ) {"
1170 9 ?})
ac59aed8 1171 (sh "condition: "
225f6185
KH
1172 '(setq input (sh-feature sh-test))
1173 "while " str "; do" \n
ac59aed8 1174 > _ \n
133693bc
KH
1175 < "done"))
1176
1177
1178
1179(define-skeleton sh-while-getopts
1180 "Insert a while getopts loop. See `sh-feature'.
1181Prompts for an options string which consists of letters for each recognized
1182option followed by a colon `:' if the option accepts an argument."
1183 (bash eval sh-modify sh
1184 18 "${0##*/}")
225f6185
KH
1185 (csh nil
1186 "while( 1 )" \n
1187 > "switch( \"$1\" )" \n
1188 '(setq input '("- x" . 2))
1189 > >
1190 ( "option, %s: "
1191 < "case " '(eval str)
1192 '(if (string-match " +" str)
1193 (setq v1 (substring str (match-end 0))
1194 str (substring str 0 (match-beginning 0)))
1195 (setq v1 nil))
1196 str ?: \n
1197 > "set " v1 & " = $2" | -4 & _ \n
1198 (if v1 "shift") & \n
1199 "breaksw" \n)
1200 < "case --:" \n
1201 > "shift" \n
1202 < "default:" \n
1203 > "break" \n
1204 resume:
1205 < < "endsw" \n
1206 "shift" \n
1207 < "end")
133693bc
KH
1208 (ksh88 eval sh-modify sh
1209 16 "print"
1210 18 "${0##*/}"
1211 36 "OPTIND-1")
1212 (posix eval sh-modify sh
1213 18 "$(basename $0)")
1214 (sh "optstring: "
1215 "while getopts :" str " OPT; do" \n
1216 > "case $OPT in" \n
1217 > >
1218 '(setq v1 (append (vconcat str) nil))
1219 ( (prog1 (if v1 (char-to-string (car v1)))
1220 (if (eq (nth 1 v1) ?:)
1221 (setq v1 (nthcdr 2 v1)
1222 v2 "\"$OPTARG\"")
1223 (setq v1 (cdr v1)
1224 v2 nil)))
1225 < str "|+" str ?\) \n
1226 > _ v2 \n
1227 ";;" \n)
1228 < "*)" \n
1229 > "echo" " \"usage: " "`basename $0`"
1230 "[ +-" '(setq v1 (point)) str
1231 '(save-excursion
1232 (while (search-backward ":" v1 t)
1233 (replace-match " arg][ +-" t t)))
1234 (if (eq (preceding-char) ?-) -5)
1235 "][ --] args\"" \n
1236 "exit 2" \n
1237 < < "esac" \n
1238 < "done" \n
1239 "shift " (sh-add "OPTIND" -1)))
1240(put 'sh-while-getopts 'menu-enable '(sh-feature sh-while-getopts))
ac59aed8
RS
1241
1242
1243
1244(defun sh-assignment (arg)
133693bc 1245 "Remember preceding identifier for future completion and do self-insert."
ac59aed8 1246 (interactive "p")
133693bc
KH
1247 (self-insert-command arg)
1248 (if (<= arg 1)
ac59aed8
RS
1249 (sh-remember-variable
1250 (save-excursion
133693bc
KH
1251 (if (re-search-forward (sh-feature sh-assignment-regexp)
1252 (prog1 (point)
1253 (beginning-of-line 1))
1254 t)
1255 (buffer-substring (match-beginning 1) (match-end 1)))))))
ac59aed8
RS
1256
1257
1258
1259(defun sh-maybe-here-document (arg)
1260 "Inserts self. Without prefix, following unquoted `<' inserts here document.
1261The document is bounded by `sh-here-document-word'."
1262 (interactive "*P")
1263 (self-insert-command (prefix-numeric-value arg))
1264 (or arg
1265 (not (eq (char-after (- (point) 2)) last-command-char))
1266 (save-excursion
133693bc 1267 (backward-char 2)
ac59aed8
RS
1268 (sh-quoted-p))
1269 (progn
1270 (insert sh-here-document-word)
133693bc 1271 (or (eolp) (looking-at "[ \t]") (insert ? ))
ac59aed8 1272 (end-of-line 1)
133693bc
KH
1273 (while
1274 (sh-quoted-p)
1275 (end-of-line 2))
ac59aed8
RS
1276 (newline)
1277 (save-excursion (insert ?\n sh-here-document-word)))))
1278
1279\f
1280;; various other commands
1281
133693bc
KH
1282(autoload 'comint-dynamic-complete "comint"
1283 "Dynamically perform completion at point." t)
1284
1285(autoload 'shell-dynamic-complete-command "shell"
1286 "Dynamically complete the command at point." t)
1287
ac59aed8
RS
1288(autoload 'comint-dynamic-complete-filename "comint"
1289 "Dynamically complete the filename at point." t)
1290
133693bc
KH
1291(autoload 'shell-dynamic-complete-environment-variable "shell"
1292 "Dynamically complete the environment variable at point." t)
1293
ac59aed8
RS
1294
1295
cd76025c
KH
1296(defun sh-newline-and-indent ()
1297 "Strip unquoted whitespace, insert newline, and indent like current line."
1298 (interactive "*")
1299 (indent-to (prog1 (current-indentation)
1300 (delete-region (point)
1301 (progn
1302 (or (zerop (skip-chars-backward " \t"))
1303 (if (sh-quoted-p)
1304 (forward-char)))
1305 (point)))
1306 (newline))))
ac59aed8
RS
1307
1308
1309
ac59aed8
RS
1310(defun sh-beginning-of-command ()
1311 "Move point to successive beginnings of commands."
1312 (interactive)
1313 (if (re-search-backward sh-beginning-of-command nil t)
1314 (goto-char (match-beginning 2))))
1315
1316
ac59aed8
RS
1317(defun sh-end-of-command ()
1318 "Move point to successive ends of commands."
1319 (interactive)
1320 (if (re-search-forward sh-end-of-command nil t)
1321 (goto-char (match-end 1))))
1322
ac59aed8 1323;; sh-script.el ends here