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