1 ;;; sh-script.el --- shell-script editing commands for Emacs
3 ;; Copyright (C) 1993, 94, 95, 96, 1997 by Free Software Foundation, Inc.
5 ;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
8 ;; Keywords: languages, unix
10 ;; This file is part of GNU Emacs.
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)
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.
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.
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.
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.
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
50 (defvar sh-mode-hook nil
51 "*Hook run by `sh-mode'.")
53 (defvar sh-set-shell-hook nil
54 "*Hook run by `sh-set-shell'.")
57 "Shell programming utilities"
61 (defgroup sh-script nil
67 (defcustom sh-ancestor-alist
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:
89 jcsh C Shell with Job Control
91 itcsh ? Toronto C Shell
96 jsh Bourne Shell with Job Control
97 bash GNU Bourne Again Shell
100 dtksh CDE Desktop Korn Shell
101 pdksh Public Domain Korn Shell
102 wksh Window Korn Shell
104 oash SCO OA (curses) Shell
105 posix IEEE 1003.2 Shell Standard
107 :type
'(repeat (cons symbol symbol
))
111 (defcustom sh-alias-alist
112 (nconc (if (eq system-type
'gnu
/linux
)
115 ;; for the time being
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
121 :type
'(repeat (cons symbol symbol
))
125 (defcustom sh-shell-file
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"))))
134 "*The executable file name for the shell being programmed."
139 (defcustom sh-shell-arg
140 ;; bash does not need any options when run in a shell script,
144 ;; Bill_Mann@praxisint.com says -p with ksh can do harm.
146 ;; -p means don't initialize functions from the environment.
148 ;; Someone proposed -motif, but we don't want to encourage
149 ;; use of a non-free widget set.
151 ;; -f means don't run .zshrc.
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
)
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.")
168 (defvar sh-shell-variables-initialized nil
169 "Non-nil if `sh-shell-variables' is initialized.")
171 (defun sh-canonicalize-shell (shell)
172 "Convert a shell name SHELL to the one we should handle it as."
174 (setq shell
(intern shell
)))
175 (or (cdr (assq shell sh-alias-alist
))
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].")
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)
188 ;;; (es eval sh-abbrevs shell
189 ;;; "function" 'sh-function)
191 ;;; (ksh88 eval sh-abbrevs sh
192 ;;; "select" 'sh-select)
194 ;;; (rc eval sh-abbrevs shell
196 ;;; "function" 'sh-function)
198 ;;; (sh eval sh-abbrevs shell
200 ;;; "function" 'sh-function
201 ;;; "until" 'sh-until
202 ;;; "getopts" 'sh-while-getopts)
204 ;;; ;; The next entry is only used for defining the others
205 ;;; (shell "for" sh-for
206 ;;; "loop" sh-indexed-loop
208 ;;; "tmpfile" sh-tmp-file
209 ;;; "while" sh-while)
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].")
219 (defvar sh-mode-syntax-table
220 '((sh eval sh-mode-syntax-table
()
233 (csh eval identity sh
)
234 (rc eval identity sh
))
235 "Syntax-table used in Shell-Script mode. See `sh-feature'.")
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
)
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
)
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
))
296 "Keymap used in Shell-Script mode.")
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
)
309 (defcustom sh-require-final-newline
312 (rc eval . require-final-newline
)
313 (sh eval . require-final-newline
))
314 "*Value of `require-final-newline' in Shell-Script mode buffers.
316 :type
'(repeat (cons (symbol :tag
"Shell")
317 (choice (const :tag
"require" t
)
318 (cons :format
"Evaluate: %v"
319 (const :format
"" eval
)
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")
335 (cons :format
"Evaluate: %v"
336 (const :format
"" eval
)
341 (defcustom sh-indentation
4
342 "The width for further indentation in Shell-Script mode."
347 (defcustom sh-remember-variable-min
3
348 "*Don't remember variables less than this length for completing reads."
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.")
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\\)."
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\\)."
375 (defvar sh-here-document-word
"EOF"
376 "Word to delimit here documents.")
381 "Initial input in Bourne if, while and until skeletons. See `sh-feature'.")
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")
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"
397 (csh eval sh-append shell
398 "alias" "chdir" "glob" "history" "limit" "nice" "nohup" "rehash"
399 "setenv" "source" "time" "unalias" "unhash")
401 (dtksh eval identity wksh
)
403 (es "access" "apids" "cd" "echo" "eval" "false" "let" "limit" "local"
404 "newpgrp" "result" "time" "umask" "var" "vars" "wait" "whatis")
406 (jsh eval sh-append sh
407 "bg" "fg" "jobs" "kill" "stop" "suspend")
409 (jcsh eval sh-append csh
410 "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
412 (ksh88 eval sh-append bourne
413 "alias" "bg" "false" "fc" "fg" "jobs" "kill" "let" "print" "time"
414 "typeset" "unalias" "whence")
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")
422 (pdksh eval sh-append ksh88
425 (posix eval sh-append sh
428 (rc "builtin" "cd" "echo" "eval" "limit" "newpgrp" "shift" "umask" "wait"
431 (sh eval sh-append bourne
432 "hash" "test" "type")
434 ;; The next entry is only used for defining the others
435 (shell "cd" "echo" "eval" "set" "shift" "umask" "unset" "wait")
437 (wksh eval sh-append ksh88
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"
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
)
459 (defcustom sh-leading-keywords
462 (es "true" "unwind-protect" "whatis")
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
)
479 (defcustom sh-other-keywords
480 '((bash eval sh-append bourne
483 ;; The next entry is only used for defining the others
484 (bourne eval sh-append sh
487 (csh eval sh-append shell
488 "breaksw" "default" "end" "endif" "endsw" "foreach" "goto"
489 "if" "logout" "onintr" "repeat" "switch" "then" "while")
491 (es "break" "catch" "exec" "exit" "fn" "for" "forever" "fork" "if"
492 "return" "throw" "while")
494 (ksh88 eval sh-append bourne
497 (rc "break" "case" "exec" "exit" "fn" "for" "if" "in" "return" "switch"
500 (sh eval sh-append shell
501 "done" "esac" "fi" "for" "in" "return")
503 ;; The next entry is only used for defining the others
504 (shell "break" "case" "continue" "exec" "exit")
506 (zsh eval sh-append bash
508 "*List of keywords not in `sh-leading-keywords'.
510 :type
'(repeat (cons (symbol :tag
"Shell")
511 (choice (repeat string
)
512 (cons :format
"Evaluate: %v"
513 (const :format
"" eval
)
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")
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")
535 (es eval sh-append shell
536 "apid" "cdpath" "CDPATH" "history" "home" "ifs" "noexport" "path"
537 "pid" "prompt" "signals")
539 (jcsh eval sh-append csh
542 (ksh88 eval sh-append sh
543 "ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO"
544 "OLDPWD" "PPID" "PS3" "PS4" "PWD" "RANDOM" "REPLY" "SECONDS"
547 (oash eval sh-append sh
548 "FIELD" "FIELD_MAX" "LAST_KEY" "OALIB" "PP_ITEM" "PP_NUM")
550 (rc eval sh-append shell
551 "apid" "apids" "cdpath" "CDPATH" "history" "home" "ifs" "path" "pid"
554 (sh eval sh-append shell
555 "CDPATH" "IFS" "OPTARG" "OPTIND" "PS1" "PS2")
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")
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"
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.
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
))
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
))
596 (rc eval identity es
)
598 (sh eval sh-append shell
599 '("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
600 font-lock-variable-name-face
))
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'.")
609 (defvar sh-font-lock-keywords-1
611 "Subdued level highlighting for Shell Script modes.")
613 (defvar sh-font-lock-keywords-2
()
614 "Gaudy level highlighting for Shell Script modes.")
616 (defconst sh-font-lock-syntactic-keywords
617 ;; Mark a `#' character as having punctuation syntax in a variable reference.
618 '(("\\$[({]?\\(#\\)" 1 (1 . nil
))))
620 ;; mode-command and utility functions
623 (put 'sh-mode
'mode-class
'special
)
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.
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.
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:
642 \\[sh-case] case statement
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
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.
660 \\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document.
662 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
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.
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."
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
)
700 (skip-syntax-backward "-")
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
710 comint-dynamic-complete-functions sh-dynamic-complete-functions
711 ;; we can't look if previous line ended with `\'
712 comint-prompt-regexp
"^[ \t]*"
714 '((sh-font-lock-keywords
715 sh-font-lock-keywords-1 sh-font-lock-keywords-2
)
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
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.
729 (goto-char (point-min))
730 (if (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")
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
))
739 (defalias 'shell-script-mode
'sh-mode
)
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
747 `((,(sh-feature sh-assignment-regexp
)
748 1 font-lock-variable-name-face
)
752 (defun sh-font-lock-keywords-1 (&optional builtins
)
753 "Function to get better fontification including keywords."
754 (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\(\\("
756 (sh-feature sh-leading-keywords
)
760 (append (sh-feature sh-leading-keywords
)
761 (sh-feature sh-other-keywords
))
764 (sh-font-lock-keywords
766 `((,(concat keywords
"[ \t]+\\)?\\("
767 (mapconcat 'identity
(sh-feature sh-builtins
) "\\|")
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
)))))
776 (defun sh-font-lock-keywords-2 ()
777 "Function to get better fontification including keywords and builtins."
778 (sh-font-lock-keywords-1 t
))
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
)
791 (setq sh-shell
(intern (file-name-nondirectory shell
))
792 sh-shell
(or (cdr (assq sh-shell sh-alias-alist
))
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
))
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)
815 ; (font-lock-mode (font-lock-mode 0)))
816 (run-hooks 'sh-set-shell-hook
))
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:
825 - Fall back on successive ancestors (see `sh-ancestor-alist') as long as
826 the alist contains no value for the current shell.
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.
836 Optional FUNCTION is applied to the determined value and the result is cached
840 (while (and l
(consp (car l
)))
844 (cdr (assoc (setq function
(cons sh-shell function
)) list
)))
845 (let ((sh-shell 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
))
854 (eval (if (consp (setq val
(cdr val
)))
855 (let ((sh-shell (car (cdr val
)))
857 (if (assq sh-shell list
)
866 (setq sh-shell
(car function
)
867 val
(funcall (cdr function
) val
))))))
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))
882 ;;; (if (listp ancestor)
883 ;;; (nconc list ancestor))
884 ;;; (define-abbrev-table sh-shell ())
885 ;;; (if (vectorp ancestor)
886 ;;; (mapatoms (lambda (atom)
888 ;;; (define-abbrev (symbol-value sh-shell)
889 ;;; (symbol-name atom)
890 ;;; (symbol-value atom)
891 ;;; (symbol-function atom))))
894 ;;; (define-abbrev (symbol-value sh-shell)
896 ;;; (if (stringp (car (cdr list)))
899 ;;; (if (symbolp (car (cdr list)))
900 ;;; (car (cdr list))))
901 ;;; (setq list (cdr (cdr list)))))
902 ;;; (symbol-value sh-shell)))
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
))
909 (modify-syntax-entry (car list
) (car (cdr list
)) table
)
910 (setq list
(cdr (cdr list
))))
914 (defun sh-append (ancestor &rest list
)
915 "Return list composed of first argument (a list) physically appended to rest."
916 (nconc list ancestor
))
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
))
923 (setcar (or (nthcdr (car list
) skeleton
)
924 (error "Index %d out of bounds" (car list
)))
926 (setq list
(nthcdr 2 list
)))
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."
934 (let ((previous (save-excursion
935 (while (and (not (bobp))
938 (back-to-indentation)
940 (eq (following-char) ?
#)))))
944 (indent-to (if (eq this-command
'newline-and-indent
)
946 (if (< (current-column)
947 (setq current
(progn (back-to-indentation)
949 (if (eolp) previous
0)
950 (delete-region (point)
951 (progn (beginning-of-line) (point)))
953 (max previous
(* (1+ (/ current sh-indentation
))
955 (* (1+ (/ current sh-indentation
)) sh-indentation
))))))
956 (if (< (current-column) (current-indentation))
957 (skip-chars-forward " \t"))))
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.
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."
970 (setq sh-header-marker
(if (> (prefix-numeric-value flag
) 0)
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
981 (delete-region sh-header-marker end
)))
982 (shell-command-on-region start end
(concat sh-shell-file
" -")))))
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
)
989 (assoc var sh-shell-variables
)
990 (setq sh-shell-variables
(cons (cons var var
) sh-shell-variables
)))
995 (defun sh-quoted-p ()
996 "Is point preceded by an odd number of backslashes?"
997 (eq -
1 (%
(save-excursion (skip-chars-backward "\\\\")) 2)))
999 ;; statement syntax-commands for various shells
1001 ;; You are welcome to add the syntax or even completely new statements as
1002 ;; appropriate for your favorite shell.
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
1011 ( "other pattern, %s: "
1021 "switch( " str
" ) {" \n
1022 > "case " (read-string "pattern: ") \n
1024 ( "other pattern, %s: "
1032 "case " str
" in" \n
1033 > (read-string "pattern: ") ?\
) \n
1036 ( "other pattern, %s: "
1045 (define-skeleton sh-for
1046 "Insert a for loop. See `sh-feature'."
1047 (csh eval sh-modify sh
1052 (es eval sh-modify rc
1054 (rc eval sh-modify sh
1058 (sh "Index variable: "
1059 "for " str
" in " _
"; do" \n
1060 > _ | ?$
& (sh-remember-variable str
) \n
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: "
1070 "while( $" str
" <= " (read-string "upper limit: ") " )" \n
1074 (es eval sh-modify rc
1076 (ksh88 "Index variable: "
1077 "integer " str
"=0" \n
1078 "while (( ( " str
" += 1 ) <= "
1079 (read-string "upper limit: ")
1081 > _ ?$
(sh-remember-variable str
) \n
1083 (posix "Index variable: "
1085 "while [ $" str
" -le "
1086 (read-string "upper limit: ")
1089 str ?
= (sh-add (sh-remember-variable str
) 1) \n
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
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
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))
1110 (goto-char (point-min))
1111 (setq sh-shell-variables-initialized t
)
1112 (while (search-forward "=" nil t
)
1114 (message "Scanning buffer `%s' for variable assignments...done"
1117 (defvar sh-add-buffer
)
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
1132 (set-buffer sh-add-buffer
)
1133 (or sh-shell-variables-initialized
1134 (sh-shell-initialize-variables))
1135 (nconc (mapcar (lambda (var)
1137 (substring var
0 (string-match "=" var
))))
1139 process-environment
)
1140 sh-shell-variables
))))
1142 (try-completion string sh-shell-variables predicate
))
1144 (all-completions string sh-shell-variables predicate
))
1146 (assoc string sh-shell-variables
)))))
1148 (defun sh-add (var delta
)
1149 "Insert an addition of VAR and prefix DELTA for Bourne (type) shell."
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 .
"$[ ")
1160 (sh-remember-variable var
)
1161 (if (< delta
0) " - " " + ")
1162 (number-to-string (abs delta
))
1163 (sh-feature '((bash .
" ]")
1172 (define-skeleton sh-function
1173 "Insert a function definition. See `sh-feature'."
1174 (bash eval sh-modify ksh88
1177 "function " str
" {" \n
1180 (rc eval sh-modify ksh88
1189 (define-skeleton sh-if
1190 "Insert an if statement. See `sh-feature'."
1192 "if( " str
" ) then" \n
1194 ( "other condition, %s: "
1195 < "else if( " str
" ) then" \n
1202 "if { " str
" } {" \n
1204 ( "other condition, %s: "
1205 < "} { " str
" } {" \n
1211 (rc eval sh-modify csh
1213 8 '( "other condition, %s: "
1214 < "} else if( " str
" ) {" \n
1219 '(setq input
(sh-feature sh-test
))
1220 "if " str
"; then" \n
1222 ( "other condition, %s: "
1223 < "elif " str
"; then" \n
1232 (define-skeleton sh-repeat
1233 "Insert a repeat loop definition. See `sh-feature'."
1239 "repeat " str
"; do"\n
1242 ;;;(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
1246 (define-skeleton sh-select
1247 "Insert a select statement. See `sh-feature'."
1248 (ksh88 "Index variable: "
1249 "select " str
" in " _
"; do" \n
1252 ;;;(put 'sh-select 'menu-enable '(sh-feature sh-select))
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
1262 (and (goto-char (point-max))
1266 "rm $tmp* >&/dev/null" >)
1267 (es (file-name-nondirectory (buffer-file-name))
1268 "local( signals = $signals sighup sigint; tmp = /tmp/" str
".$pid ) {" \n
1270 > "rm $tmp^* >[2]/dev/null" \n
1276 (ksh88 eval sh-modify sh
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))
1287 (define-skeleton sh-until
1288 "Insert an until loop. See `sh-feature'."
1290 '(setq input
(sh-feature sh-test
))
1291 "until " str
"; do" \n
1294 ;;;(put 'sh-until 'menu-enable '(sh-feature sh-until))
1298 (define-skeleton sh-while
1299 "Insert a while loop. See `sh-feature'."
1300 (csh eval sh-modify sh
1304 (es eval sh-modify rc
1307 (rc eval sh-modify csh
1311 '(setq input
(sh-feature sh-test
))
1312 "while " str
"; do" \n
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
1326 > "switch( \"$1\" )" \n
1327 '(setq input
'("- x" .
2))
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)))
1336 > "set " v1
& " = $2" | -
4 & _
\n
1337 (if v1
"shift") & \n
1347 (ksh88 eval sh-modify sh
1351 (posix eval sh-modify sh
1352 18 "$(basename $0)")
1354 "while getopts :" str
" OPT; do" \n
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
)
1364 < str
"|+" str ?\
) \n
1368 > "echo" " \"usage: " "`basename $0`"
1369 " [+-" '(setq v1
(point)) str
1371 (while (search-backward ":" v1 t
)
1372 (replace-match " ARG] [+-" t t
)))
1373 (if (eq (preceding-char) ?-
) -
5)
1374 "] [--] ARGS...\"" \n
1378 "shift " (sh-add "OPTIND" -
1)))
1382 (defun sh-assignment (arg)
1383 "Remember preceding identifier for future completion and do self-insert."
1385 (self-insert-command arg
)
1387 (sh-remember-variable
1389 (if (re-search-forward (sh-feature sh-assignment-regexp
)
1391 (beginning-of-line 1))
1393 (match-string 1))))))
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'."
1401 (self-insert-command (prefix-numeric-value arg
))
1403 (not (eq (char-after (- (point) 2)) last-command-char
))
1408 (insert sh-here-document-word
)
1409 (or (eolp) (looking-at "[ \t]") (insert ?
))
1415 (save-excursion (insert ?
\n sh-here-document-word
)))))
1418 ;; various other commands
1420 (autoload 'comint-dynamic-complete
"comint"
1421 "Dynamically perform completion at point." t
)
1423 (autoload 'shell-dynamic-complete-command
"shell"
1424 "Dynamically complete the command at point." t
)
1426 (autoload 'comint-dynamic-complete-filename
"comint"
1427 "Dynamically complete the filename at point." t
)
1429 (autoload 'shell-dynamic-complete-environment-variable
"shell"
1430 "Dynamically complete the environment variable at point." t
)
1434 (defun sh-newline-and-indent ()
1435 "Strip unquoted whitespace, insert newline, and indent like current line."
1437 (indent-to (prog1 (current-indentation)
1438 (delete-region (point)
1440 (or (zerop (skip-chars-backward " \t"))
1448 (defun sh-beginning-of-command ()
1449 "Move point to successive beginnings of commands."
1451 (if (re-search-backward sh-beginning-of-command nil t
)
1452 (goto-char (match-beginning 2))))
1455 (defun sh-end-of-command ()
1456 "Move point to successive ends of commands."
1458 (if (re-search-forward sh-end-of-command nil t
)
1459 (goto-char (match-end 1))))
1461 (provide 'sh-script
)
1462 ;; sh-script.el ends here