xref: /386bsd/usr/local/lib/emacs/19.25/lisp/sh-script.el (revision a2142627)
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;;; Code:
31
32;; page 1:	variables and settings
33;; page 2:	mode-command and utility functions
34;; page 3:	statement syntax-commands for various shells
35;; page 4:	various other commands
36
37
38;;;###autoload
39(setq auto-mode-alist
40      ;; matches files
41      ;;	- who's path contains /bin/, but not directories
42      (cons '("/bin/" . sh-or-other-mode)
43	    ;;	- that have a suffix .sh or .shar (shell archive)
44	    ;;	- that contain ressources for the various shells
45	    ;;	- startup files for X11
46	    (cons '("\\.sh$\\|\\.shar$\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)$" . sh-mode)
47		  auto-mode-alist)))
48
49
50(defvar sh-mode-syntax-table
51  (let ((table (copy-syntax-table)))
52    (modify-syntax-entry ?\# "<" table)
53    (modify-syntax-entry ?\^l ">#" table)
54    (modify-syntax-entry ?\n ">#" table)
55    (modify-syntax-entry ?\" "\"\"" table)
56    (modify-syntax-entry ?\' "\"'" table)
57    (modify-syntax-entry ?\` "$`" table)
58    (modify-syntax-entry ?$ "_" 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    table)
66  "Syntax table in use in Shell-Script mode.")
67
68
69
70(defvar sh-use-prefix nil
71  "If non-nil when loading, `$' and `<' will be  C-c $  and  C-c < .")
72
73(defvar sh-mode-map
74  (let ((map (make-sparse-keymap)))
75    (define-key map "\C-c(" 'sh-function)
76    (define-key map "\C-c\C-w" 'sh-while)
77    (define-key map "\C-c\C-u" 'sh-until)
78    (define-key map "\C-c\C-s" 'sh-select)
79    (define-key map "\C-c\C-l" 'sh-indexed-loop)
80    (define-key map "\C-c\C-i" 'sh-if)
81    (define-key map "\C-c\C-f" 'sh-for)
82    (define-key map "\C-c\C-c" 'sh-case)
83
84    (define-key map (if sh-use-prefix "\C-c$" "$")
85      'sh-query-for-variable)
86    (define-key map "=" 'sh-assignment)
87    (define-key map "\C-c+" 'sh-add)
88    (define-key map (if sh-use-prefix "\C-c<" "<")
89      'sh-maybe-here-document)
90    (define-key map "(" 'pair-insert-maybe)
91    (define-key map "{" 'pair-insert-maybe)
92    (define-key map "[" 'pair-insert-maybe)
93    (define-key map "'" 'pair-insert-maybe)
94    (define-key map "`" 'pair-insert-maybe)
95    (define-key map "\"" 'pair-insert-maybe)
96
97    (define-key map "\t" 'sh-indent-line)
98    (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename
99			       map (current-global-map))
100    (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
101			       map (current-global-map))
102    ;; Now that tabs work properly, this might be unwanted.
103    (substitute-key-definition 'delete-backward-char
104			       'backward-delete-char-untabify
105			       map (current-global-map))
106    (define-key map "\C-c:" 'sh-set-shell)
107    (substitute-key-definition 'beginning-of-defun
108			       'sh-beginning-of-compound-command
109			       map (current-global-map))
110    (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
111			       map (current-global-map))
112    (substitute-key-definition 'forward-sentence 'sh-end-of-command
113			       map (current-global-map))
114    (substitute-key-definition 'manual-entry 'sh-manual-entry
115			       map (current-global-map))
116    (define-key map [menu-bar insert]
117      (cons "Insert" (make-sparse-keymap "Insert")))
118    (define-key map [menu-bar insert sh-while]
119      '("While loop" . sh-while))
120    (define-key map [menu-bar insert sh-until]
121      '("Until loop" . sh-until))
122    (define-key map [menu-bar insert sh-select]
123      '("Select statement" . sh-select))
124    (define-key map [menu-bar insert sh-indexed-loop]
125      '("Indexed loop" . sh-indexed-loop))
126    (define-key map [menu-bar insert sh-if]
127      '("If statement" . sh-if))
128    (define-key map [menu-bar insert sh-for]
129      '("For loop" . sh-for))
130    (define-key map [menu-bar insert sh-case]
131      '("Case statement" . sh-case))
132    map)
133  "Keymap used in Shell-Script mode.")
134
135
136
137(defvar sh-find-file-modifies t
138  "*What to do when newly found file has no magic number:
139	nil	do nothing
140	t	insert magic number
141	other	insert magic number, but mark as unmodified.")
142
143
144(defvar sh-query-for-magic t
145  "*If non-nil, ask user before changing or inserting magic number.")
146
147
148(defvar sh-magicless-file-regexp "/\\.[^/]+$"
149  "*On files with this kind of name no magic is inserted or changed.")
150
151
152;; someone who understands /etc/magic better than me should beef this up
153;; this currently covers only SCO Unix and Sinix executables
154;; the elegant way would be to read /etc/magic
155(defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode)
156			     ("#!.*perl" . perl-mode))
157  "A regexp to match the magic number of a found file.
158Currently this is only used by function `sh-or-other-mode'.")
159
160
161(defvar sh-executable ".* is \\([^ \t]*\\)\n"
162  "*Regexp to match the output of sh builtin `type' command on your machine.
163The regexp must match the whole output, and must contain a \\(something\\)
164construct which matches the actual executable.")
165
166
167
168(defvar sh-chmod-argument "755"
169  "*After saving, if the file is not executable, set this mode.
170The mode can be absolute \"511\" or relative \"u+x\".  Do nothing if this is nil.")
171
172
173(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
174  "*The executable of the shell being programmed.")
175
176(defvar sh-shell-argument nil
177  "*A single argument for the magic number, or nil.")
178
179(defvar sh-shell nil
180  "The shell being programmed.  This is set by \\[sh-set-shell].")
181
182(defvar sh-shell-is-csh nil
183  "The shell being programmed.  This is set by \\[sh-set-shell].")
184
185(defvar sh-tab-width 4
186  "The default value for `tab-width' in Shell-Script mode.
187This is the width of tab stops after the indentation of the preceeding line.")
188
189(defvar sh-remember-variable-min 3
190  "*Don't remember variables less than this length for completing reads.")
191
192
193(defvar sh-beginning-of-command
194  "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)"
195  "*Regexp to determine the beginning of a shell command.
196The actual command starts at the beginning of the second \\(grouping\\).")
197
198(defvar sh-end-of-command
199  "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
200  "*Regexp to determine the end of a shell command.
201The actual command ends at the end of the first \\(grouping\\).")
202
203
204
205(defvar sh-assignment-space '(csh tcsh)
206  "List of shells that allow spaces around the assignment =.")
207
208(defvar sh-here-document-word "+"
209  "Word to delimit here documents.")
210
211
212;process-environment
213(defvar sh-variables
214  '(("addsuffix" tcsh)			("allow_null_glob_expansion" bash)
215    ("ampm" tcsh)			("argv" csh tcsh)
216    ("autocorrect" tcsh)		("autoexpand" tcsh)
217    ("autolist" tcsh)			("autologout" tcsh)
218    ("auto_resume" bash)		("BASH" bash)
219    ("BASH_VERSION" bash)		("cdable_vars" bash)
220    ("cdpath" csh tcsh)			("CDPATH" sh ksh bash)
221    ("chase_symlinks" tcsh)		("child" csh tcsh)
222    ("COLUMNS" ksh tcsh)		("correct" tcsh)
223    ("dextract" tcsh)			("echo" csh tcsh)
224    ("edit" tcsh)			("EDITOR")
225    ("el" tcsh)				("ENV" ksh bash)
226    ("ERRNO" ksh)			("EUID" bash)
227    ("FCEDIT" ksh bash)			("FIGNORE" bash)
228    ("fignore" tcsh)			("FPATH" ksh)
229    ("gid" tcsh)			("glob_dot_filenames" bash)
230    ("histchars" bash csh tcsh)		("HISTFILE" ksh bash)
231    ("HISTFILESIZE" bash)		("histlit" tcsh)
232    ("history" csh tcsh)		("history_control" bash)
233    ("HISTSIZE" bash)			("home" csh tcsh)
234    ("HOME")				("HOST" tcsh)
235    ("hostname_completion_file" bash)	("HOSTTYPE" bash tcsh)
236    ("HPATH" tcsh)			("HUSHLOGIN")
237    ("IFS" sh ksh bash)			("ignoreeof" bash csh tcsh)
238    ("IGNOREEOF" bash)			("ignore_symlinks" tcsh)
239    ("LANG")				("LC_COLLATE")
240    ("LC_CTYPE")			("LC_MESSAGES")
241    ("LC_MONETARY")			("LC_NUMERIC")
242    ("LC_TIME")				("LINENO" ksh bash)
243    ("LINES" ksh tcsh)			("listjobs" tcsh)
244    ("listlinks" tcsh)			("listmax" tcsh)
245    ("LOGNAME")				("mail" csh tcsh)
246    ("MAIL")				("MAILCHECK")
247    ("MAILPATH")			("MAIL_WARNING" bash)
248    ("matchbeep" tcsh)			("nobeep" tcsh)
249    ("noclobber" bash csh tcsh)		("noglob" csh tcsh)
250    ("nolinks" bash)			("nonomatch" csh tcsh)
251    ("NOREBIND" tcsh)			("notify" bash)
252    ("no_exit_on_failed_exec" bash)	("NO_PROMPT_VARS" bash)
253    ("oid" tcsh)			("OLDPWD" ksh bash)
254    ("OPTARG" sh ksh bash)		("OPTERR" bash)
255    ("OPTIND" sh ksh bash)		("PAGER")
256    ("path" csh tcsh)			("PATH")
257    ("PPID" ksh bash)			("printexitvalue" tcsh)
258    ("prompt" csh tcsh)			("prompt2" tcsh)
259    ("prompt3" tcsh)			("PROMPT_COMMAND" bash)
260    ("PS1" sh ksh bash)			("PS2" sh ksh bash)
261    ("PS3" ksh)				("PS4" ksh bash)
262    ("pushdsilent" tcsh)		("pushdtohome" tcsh)
263    ("pushd_silent" bash)		("PWD" ksh bash)
264    ("RANDOM" ksh bash)			("recexact" tcsh)
265    ("recognize_only_executables" tcsh)	("REPLY" ksh bash)
266    ("rmstar" tcsh)			("savehist" tcsh)
267    ("SECONDS" ksh bash)		("shell" csh tcsh)
268    ("SHELL")				("SHLVL" bash tcsh)
269    ("showdots" tcsh)			("sl" tcsh)
270    ("status" csh tcsh)			("SYSTYPE" tcsh)
271    ("tcsh" tcsh)			("term" tcsh)
272    ("TERM")				("TERMCAP")
273    ("time" csh tcsh)			("TMOUT" ksh bash)
274    ("tperiod" tcsh)			("tty" tcsh)
275    ("UID" bash)			("uid" tcsh)
276    ("verbose" csh tcsh)		("version" tcsh)
277    ("visiblebell" tcsh)		("VISUAL")
278    ("watch" tcsh)			("who" tcsh)
279    ("wordchars" tcsh))
280  "Alist of all environment and shell variables used for completing read.
281Variables only understood by some shells are associated to a list of those.")
282
283
284
285(defvar sh-font-lock-keywords
286  '(("[ \t]\\(#.*\\)" 1 font-lock-comment-face)
287    ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face))
288  "*Rules for highlighting shell scripts.
289This variable is included into the various variables
290`sh-SHELL-font-lock-keywords'.  If no such variable exists for some shell,
291this one is used.")
292
293
294(defvar sh-sh-font-lock-keywords
295  (append sh-font-lock-keywords
296	  '(("\\(^\\|[^-._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)))
297  "*Rules for highlighting Bourne shell scripts.")
298
299(defvar sh-ksh-font-lock-keywords
300  (append sh-sh-font-lock-keywords
301	  '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
302  "*Rules for highlighting Korn shell scripts.")
303
304(defvar sh-bash-font-lock-keywords
305  (append sh-sh-font-lock-keywords
306	  '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
307  "*Rules for highlighting Bourne again shell scripts.")
308
309
310(defvar sh-csh-font-lock-keywords
311  (append sh-font-lock-keywords
312	  '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
313  "*Rules for highlighting C shell scripts.")
314
315(defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
316  "*Rules for highlighting Toronto C shell scripts.")
317
318
319
320;; mode-command and utility functions
321
322;;;###autoload
323(defun sh-or-other-mode ()
324  "Decide whether this is a compiled executable or a script.
325Usually the file-names of scripts and binaries cannot be automatically
326distinguished, so the presence of an executable's magic number is used."
327  (funcall (or (let ((l magic-number-alist))
328		 (while (and l
329			     (not (looking-at (car (car l)))))
330		   (setq l (cdr l)))
331		 (cdr (car l)))
332	       'sh-mode)))
333
334
335;;;###autoload
336(defun sh-mode ()
337  "Major mode for editing shell scripts.
338This mode works for many shells, since they all have roughly the same syntax,
339as far as commands, arguments, variables, pipes, comments etc. are concerned.
340Unless the file's magic number indicates the shell, your usual shell is
341assumed.  Since filenames rarely give a clue, they are not further analyzed.
342
343The syntax of the statements varies with the shell being used.  The syntax of
344statements can be modified by putting a property on the command or new ones
345defined with `define-sh-skeleton'.  For example
346
347    (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
348or
349    (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
350
351where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
352
353The following commands are available, based on the current shell's syntax:
354
355\\[sh-case]	 case statement
356\\[sh-for]	 for loop
357\\[sh-function]	 function definition
358\\[sh-if]	 if statement
359\\[sh-indexed-loop]	 indexed loop from 1 to n
360\\[sh-select]	 select statement
361\\[sh-until]	 until loop
362\\[sh-while]	 while loop
363
364\\[backward-delete-char-untabify]	 Delete backward one position, even if it was a tab.
365\\[sh-newline-and-indent]	 Delete unquoted space and indent new line same as this one.
366\\[sh-end-of-command]	 Go to end of successive commands.
367\\[sh-beginning-of-command]	 Go to beginning of successive commands.
368\\[sh-set-shell]	 Set this buffer's shell, and maybe its magic number.
369\\[sh-manual-entry]	 Display the Unix manual entry for the current command or shell.
370
371\\[sh-query-for-variable]	 Unless quoted with \\, query for a variable with completions offered.
372\\[sh-maybe-here-document]	 Without prefix, following an unquoted < inserts here document.
373{, (, [, ', \", `
374	Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
375  (interactive)
376  (kill-all-local-variables)
377  (set-syntax-table sh-mode-syntax-table)
378  (use-local-map sh-mode-map)
379  (make-local-variable 'indent-line-function)
380  (make-local-variable 'comment-start)
381  (make-local-variable 'comment-start-skip)
382  (make-local-variable 'after-save-hook)
383  (make-local-variable 'require-final-newline)
384  (make-local-variable 'sh-shell-path)
385  (make-local-variable 'sh-shell)
386  (make-local-variable 'sh-shell-is-csh)
387  (make-local-variable 'pair-alist)
388  (make-local-variable 'pair-filter)
389  (make-local-variable 'font-lock-keywords)
390  (make-local-variable 'font-lock-keywords-case-fold-search)
391  (make-local-variable 'sh-variables)
392  (setq major-mode 'sh-mode
393	mode-name "Shell-script"
394	;; Why can't Emacs have one standard function with some parameters?
395	;; Only few modes actually analyse the previous line's contents
396	indent-line-function 'sh-indent-line
397	comment-start "# "
398	after-save-hook 'sh-chmod
399	tab-width sh-tab-width
400	;; C shells do
401	require-final-newline t
402	font-lock-keywords-case-fold-search nil
403	pair-alist '((?` _ ?`))
404	pair-filter 'sh-quoted-p)
405  ; parse or insert magic number for exec()
406  (goto-char (point-min))
407  (sh-set-shell
408   (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
409       (buffer-substring (match-beginning 1) (match-end 1))
410     sh-shell-path))
411  ;; find-file is set by `normal-mode' when called by `after-find-file'
412  (and (boundp 'find-file) find-file
413       (or (eq sh-find-file-modifies t)
414	   (set-buffer-modified-p nil)))
415  (run-hooks 'sh-mode-hook))
416;;;###autoload
417(defalias 'shell-script-mode 'sh-mode)
418
419
420
421(defmacro define-sh-skeleton (command documentation &rest definitions)
422  "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ...
423Prior definitions (e.g. from ~/.emacs) are maintained.
424Each definition is built up as (SHELL PROMPT ELEMENT ...).  Alternately
425a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
426
427For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
428Each DEFINITION is actually stored as
429	(put COMMAND SHELL (PROMPT ELEMENT ...)),
430which you can also do yourself."
431  (or (stringp documentation)
432      (setq definitions (cons documentation definitions)
433	    documentation ""))
434  ;; The compiled version doesn't.
435  (require 'backquote)
436  (`(progn
437      (let ((definitions '(, definitions)))
438	(while definitions
439	  ;; skeleton need not be loaded to define these
440	  (or (and (not (if (boundp 'skeleton-debug) skeleton-debug))
441		   (get '(, command) (car (car definitions))))
442	      (put '(, command) (car (car definitions))
443		   (if (symbolp (cdr (car definitions)))
444		       (get '(, command) (cdr (car definitions)))
445		     (cdr (car definitions)))))
446	  (setq definitions (cdr definitions))))
447      (put '(, command) 'menu-enable '(get '(, command) sh-shell))
448      (defun (, command) ()
449	(, documentation)
450	(interactive)
451	(skeleton-insert
452	 (or (get '(, command) sh-shell)
453	     (error "%s statement syntax not defined for shell %s."
454		    '(, command) sh-shell)))))))
455
456
457
458(defun sh-indent-line ()
459  "Indent as far as preceding line, then by steps of `tab-width'.
460If previous line starts with a comment, it's considered empty."
461  (interactive)
462  (let ((previous (save-excursion
463		    (line-move -1)
464		    (back-to-indentation)
465		    (if (looking-at comment-start-skip)
466			0
467		      (current-column)))))
468    (save-excursion
469      (indent-to (if (eq this-command 'newline-and-indent)
470		     previous
471		   (if (< (current-column)
472			  (progn (back-to-indentation)
473				 (current-column)))
474		       (if (eolp) previous 0)
475		     (if (eolp)
476			 (max previous (* (1+ (/ (current-column) tab-width))
477					  tab-width))
478		       (* (1+ (/ (current-column) tab-width)) tab-width))))))
479      (if (< (current-column) (current-indentation))
480	  (skip-chars-forward " \t"))))
481
482
483(defun sh-remember-variable (var)
484  "Make VARIABLE available for future completing reads in this buffer."
485  (or (< (length var) sh-remember-variable-min)
486      (assoc var sh-variables)
487      (setq sh-variables (cons (list var) sh-variables)))
488  var)
489
490
491;; Augment the standard variables by those found in the environment.
492(if (boundp 'process-environment)(let ((l process-environment))
493  (while l
494    (sh-remember-variable (substring (car l)
495				     0 (string-match "=" (car l))))
496    (setq l (cdr l)))))
497
498
499
500(defun sh-quoted-p ()
501  "Is point preceded by an odd number of backslashes?"
502  (eq 1 (% (- (point) (save-excursion
503			(skip-chars-backward "\\\\")
504			(point)))
505	   2)))
506
507
508
509(defun sh-executable (command)
510  "If COMMAND is an executable in $PATH its full name is returned.  Else nil."
511  (let ((point (point))
512	(buffer-modified-p (buffer-modified-p))
513	buffer-read-only after-change-function)
514    (call-process "sh" nil t nil "-c" (concat "type " command))
515    (setq point (prog1 (point)
516		  (goto-char point)))
517    (prog1
518	(and (looking-at sh-executable)
519	     (eq point (match-end 0))
520	     (buffer-substring (match-beginning 1) (match-end 1)))
521      (delete-region (point) point)
522      (set-buffer-modified-p buffer-modified-p))))
523
524
525
526(defun sh-chmod ()
527  "This gets called after saving a file to assure that it be executable.
528You can set the absolute or relative mode with `sh-chmod-argument'."
529  (if sh-chmod-argument
530       (or (file-executable-p buffer-file-name)
531	   (shell-command (concat "chmod " sh-chmod-argument
532				  " " buffer-file-name)))))
533
534;; statement syntax-commands for various shells
535
536;; You are welcome to add the syntax or even completely new statements as
537;; appropriate for your favorite shell.
538
539(define-sh-skeleton sh-case
540  "Insert a case/switch statement in the current shell's syntax."
541  (sh "expression: "
542      "case " str " in" \n
543      > (read-string "pattern: ") ?\) \n
544      > _ \n
545      ";;" \n
546      ( "other pattern, %s: "
547	< str ?\) \n
548	> \n
549	";;" \n)
550      < "*)" \n
551      > \n
552      resume:
553      < < "esac")
554  (ksh . sh)
555  (bash . sh)
556  (csh "expression: "
557       "switch( " str " )" \n
558       > "case " (read-string "pattern: ") ?: \n
559       > _ \n
560       "breaksw" \n
561       ( "other pattern, %s: "
562	 < "case " str ?: \n
563	 > \n
564	 "breaksw" \n)
565       < "default:" \n
566       > \n
567       resume:
568       < < "endsw")
569  (tcsh . csh))
570
571
572
573(define-sh-skeleton sh-for
574  "Insert a for loop in the current shell's syntax."
575  (sh "Index variable: "
576      "for " str " in " _ "; do" \n
577      > ?$ (sh-remember-variable str) \n
578      < "done")
579  (ksh . sh)
580  (bash . sh)
581  (csh "Index variable: "
582       "foreach " str " ( " _ " )" \n
583       > ?$ (sh-remember-variable str) \n
584       < "end")
585  (tcsh . csh))
586
587
588
589(define-sh-skeleton sh-indexed-loop
590  "Insert an indexed loop from 1 to n in the current shell's syntax."
591  (sh "Index variable: "
592      str "=1" \n
593      "while [ $" str " -le "
594      (read-string "upper limit: ")
595      " ]; do" \n
596      > _ ?$ str \n
597      str ?= (sh-add (sh-remember-variable str) 1) \n
598      < "done")
599  (ksh . sh)
600  (bash . sh)
601  (csh "Index variable: "
602       "@ " str " = 1" \n
603       "while( $" str " <= "
604       (read-string "upper limit: ")
605       " )" \n
606       > _ ?$ (sh-remember-variable str) \n
607       "@ " str "++" \n
608       < "end")
609  (tcsh . csh))
610
611
612
613(defun sh-add (var delta)
614  "Insert an addition of VAR and prefix DELTA for Bourne type shells."
615  (interactive
616   (list (sh-remember-variable
617	  (completing-read "Variable: " sh-variables
618			   (lambda (element)
619			     (or (not (cdr element))
620				 (memq sh-shell (cdr element))))))
621	 (prefix-numeric-value current-prefix-arg)))
622  (setq delta (concat (if (< delta 0) " - " " + ")
623		      (abs delta)))
624  (skeleton-insert
625   (assq sh-shell
626	 '((sh "`expr $" var delta "`")
627	   (ksh "$(( $" var delta " ))")
628	   (bash "$[ $" var delta " ]")))
629   t))
630
631
632
633(define-sh-skeleton sh-function
634  "Insert a function definition in the current shell's syntax."
635  (sh ()
636      "() {" \n
637      > _ \n
638      < "}")
639  (ksh "name: "
640       "function " str " {" \n
641       > _ \n
642       < "}")
643  (bash "name: "
644       "function " str "() {" \n
645       > _ \n
646       < "}"))
647
648
649
650(define-sh-skeleton sh-if
651  "Insert an if statement in the current shell's syntax."
652  (sh "condition: "
653      "if [ " str " ]; then" \n
654      > _ \n
655      ( "other condition, %s: "
656	< "elif [ " str " ]; then" \n
657	> \n)
658      < "else" \n
659      > \n
660      resume:
661      < "fi")
662  (ksh . sh)
663  (bash . sh)
664  (csh "condition: "
665       "if( " str " ) then" \n
666       > _ \n
667       ( "other condition, %s: "
668	 < "else if ( " str " ) then" \n
669	 > \n)
670       < "else" \n
671       > \n
672       resume:
673       < "endif")
674  (tcsh . csh))
675
676
677
678(define-sh-skeleton sh-select
679  "Insert a select statement in the current shell's syntax."
680  (ksh "Index variable: "
681       "select " str " in " _ "; do" \n
682       > ?$ str \n
683       < "done"))
684(put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
685
686
687
688(define-sh-skeleton sh-until
689  "Insert an until loop in the current shell's syntax."
690  (sh "condition: "
691      "until [ " str " ]; do" \n
692      > _ \n
693      < "done")
694  (ksh . sh)
695  (bash . sh))
696(put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
697
698
699(define-sh-skeleton sh-while
700  "Insert a while loop in the current shell's syntax."
701  (sh "condition: "
702      "while [ " str " ]; do" \n
703      > _ \n
704      < "done")
705  (ksh . sh)
706  (bash . sh)
707  (csh "condition: "
708       "while( " str " )" \n
709       > _ \n
710       < "end")
711  (tcsh . csh))
712
713
714
715(defun sh-query-for-variable (arg)
716  "Unless quoted with `\\', query for variable-name with completions.
717Prefix arg 0 means don't insert `$' before the variable.
718Prefix arg 2 or more means only do self-insert that many times.
719  If { is pressed as the first character, it will surround the variable name."
720  (interactive "*p")
721  (or (prog1 (or (> arg 1)
722		 (sh-quoted-p))
723	(self-insert-command arg))
724    (let (completion-ignore-case
725	  (minibuffer-local-completion-map
726	   (or (get 'sh-query-for-variable 'keymap)
727	       (put 'sh-query-for-variable 'keymap
728		    (copy-keymap minibuffer-local-completion-map))))
729	  (buffer (current-buffer)))
730      ;; local function that depends on `arg' and `buffer'
731      (define-key minibuffer-local-completion-map "{"
732	(lambda () (interactive)
733	  (if (or arg (> (point) 1))
734	      (beep)
735	    (save-window-excursion
736	      (setq arg t)
737	      (switch-to-buffer-other-window buffer)
738	      (insert "{}")))))
739      (insert
740       (prog1
741	   (sh-remember-variable
742	    (completing-read "Variable: " sh-variables
743			     (lambda (element)
744			       (or (not (cdr element))
745				   (memq sh-shell (cdr element))))))
746	 (if (eq t arg) (forward-char 1))))
747      (if (eq t arg) (forward-char 1)))))
748
749
750
751(defun sh-assignment (arg)
752  "Insert self.  Remember previous identifier for future completing read."
753  (interactive "p")
754  (if (eq arg 1)
755      (sh-remember-variable
756       (save-excursion
757	 (buffer-substring
758	  (progn
759	    (if (memq sh-shell sh-assignment-space)
760		(skip-chars-backward " \t"))
761	    (point))
762	  (progn
763	    (skip-chars-backward "a-zA-Z0-9_")
764	    (point))))))
765  (self-insert-command arg))
766
767
768
769(defun sh-maybe-here-document (arg)
770  "Inserts self.  Without prefix, following unquoted `<' inserts here document.
771The document is bounded by `sh-here-document-word'."
772  (interactive "*P")
773  (self-insert-command (prefix-numeric-value arg))
774  (or arg
775      (not (eq (char-after (- (point) 2)) last-command-char))
776      (save-excursion
777	(goto-char (- (point) 2))
778	(sh-quoted-p))
779      (progn
780	(insert sh-here-document-word)
781	(or (looking-at "[ \t\n]") (insert ? ))
782	(end-of-line 1)
783	(newline)
784	(save-excursion (insert ?\n sh-here-document-word)))))
785
786
787;; various other commands
788
789(autoload 'comint-dynamic-complete-filename "comint"
790  "Dynamically complete the filename at point." t)
791
792
793
794(defun sh-newline-and-indent (&optional arg)
795  "Strip unquoted whitespace, insert newline, and indent like current line.
796Unquoted whitespace is stripped from the current line's end, unless a
797prefix ARG is given."
798  (interactive "*P")
799  (let ((previous (current-indentation))
800	(end-of-line (point)))
801    (if arg ()
802      (skip-chars-backward " \t")
803      (and (< (point) end-of-line)
804	   (sh-quoted-p)
805	   (forward-char 1))
806      (delete-region (point) end-of-line))
807    (newline)
808    (indent-to previous)))
809
810
811
812(defun sh-set-shell (shell)
813  "Set this buffer's shell to SHELL (a string).
814Calls the value of `sh-set-shell-hook' if set."
815  (interactive "sName or path of shell: ")
816  (save-excursion
817    (goto-char (point-min))
818    (setq sh-shell-path (if (file-name-absolute-p shell)
819				      shell
820				    (or (sh-executable shell)
821					(error "Cannot find %s." shell)))
822	  sh-shell (intern (file-name-nondirectory sh-shell-path))
823	  sh-shell-is-csh (memq sh-shell '(csh tcsh))
824	  font-lock-keywords
825	  (intern-soft (format "sh-%s-font-lock-keywords" sh-shell))
826	  font-lock-keywords (if (and font-lock-keywords
827				      (boundp font-lock-keywords))
828				 (symbol-value font-lock-keywords)
829			       sh-font-lock-keywords)
830	  comment-start-skip (if sh-shell-is-csh
831				 "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*"
832			       "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*")
833	  mode-line-process (format ": %s" sh-shell)
834	  shell (concat sh-shell-path
835			(and sh-shell-argument " ")
836			sh-shell-argument))
837    (and (not buffer-read-only)
838	 (not (if buffer-file-name
839		  (string-match sh-magicless-file-regexp buffer-file-name)))
840	 ;; find-file is set by `normal-mode' when called by `after-find-file'
841	 (if (and (boundp 'find-file) find-file) sh-find-file-modifies t)
842	 (if (looking-at "#!")
843	     (and (skip-chars-forward "#! \t")
844		  (not (string= shell
845				(buffer-substring (point)
846						  (save-excursion (end-of-line)
847								  (point)))))
848		  (if sh-query-for-magic
849		      (y-or-n-p (concat "Replace magic number by ``#! "
850					shell "''? "))
851		    (message "Magic number ``%s'' replaced."
852			     (buffer-substring (point-min) (point))))
853		  (not (delete-region (point) (progn (end-of-line) (point))))
854		  (insert shell))
855	   (insert "#! " shell ?\n))))
856  (run-hooks 'sh-set-shell-hook))
857
858
859
860(defun sh-beginning-of-command ()
861  "Move point to successive beginnings of commands."
862  (interactive)
863  (if (re-search-backward sh-beginning-of-command nil t)
864      (goto-char (match-beginning 2))))
865
866
867
868(defun sh-end-of-command ()
869  "Move point to successive ends of commands."
870  (interactive)
871  (if (re-search-forward sh-end-of-command nil t)
872      (goto-char (match-end 1))))
873
874
875
876(defun sh-manual-entry (arg)
877  "Display the Unix manual entry for the current command or shell.
878Universal argument ARG, is passed to `Man-getpage-in-background'."
879  (interactive "P")
880  (let ((command (save-excursion
881		   (sh-beginning-of-command)
882		   (sh-executable
883		    (buffer-substring (point)
884				      (progn (forward-sexp) (point)))))))
885    (setq command (read-input (concat "Manual entry (default "
886				      (symbol-name sh-shell)
887				      "): ")
888			      (if command
889				  (file-name-nondirectory command))))
890    (manual-entry (if (string= command "")
891		      (symbol-name sh-shell)
892		    command)
893		  arg)))
894
895;; sh-script.el ends here
896