Bug fix in menu code for XEmacs.
[bpt/emacs.git] / lisp / progmodes / sh-script.el
1 ;;; sh-script.el --- shell-script editing commands for Emacs
2 ;; Copyright (C) 1993 Free Software Foundation, Inc.
3
4 ;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
5 ;; Maintainer: FSF
6 ;; Keywords: shell programming
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24 ;;; Commentary:
25
26 ;; Major mode for editing shell scripts. Currently sh, ksh, bash and csh,
27 ;; tcsh are supported. Structured statements can be inserted with one
28 ;; command.
29
30 ;; Autoloading of these functions is currently turned off
31 ;; because it's not clear whether this mode is really desirable to use.
32 ;; -- rms
33
34 ;;; Code:
35
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
40
41
42 ;;;###dont-autoload
43 (setq auto-mode-alist
44 ;; matches files
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)
51 auto-mode-alist)))
52
53
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)
69 table)
70 "Syntax table in use in Shell-Script mode.")
71
72
73
74 (defvar sh-use-prefix nil
75 "If non-nil when loading, `$' and `<' will be C-c $ and C-c < .")
76
77 (defvar sh-mode-map
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)
87
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)
100
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))
136 map)
137 "Keymap used in Shell-Script mode.")
138
139
140
141 (defvar sh-find-file-modifies t
142 "*What to do when newly found file has no magic number:
143 nil do nothing
144 t insert magic number
145 other insert magic number, but mark as unmodified.")
146
147
148 (defvar sh-query-for-magic t
149 "*If non-nil, ask user before changing or inserting magic number.")
150
151
152 (defvar sh-magicless-file-regexp "/\\.[^/]+$"
153 "*On files with this kind of name no magic is inserted or changed.")
154
155
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'.")
163
164
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.")
169
170
171
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.")
176
177
178 (defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
179 "*The executable of the shell being programmed.")
180
181 (defvar sh-shell-argument nil
182 "*A single argument for the magic number, or nil.")
183
184 (defvar sh-shell nil
185 "The shell being programmed. This is set by \\[sh-set-shell].")
186
187 (defvar sh-shell-is-csh nil
188 "The shell being programmed. This is set by \\[sh-set-shell].")
189
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.")
193
194 (defvar sh-remember-variable-min 3
195 "*Don't remember variables less than this length for completing reads.")
196
197
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\\).")
202
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\\).")
207
208
209
210 (defvar sh-assignment-space '(csh tcsh)
211 "List of shells that allow spaces around the assignment =.")
212
213 (defvar sh-here-document-word "+"
214 "Word to delimit here documents.")
215
216
217 ;process-environment
218 (defvar sh-variables
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)
277 ("TERM") ("TERMCAP")
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)
284 ("wordchars" 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.")
287
288
289
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,
297 this one is used.")
298
299
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.")
304
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.")
309
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.")
314
315
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.")
320
321 (defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
322 "*Rules for highlighting Toronto C shell scripts.")
323
324
325 \f
326 ;; mode-command and utility functions
327
328 ;;;###dont-autoload
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))
334 (while (and l
335 (not (looking-at (car (car l)))))
336 (setq l (cdr l)))
337 (cdr (car l)))
338 'sh-mode)))
339
340
341 ;;;###dont-autoload
342 (defun sh-mode ()
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.
348
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
352
353 (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
354 or
355 (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
356
357 where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
358
359 The following commands are available, based on the current shell's syntax:
360
361 \\[sh-case] case statement
362 \\[sh-for] for loop
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
369
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.
376
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.
379 {, (, [, ', \", `
380 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
381 (interactive)
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
402 comment-start "# "
403 after-save-hook 'sh-chmod
404 tab-width sh-tab-width
405 ;; C shells do
406 require-final-newline t
407 pair-alist '((?` _ ?`))
408 pair-filter 'sh-quoted-p)
409 ;; parse or insert magic number for exec
410 (save-excursion
411 (goto-char (point-min))
412 (sh-set-shell
413 (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
414 (buffer-substring (match-beginning 1) (match-end 1))
415 sh-shell-path)))
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))
421 ;;;###dont-autoload
422 (defalias 'shell-script-mode 'sh-mode)
423
424
425
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).
431
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)
438 documentation ""))
439 ;; The compiled version doesn't.
440 (require 'backquote)
441 (`(progn
442 (let ((definitions '(, definitions)))
443 (while 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) ()
454 (, documentation)
455 (interactive)
456 (skeleton-insert
457 (or (get '(, command) sh-shell)
458 (error "%s statement syntax not defined for shell %s."
459 '(, command) sh-shell)))))))
460
461
462
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."
466 (interactive)
467 (let ((previous (save-excursion
468 (line-move -1)
469 (back-to-indentation)
470 (if (looking-at comment-start-skip)
471 0
472 (current-column)))))
473 (save-excursion
474 (indent-to (if (eq this-command 'newline-and-indent)
475 previous
476 (if (< (current-column)
477 (progn (back-to-indentation)
478 (current-column)))
479 (if (eolp) previous 0)
480 (if (eolp)
481 (max previous (* (1+ (/ (current-column) tab-width))
482 tab-width))
483 (* (1+ (/ (current-column) tab-width)) tab-width))))))
484 (if (< (current-column) (current-indentation))
485 (skip-chars-forward " \t"))))
486
487
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)))
493 var)
494
495
496 ;; Augment the standard variables by those found in the environment.
497 (if (boundp 'process-environment)(let ((l process-environment))
498 (while l
499 (sh-remember-variable (substring (car l)
500 0 (string-match "=" (car l))))
501 (setq l (cdr l)))))
502
503
504
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 "\\\\")
509 (point)))
510 2)))
511
512
513
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)
521 (goto-char point)))
522 (prog1
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))))
528
529
530
531 (defun sh-chmod ()
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)))))
538 \f
539 ;; statement syntax-commands for various shells
540
541 ;; You are welcome to add the syntax or even completely new statements as
542 ;; appropriate for your favorite shell.
543
544 (define-sh-skeleton sh-case
545 "Insert a case/switch statement in the current shell's syntax."
546 (sh "expression: "
547 "case " str " in" \n
548 > (read-string "pattern: ") ?\) \n
549 > _ \n
550 ";;" \n
551 ( "other pattern, %s: "
552 < str ?\) \n
553 > \n
554 ";;" \n)
555 < "*)" \n
556 > \n
557 resume:
558 < < "esac")
559 (ksh . sh)
560 (bash . sh)
561 (csh "expression: "
562 "switch( " str " )" \n
563 > "case " (read-string "pattern: ") ?: \n
564 > _ \n
565 "breaksw" \n
566 ( "other pattern, %s: "
567 < "case " str ?: \n
568 > \n
569 "breaksw" \n)
570 < "default:" \n
571 > \n
572 resume:
573 < < "endsw")
574 (tcsh . csh))
575
576
577
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
583 < "done")
584 (ksh . sh)
585 (bash . sh)
586 (csh "Index variable: "
587 "foreach " str " ( " _ " )" \n
588 > ?$ (sh-remember-variable str) \n
589 < "end")
590 (tcsh . csh))
591
592
593
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: "
597 str "=1" \n
598 "while [ $" str " -le "
599 (read-string "upper limit: ")
600 " ]; do" \n
601 > _ ?$ str \n
602 str ?= (sh-add (sh-remember-variable str) 1) \n
603 < "done")
604 (ksh . sh)
605 (bash . sh)
606 (csh "Index variable: "
607 "@ " str " = 1" \n
608 "while( $" str " <= "
609 (read-string "upper limit: ")
610 " )" \n
611 > _ ?$ (sh-remember-variable str) \n
612 "@ " str "++" \n
613 < "end")
614 (tcsh . csh))
615
616
617
618 (defun sh-add (var delta)
619 "Insert an addition of VAR and prefix DELTA for Bourne type shells."
620 (interactive
621 (list (sh-remember-variable
622 (completing-read "Variable: " sh-variables
623 (lambda (element)
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) " - " " + ")
628 (abs delta)))
629 (skeleton-insert
630 (assq sh-shell
631 '((sh "`expr $" var delta "`")
632 (ksh "$(( $" var delta " ))")
633 (bash "$[ $" var delta " ]")))
634 t))
635
636
637
638 (define-sh-skeleton sh-function
639 "Insert a function definition in the current shell's syntax."
640 (sh ()
641 "() {" \n
642 > _ \n
643 < "}")
644 (ksh "name: "
645 "function " str " {" \n
646 > _ \n
647 < "}")
648 (bash "name: "
649 "function " str "() {" \n
650 > _ \n
651 < "}"))
652
653
654
655 (define-sh-skeleton sh-if
656 "Insert an if statement in the current shell's syntax."
657 (sh "condition: "
658 "if [ " str " ]; then" \n
659 > _ \n
660 ( "other condition, %s: "
661 < "elif [ " str " ]; then" \n
662 > \n)
663 < "else" \n
664 > \n
665 resume:
666 < "fi")
667 (ksh . sh)
668 (bash . sh)
669 (csh "condition: "
670 "if( " str " ) then" \n
671 > _ \n
672 ( "other condition, %s: "
673 < "else if ( " str " ) then" \n
674 > \n)
675 < "else" \n
676 > \n
677 resume:
678 < "endif")
679 (tcsh . csh))
680
681
682
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
687 > ?$ str \n
688 < "done"))
689 (put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
690
691
692
693 (define-sh-skeleton sh-until
694 "Insert an until loop in the current shell's syntax."
695 (sh "condition: "
696 "until [ " str " ]; do" \n
697 > _ \n
698 < "done")
699 (ksh . sh)
700 (bash . sh))
701 (put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
702
703
704 (define-sh-skeleton sh-while
705 "Insert a while loop in the current shell's syntax."
706 (sh "condition: "
707 "while [ " str " ]; do" \n
708 > _ \n
709 < "done")
710 (ksh . sh)
711 (bash . sh)
712 (csh "condition: "
713 "while( " str " )" \n
714 > _ \n
715 < "end")
716 (tcsh . csh))
717
718
719
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."
725 (interactive "*p")
726 (or (prog1 (or (> arg 1)
727 (sh-quoted-p))
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))
739 (beep)
740 (save-window-excursion
741 (setq arg t)
742 (switch-to-buffer-other-window buffer)
743 (insert "{}")))))
744 (insert
745 (prog1
746 (sh-remember-variable
747 (completing-read "Variable: " sh-variables
748 (lambda (element)
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)))))
753
754
755
756 (defun sh-assignment (arg)
757 "Insert self. Remember previous identifier for future completing read."
758 (interactive "p")
759 (if (eq arg 1)
760 (sh-remember-variable
761 (save-excursion
762 (buffer-substring
763 (progn
764 (if (memq sh-shell sh-assignment-space)
765 (skip-chars-backward " \t"))
766 (point))
767 (progn
768 (skip-chars-backward "a-zA-Z0-9_")
769 (point))))))
770 (self-insert-command arg))
771
772
773
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'."
777 (interactive "*P")
778 (self-insert-command (prefix-numeric-value arg))
779 (or arg
780 (not (eq (char-after (- (point) 2)) last-command-char))
781 (save-excursion
782 (goto-char (- (point) 2))
783 (sh-quoted-p))
784 (progn
785 (insert sh-here-document-word)
786 (or (looking-at "[ \t\n]") (insert ? ))
787 (end-of-line 1)
788 (newline)
789 (save-excursion (insert ?\n sh-here-document-word)))))
790
791 \f
792 ;; various other commands
793
794 (autoload 'comint-dynamic-complete-filename "comint"
795 "Dynamically complete the filename at point." t)
796
797
798
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."
803 (interactive "*P")
804 (let ((previous (current-indentation))
805 (end-of-line (point)))
806 (if arg ()
807 (skip-chars-backward " \t")
808 (and (< (point) end-of-line)
809 (sh-quoted-p)
810 (forward-char 1))
811 (delete-region (point) end-of-line))
812 (newline)
813 (indent-to previous)))
814
815
816
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: ")
821 (save-excursion
822 (goto-char (point-min))
823 (setq sh-shell-path (if (file-name-absolute-p shell)
824 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))
829 font-lock-defaults
830 (let ((keywords (intern-soft (format "sh-%s-font-lock-keywords"
831 sh-shell))))
832 (list (if (and keywords (boundp keywords))
833 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 " ")
841 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")
849 (not (string= shell
850 (buffer-substring (point)
851 (save-excursion (end-of-line)
852 (point)))))
853 (if sh-query-for-magic
854 (y-or-n-p (concat "Replace magic number by ``#! "
855 shell "''? "))
856 (message "Magic number ``%s'' replaced."
857 (buffer-substring (point-min) (point))))
858 (not (delete-region (point) (progn (end-of-line) (point))))
859 (insert shell))
860 (if (if sh-query-for-magic
861 (y-or-n-p (concat "Add ``#! " shell "''? "))
862 t)
863 (insert "#! " shell ?\n)))))
864 (run-hooks 'sh-set-shell-hook))
865
866
867
868 (defun sh-beginning-of-command ()
869 "Move point to successive beginnings of commands."
870 (interactive)
871 (if (re-search-backward sh-beginning-of-command nil t)
872 (goto-char (match-beginning 2))))
873
874
875
876 (defun sh-end-of-command ()
877 "Move point to successive ends of commands."
878 (interactive)
879 (if (re-search-forward sh-end-of-command nil t)
880 (goto-char (match-end 1))))
881
882
883
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'."
887 (interactive "P")
888 (let ((command (save-excursion
889 (sh-beginning-of-command)
890 (sh-executable
891 (buffer-substring (point)
892 (progn (forward-sexp) (point)))))))
893 (setq command (read-input (concat "Manual entry (default "
894 (symbol-name sh-shell)
895 "): ")
896 (if command
897 (file-name-nondirectory command))))
898 (manual-entry (if (string= command "")
899 (symbol-name sh-shell)
900 command)
901 arg)))
902
903 ;; sh-script.el ends here