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