1 ;;; sh-script.el --- shell-script editing commands for Emacs
2 ;; Copyright (C) 1993 Free Software Foundation, Inc.
4 ;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
6 ;; Keywords: shell programming
8 ;; This file is part of GNU Emacs.
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
26 ;; Major mode for editing shell scripts. Currently sh, ksh, bash and csh,
27 ;; tcsh are supported. Structured statements can be inserted with one
30 ;; Autoloading of these functions is currently turned off
31 ;; because it's not clear whether this mode is really desirable to use.
36 ;; page 1: variables and settings
37 ;; page 2: mode-command and utility functions
38 ;; page 3: statement syntax-commands for various shells
39 ;; page 4: various other commands
45 ;; - whose path contains /bin/, but not directories
46 (cons '("/bin/" . sh-or-other-mode)
47 ;; - that have a suffix .sh or .shar (shell archive)
48 ;; - that contain resources for the various shells
49 ;; - startup files for X11
50 (cons '("\\.sh\\'\\|\\.shar\\'\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)\\'" . sh-mode)
54 (defvar sh-mode-syntax-table
55 (let ((table (copy-syntax-table)))
56 (modify-syntax-entry ?\# "<" table)
57 (modify-syntax-entry ?\^l ">#" table)
58 (modify-syntax-entry ?\n ">#" table)
59 (modify-syntax-entry ?\" "\"\"" table)
60 (modify-syntax-entry ?\' "\"'" table)
61 (modify-syntax-entry ?\` "$`" table)
62 (modify-syntax-entry ?$ "_" table)
63 (modify-syntax-entry ?! "_" table)
64 (modify-syntax-entry ?% "_" table)
65 (modify-syntax-entry ?: "_" table)
66 (modify-syntax-entry ?. "_" table)
67 (modify-syntax-entry ?^ "_" table)
68 (modify-syntax-entry ?~ "_" table)
70 "Syntax table in use in Shell-Script mode.")
74 (defvar sh-use-prefix nil
75 "If non-nil when loading, `$' and `<' will be C-c $ and C-c < .")
78 (let ((map (make-sparse-keymap)))
79 (define-key map "\C-c(" 'sh-function)
80 (define-key map "\C-c\C-w" 'sh-while)
81 (define-key map "\C-c\C-u" 'sh-until)
82 (define-key map "\C-c\C-s" 'sh-select)
83 (define-key map "\C-c\C-l" 'sh-indexed-loop)
84 (define-key map "\C-c\C-i" 'sh-if)
85 (define-key map "\C-c\C-f" 'sh-for)
86 (define-key map "\C-c\C-c" 'sh-case)
88 (define-key map (if sh-use-prefix "\C-c$" "$")
89 'sh-query-for-variable)
90 (define-key map "=" 'sh-assignment)
91 (define-key map "\C-c+" 'sh-add)
92 (define-key map (if sh-use-prefix "\C-c<" "<")
93 'sh-maybe-here-document)
94 (define-key map "(" 'pair-insert-maybe)
95 (define-key map "{" 'pair-insert-maybe)
96 (define-key map "[" 'pair-insert-maybe)
97 (define-key map "'" 'pair-insert-maybe)
98 (define-key map "`" 'pair-insert-maybe)
99 (define-key map "\"" 'pair-insert-maybe)
101 (define-key map "\t" 'sh-indent-line)
102 (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename
103 map (current-global-map))
104 (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
105 map (current-global-map))
106 ;; Now that tabs work properly, this might be unwanted.
107 (substitute-key-definition 'delete-backward-char
108 'backward-delete-char-untabify
109 map (current-global-map))
110 (define-key map "\C-c:" 'sh-set-shell)
111 (substitute-key-definition 'beginning-of-defun
112 'sh-beginning-of-compound-command
113 map (current-global-map))
114 (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
115 map (current-global-map))
116 (substitute-key-definition 'forward-sentence 'sh-end-of-command
117 map (current-global-map))
118 (substitute-key-definition 'manual-entry 'sh-manual-entry
119 map (current-global-map))
120 (define-key map [menu-bar insert]
121 (cons "Insert" (make-sparse-keymap "Insert")))
122 (define-key map [menu-bar insert sh-while]
123 '("While loop" . sh-while))
124 (define-key map [menu-bar insert sh-until]
125 '("Until loop" . sh-until))
126 (define-key map [menu-bar insert sh-select]
127 '("Select statement" . sh-select))
128 (define-key map [menu-bar insert sh-indexed-loop]
129 '("Indexed loop" . sh-indexed-loop))
130 (define-key map [menu-bar insert sh-if]
131 '("If statement" . sh-if))
132 (define-key map [menu-bar insert sh-for]
133 '("For loop" . sh-for))
134 (define-key map [menu-bar insert sh-case]
135 '("Case statement" . sh-case))
137 "Keymap used in Shell-Script mode.")
141 (defvar sh-find-file-modifies t
142 "*What to do when newly found file has no magic number:
144 t insert magic number
145 other insert magic number, but mark as unmodified.")
148 (defvar sh-query-for-magic t
149 "*If non-nil, ask user before changing or inserting magic number.")
152 (defvar sh-magicless-file-regexp "/\\.[^/]+$"
153 "*On files with this kind of name no magic is inserted or changed.")
156 ;; someone who understands /etc/magic better than me should beef this up
157 ;; this currently covers only SCO Unix and Sinix executables
158 ;; the elegant way would be to read /etc/magic
159 (defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode)
160 ("#!.*perl" . perl-mode))
161 "A regexp to match the magic number of a found file.
162 Currently this is only used by function `sh-or-other-mode'.")
165 (defvar sh-executable ".* is \\([^ \t]*\\)\n"
166 "*Regexp to match the output of sh builtin `type' command on your machine.
167 The regexp must match the whole output, and must contain a \\(something\\)
168 construct which matches the actual executable.")
172 (defvar sh-chmod-argument "+x"
173 "*After saving, if the file is not executable, set this mode.
174 The mode can be absolute, such as \"777\", or relative, such as \"+x\".
175 Do nothing if this is nil.")
178 (defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
179 "*The executable of the shell being programmed.")
181 (defvar sh-shell-argument nil
182 "*A single argument for the magic number, or nil.")
185 "The shell being programmed. This is set by \\[sh-set-shell].")
187 (defvar sh-shell-is-csh nil
188 "The shell being programmed. This is set by \\[sh-set-shell].")
190 (defvar sh-tab-width 4
191 "The default value for `tab-width' in Shell-Script mode.
192 This is the width of tab stops after the indentation of the preceeding line.")
194 (defvar sh-remember-variable-min 3
195 "*Don't remember variables less than this length for completing reads.")
198 (defvar sh-beginning-of-command
199 "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)"
200 "*Regexp to determine the beginning of a shell command.
201 The actual command starts at the beginning of the second \\(grouping\\).")
203 (defvar sh-end-of-command
204 "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
205 "*Regexp to determine the end of a shell command.
206 The actual command ends at the end of the first \\(grouping\\).")
210 (defvar sh-assignment-space '(csh tcsh)
211 "List of shells that allow spaces around the assignment =.")
213 (defvar sh-here-document-word "+"
214 "Word to delimit here documents.")
219 '(("addsuffix" tcsh) ("allow_null_glob_expansion" bash)
220 ("ampm" tcsh) ("argv" csh tcsh)
221 ("autocorrect" tcsh) ("autoexpand" tcsh)
222 ("autolist" tcsh) ("autologout" tcsh)
223 ("auto_resume" bash) ("BASH" bash)
224 ("BASH_VERSION" bash) ("cdable_vars" bash)
225 ("cdpath" csh tcsh) ("CDPATH" sh ksh bash)
226 ("chase_symlinks" tcsh) ("child" csh tcsh)
227 ("COLUMNS" ksh tcsh) ("correct" tcsh)
228 ("dextract" tcsh) ("echo" csh tcsh)
229 ("edit" tcsh) ("EDITOR")
230 ("el" tcsh) ("ENV" ksh bash)
231 ("ERRNO" ksh) ("EUID" bash)
232 ("FCEDIT" ksh bash) ("FIGNORE" bash)
233 ("fignore" tcsh) ("FPATH" ksh)
234 ("gid" tcsh) ("glob_dot_filenames" bash)
235 ("histchars" bash csh tcsh) ("HISTFILE" ksh bash)
236 ("HISTFILESIZE" bash) ("histlit" tcsh)
237 ("history" csh tcsh) ("history_control" bash)
238 ("HISTSIZE" bash) ("home" csh tcsh)
239 ("HOME") ("HOST" tcsh)
240 ("hostname_completion_file" bash) ("HOSTTYPE" bash tcsh)
241 ("HPATH" tcsh) ("HUSHLOGIN")
242 ("IFS" sh ksh bash) ("ignoreeof" bash csh tcsh)
243 ("IGNOREEOF" bash) ("ignore_symlinks" tcsh)
244 ("LANG") ("LC_COLLATE")
245 ("LC_CTYPE") ("LC_MESSAGES")
246 ("LC_MONETARY") ("LC_NUMERIC")
247 ("LC_TIME") ("LINENO" ksh bash)
248 ("LINES" ksh tcsh) ("listjobs" tcsh)
249 ("listlinks" tcsh) ("listmax" tcsh)
250 ("LOGNAME") ("mail" csh tcsh)
251 ("MAIL") ("MAILCHECK")
252 ("MAILPATH") ("MAIL_WARNING" bash)
253 ("matchbeep" tcsh) ("nobeep" tcsh)
254 ("noclobber" bash csh tcsh) ("noglob" csh tcsh)
255 ("nolinks" bash) ("nonomatch" csh tcsh)
256 ("NOREBIND" tcsh) ("notify" bash)
257 ("no_exit_on_failed_exec" bash) ("NO_PROMPT_VARS" bash)
258 ("oid" tcsh) ("OLDPWD" ksh bash)
259 ("OPTARG" sh ksh bash) ("OPTERR" bash)
260 ("OPTIND" sh ksh bash) ("PAGER")
261 ("path" csh tcsh) ("PATH")
262 ("PPID" ksh bash) ("printexitvalue" tcsh)
263 ("prompt" csh tcsh) ("prompt2" tcsh)
264 ("prompt3" tcsh) ("PROMPT_COMMAND" bash)
265 ("PS1" sh ksh bash) ("PS2" sh ksh bash)
266 ("PS3" ksh) ("PS4" ksh bash)
267 ("pushdsilent" tcsh) ("pushdtohome" tcsh)
268 ("pushd_silent" bash) ("PWD" ksh bash)
269 ("RANDOM" ksh bash) ("recexact" tcsh)
270 ("recognize_only_executables" tcsh) ("REPLY" ksh bash)
271 ("rmstar" tcsh) ("savehist" tcsh)
272 ("SECONDS" ksh bash) ("shell" csh tcsh)
273 ("SHELL") ("SHLVL" bash tcsh)
274 ("showdots" tcsh) ("sl" tcsh)
275 ("status" csh tcsh) ("SYSTYPE" tcsh)
276 ("tcsh" tcsh) ("term" tcsh)
278 ("time" csh tcsh) ("TMOUT" ksh bash)
279 ("tperiod" tcsh) ("tty" tcsh)
280 ("UID" bash) ("uid" tcsh)
281 ("verbose" csh tcsh) ("version" tcsh)
282 ("visiblebell" tcsh) ("VISUAL")
283 ("watch" tcsh) ("who" tcsh)
285 "Alist of all environment and shell variables used for completing read.
286 Variables only understood by some shells are associated to a list of those.")
290 (defvar sh-font-lock-keywords nil
291 ;; This is done syntactically:
292 ;'(("[ \t]\\(#.*\\)" 1 font-lock-comment-face)
293 ; ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face))
294 "*Rules for highlighting shell scripts.
295 This variable is included into the various variables
296 `sh-SHELL-font-lock-keywords'. If no such variable exists for some shell,
300 (defvar sh-sh-font-lock-keywords
301 (append sh-font-lock-keywords
302 '(("\\(^\\|[^-._a-z0-9]\\)\\(case\\|do\\|done\\|elif\\|else\\|esac\\|fi\\|for\\|if\\|in\\|then\\|until\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
303 "*Rules for highlighting Bourne shell scripts.")
305 (defvar sh-ksh-font-lock-keywords
306 (append sh-sh-font-lock-keywords
307 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
308 "*Rules for highlighting Korn shell scripts.")
310 (defvar sh-bash-font-lock-keywords
311 (append sh-sh-font-lock-keywords
312 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
313 "*Rules for highlighting Bourne again shell scripts.")
316 (defvar sh-csh-font-lock-keywords
317 (append sh-font-lock-keywords
318 '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
319 "*Rules for highlighting C shell scripts.")
321 (defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
322 "*Rules for highlighting Toronto C shell scripts.")
326 ;; mode-command and utility functions
329 (defun sh-or-other-mode ()
330 "Decide whether this is a compiled executable or a script.
331 Usually the file-names of scripts and binaries cannot be automatically
332 distinguished, so the presence of an executable's magic number is used."
333 (funcall (or (let ((l magic-number-alist))
335 (not (looking-at (car (car l)))))
343 "Major mode for editing shell scripts.
344 This mode works for many shells, since they all have roughly the same syntax,
345 as far as commands, arguments, variables, pipes, comments etc. are concerned.
346 Unless the file's magic number indicates the shell, your usual shell is
347 assumed. Since filenames rarely give a clue, they are not further analyzed.
349 The syntax of the statements varies with the shell being used. The syntax of
350 statements can be modified by putting a property on the command or new ones
351 defined with `define-sh-skeleton'. For example
353 (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
355 (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
357 where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
359 The following commands are available, based on the current shell's syntax:
361 \\[sh-case] case statement
363 \\[sh-function] function definition
364 \\[sh-if] if statement
365 \\[sh-indexed-loop] indexed loop from 1 to n
366 \\[sh-select] select statement
367 \\[sh-until] until loop
368 \\[sh-while] while loop
370 \\[backward-delete-char-untabify] Delete backward one position, even if it was a tab.
371 \\[sh-newline-and-indent] Delete unquoted space and indent new line same as this one.
372 \\[sh-end-of-command] Go to end of successive commands.
373 \\[sh-beginning-of-command] Go to beginning of successive commands.
374 \\[sh-set-shell] Set this buffer's shell, and maybe its magic number.
375 \\[sh-manual-entry] Display the Unix manual entry for the current command or shell.
377 \\[sh-query-for-variable] Unless quoted with \\, query for a variable with completions offered.
378 \\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document.
380 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
382 (kill-all-local-variables)
383 (set-syntax-table sh-mode-syntax-table)
384 (use-local-map sh-mode-map)
385 (make-local-variable 'indent-line-function)
386 (make-local-variable 'comment-start)
387 (make-local-variable 'comment-start-skip)
388 (make-local-variable 'after-save-hook)
389 (make-local-variable 'require-final-newline)
390 (make-local-variable 'sh-shell-path)
391 (make-local-variable 'sh-shell)
392 (make-local-variable 'sh-shell-is-csh)
393 (make-local-variable 'pair-alist)
394 (make-local-variable 'pair-filter)
395 (make-local-variable 'font-lock-defaults)
396 (make-local-variable 'sh-variables)
397 (setq major-mode 'sh-mode
398 mode-name "Shell-script"
399 ;; Why can't Emacs have one standard function with some parameters?
400 ;; Only few modes actually analyse the previous line's contents
401 indent-line-function 'sh-indent-line
403 after-save-hook 'sh-chmod
404 tab-width sh-tab-width
406 require-final-newline t
407 pair-alist '((?` _ ?`))
408 pair-filter 'sh-quoted-p)
409 ;; parse or insert magic number for exec
411 (goto-char (point-min))
413 (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
414 (buffer-substring (match-beginning 1) (match-end 1))
416 ;; find-file is set by `normal-mode' when called by `after-find-file'
417 (and (boundp 'find-file) find-file
418 (or (eq sh-find-file-modifies t)
419 (set-buffer-modified-p nil)))
420 (run-hooks 'sh-mode-hook))
422 (defalias 'shell-script-mode 'sh-mode)
426 (defmacro define-sh-skeleton (command documentation &rest definitions)
427 "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ...
428 Prior definitions (e.g. from ~/.emacs) are maintained.
429 Each definition is built up as (SHELL PROMPT ELEMENT ...). Alternately
430 a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
432 For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
433 Each DEFINITION is actually stored as
434 (put COMMAND SHELL (PROMPT ELEMENT ...)),
435 which you can also do yourself."
436 (or (stringp documentation)
437 (setq definitions (cons documentation definitions)
439 ;; The compiled version doesn't.
442 (let ((definitions '(, definitions)))
444 ;; skeleton need not be loaded to define these
445 (or (and (not (if (boundp 'skeleton-debug) skeleton-debug))
446 (get '(, command) (car (car definitions))))
447 (put '(, command) (car (car definitions))
448 (if (symbolp (cdr (car definitions)))
449 (get '(, command) (cdr (car definitions)))
450 (cdr (car definitions)))))
451 (setq definitions (cdr definitions))))
452 (put '(, command) 'menu-enable '(get '(, command) sh-shell))
453 (defun (, command) ()
457 (or (get '(, command) sh-shell)
458 (error "%s statement syntax not defined for shell %s."
459 '(, command) sh-shell)))))))
463 (defun sh-indent-line ()
464 "Indent as far as preceding line, then by steps of `tab-width'.
465 If previous line starts with a comment, it's considered empty."
467 (let ((previous (save-excursion
469 (back-to-indentation)
470 (if (looking-at comment-start-skip)
474 (indent-to (if (eq this-command 'newline-and-indent)
476 (if (< (current-column)
477 (progn (back-to-indentation)
479 (if (eolp) previous 0)
481 (max previous (* (1+ (/ (current-column) tab-width))
483 (* (1+ (/ (current-column) tab-width)) tab-width))))))
484 (if (< (current-column) (current-indentation))
485 (skip-chars-forward " \t"))))
488 (defun sh-remember-variable (var)
489 "Make VARIABLE available for future completing reads in this buffer."
490 (or (< (length var) sh-remember-variable-min)
491 (assoc var sh-variables)
492 (setq sh-variables (cons (list var) sh-variables)))
496 ;; Augment the standard variables by those found in the environment.
497 (if (boundp 'process-environment)(let ((l process-environment))
499 (sh-remember-variable (substring (car l)
500 0 (string-match "=" (car l))))
505 (defun sh-quoted-p ()
506 "Is point preceded by an odd number of backslashes?"
507 (eq 1 (% (- (point) (save-excursion
508 (skip-chars-backward "\\\\")
514 (defun sh-executable (command)
515 "If COMMAND is an executable in $PATH its full name is returned. Else nil."
516 (let ((point (point))
517 (buffer-modified-p (buffer-modified-p))
518 buffer-read-only after-change-function)
519 (call-process "sh" nil t nil "-c" (concat "type " command))
520 (setq point (prog1 (point)
523 (and (looking-at sh-executable)
524 (eq point (match-end 0))
525 (buffer-substring (match-beginning 1) (match-end 1)))
526 (delete-region (point) point)
527 (set-buffer-modified-p buffer-modified-p))))
532 "This gets called after saving a file to assure that it be executable.
533 You can set the absolute or relative mode with `sh-chmod-argument'."
534 (if sh-chmod-argument
535 (or (file-executable-p buffer-file-name)
536 (shell-command (concat "chmod " sh-chmod-argument
537 " " buffer-file-name)))))
539 ;; statement syntax-commands for various shells
541 ;; You are welcome to add the syntax or even completely new statements as
542 ;; appropriate for your favorite shell.
544 (define-sh-skeleton sh-case
545 "Insert a case/switch statement in the current shell's syntax."
548 > (read-string "pattern: ") ?\) \n
551 ( "other pattern, %s: "
562 "switch( " str " )" \n
563 > "case " (read-string "pattern: ") ?: \n
566 ( "other pattern, %s: "
578 (define-sh-skeleton sh-for
579 "Insert a for loop in the current shell's syntax."
580 (sh "Index variable: "
581 "for " str " in " _ "; do" \n
582 > ?$ (sh-remember-variable str) \n
586 (csh "Index variable: "
587 "foreach " str " ( " _ " )" \n
588 > ?$ (sh-remember-variable str) \n
594 (define-sh-skeleton sh-indexed-loop
595 "Insert an indexed loop from 1 to n in the current shell's syntax."
596 (sh "Index variable: "
598 "while [ $" str " -le "
599 (read-string "upper limit: ")
602 str ?= (sh-add (sh-remember-variable str) 1) \n
606 (csh "Index variable: "
608 "while( $" str " <= "
609 (read-string "upper limit: ")
611 > _ ?$ (sh-remember-variable str) \n
618 (defun sh-add (var delta)
619 "Insert an addition of VAR and prefix DELTA for Bourne type shells."
621 (list (sh-remember-variable
622 (completing-read "Variable: " sh-variables
624 (or (not (cdr element))
625 (memq sh-shell (cdr element))))))
626 (prefix-numeric-value current-prefix-arg)))
627 (setq delta (concat (if (< delta 0) " - " " + ")
631 '((sh "`expr $" var delta "`")
632 (ksh "$(( $" var delta " ))")
633 (bash "$[ $" var delta " ]")))
638 (define-sh-skeleton sh-function
639 "Insert a function definition in the current shell's syntax."
645 "function " str " {" \n
649 "function " str "() {" \n
655 (define-sh-skeleton sh-if
656 "Insert an if statement in the current shell's syntax."
658 "if [ " str " ]; then" \n
660 ( "other condition, %s: "
661 < "elif [ " str " ]; then" \n
670 "if( " str " ) then" \n
672 ( "other condition, %s: "
673 < "else if ( " str " ) then" \n
683 (define-sh-skeleton sh-select
684 "Insert a select statement in the current shell's syntax."
685 (ksh "Index variable: "
686 "select " str " in " _ "; do" \n
689 (put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
693 (define-sh-skeleton sh-until
694 "Insert an until loop in the current shell's syntax."
696 "until [ " str " ]; do" \n
701 (put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
704 (define-sh-skeleton sh-while
705 "Insert a while loop in the current shell's syntax."
707 "while [ " str " ]; do" \n
713 "while( " str " )" \n
720 (defun sh-query-for-variable (arg)
721 "Unless quoted with `\\', query for variable-name with completions.
722 Prefix arg 0 means don't insert `$' before the variable.
723 Prefix arg 2 or more means only do self-insert that many times.
724 If { is pressed as the first character, it will surround the variable name."
726 (or (prog1 (or (> arg 1)
728 (self-insert-command arg))
729 (let (completion-ignore-case
730 (minibuffer-local-completion-map
731 (or (get 'sh-query-for-variable 'keymap)
732 (put 'sh-query-for-variable 'keymap
733 (copy-keymap minibuffer-local-completion-map))))
734 (buffer (current-buffer)))
735 ;; local function that depends on `arg' and `buffer'
736 (define-key minibuffer-local-completion-map "{"
737 (lambda () (interactive)
738 (if (or arg (> (point) 1))
740 (save-window-excursion
742 (switch-to-buffer-other-window buffer)
746 (sh-remember-variable
747 (completing-read "Variable: " sh-variables
749 (or (not (cdr element))
750 (memq sh-shell (cdr element))))))
751 (if (eq t arg) (forward-char 1))))
752 (if (eq t arg) (forward-char 1)))))
756 (defun sh-assignment (arg)
757 "Insert self. Remember previous identifier for future completing read."
760 (sh-remember-variable
764 (if (memq sh-shell sh-assignment-space)
765 (skip-chars-backward " \t"))
768 (skip-chars-backward "a-zA-Z0-9_")
770 (self-insert-command arg))
774 (defun sh-maybe-here-document (arg)
775 "Inserts self. Without prefix, following unquoted `<' inserts here document.
776 The document is bounded by `sh-here-document-word'."
778 (self-insert-command (prefix-numeric-value arg))
780 (not (eq (char-after (- (point) 2)) last-command-char))
782 (goto-char (- (point) 2))
785 (insert sh-here-document-word)
786 (or (looking-at "[ \t\n]") (insert ? ))
789 (save-excursion (insert ?\n sh-here-document-word)))))
792 ;; various other commands
794 (autoload 'comint-dynamic-complete-filename "comint"
795 "Dynamically complete the filename at point." t)
799 (defun sh-newline-and-indent (&optional arg)
800 "Strip unquoted whitespace, insert newline, and indent like current line.
801 Unquoted whitespace is stripped from the current line's end, unless a
802 prefix ARG is given."
804 (let ((previous (current-indentation))
805 (end-of-line (point)))
807 (skip-chars-backward " \t")
808 (and (< (point) end-of-line)
811 (delete-region (point) end-of-line))
813 (indent-to previous)))
817 (defun sh-set-shell (shell)
818 "Set this buffer's shell to SHELL (a string).
819 Calls the value of `sh-set-shell-hook' if set."
820 (interactive "sName or path of shell: ")
822 (goto-char (point-min))
823 (setq sh-shell-path (if (file-name-absolute-p shell)
825 (or (sh-executable shell)
826 (error "Cannot find %s." shell)))
827 sh-shell (intern (file-name-nondirectory sh-shell-path))
828 sh-shell-is-csh (memq sh-shell '(csh tcsh))
830 (let ((keywords (intern-soft (format "sh-%s-font-lock-keywords"
832 (list (if (and keywords (boundp keywords))
834 'sh-font-lock-keywords)))
835 comment-start-skip (if sh-shell-is-csh
836 "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*"
837 "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*")
838 mode-line-process (format ": %s" sh-shell)
839 shell (concat sh-shell-path
840 (and sh-shell-argument " ")
842 (and (not buffer-read-only)
843 (not (if buffer-file-name
844 (string-match sh-magicless-file-regexp buffer-file-name)))
845 ;; find-file is set by `normal-mode' when called by `after-find-file'
846 (if (and (boundp 'find-file) find-file) sh-find-file-modifies t)
847 (if (looking-at "#!")
848 (and (skip-chars-forward "#! \t")
850 (buffer-substring (point)
851 (save-excursion (end-of-line)
853 (if sh-query-for-magic
854 (y-or-n-p (concat "Replace magic number by ``#! "
856 (message "Magic number ``%s'' replaced."
857 (buffer-substring (point-min) (point))))
858 (not (delete-region (point) (progn (end-of-line) (point))))
860 (if (if sh-query-for-magic
861 (y-or-n-p (concat "Add ``#! " shell "''? "))
863 (insert "#! " shell ?\n)))))
864 (run-hooks 'sh-set-shell-hook))
868 (defun sh-beginning-of-command ()
869 "Move point to successive beginnings of commands."
871 (if (re-search-backward sh-beginning-of-command nil t)
872 (goto-char (match-beginning 2))))
876 (defun sh-end-of-command ()
877 "Move point to successive ends of commands."
879 (if (re-search-forward sh-end-of-command nil t)
880 (goto-char (match-end 1))))
884 (defun sh-manual-entry (arg)
885 "Display the Unix manual entry for the current command or shell.
886 Universal argument ARG, is passed to `Man-getpage-in-background'."
888 (let ((command (save-excursion
889 (sh-beginning-of-command)
891 (buffer-substring (point)
892 (progn (forward-sexp) (point)))))))
893 (setq command (read-input (concat "Manual entry (default "
894 (symbol-name sh-shell)
897 (file-name-nondirectory command))))
898 (manual-entry (if (string= command "")
899 (symbol-name sh-shell)
903 ;; sh-script.el ends here