6463
|
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.
|
|
158 Currently 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.
|
|
163 The regexp must match the whole output, and must contain a \\(something\\)
|
|
164 construct 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.
|
|
170 The 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.
|
|
187 This 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.
|
|
196 The 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.
|
|
201 The 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.
|
|
281 Variables 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.
|
|
289 This variable is included into the various variables
|
|
290 `sh-SHELL-font-lock-keywords'. If no such variable exists for some shell,
|
|
291 this 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.
|
|
325 Usually the file-names of scripts and binaries cannot be automatically
|
|
326 distinguished, 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.
|
|
338 This mode works for many shells, since they all have roughly the same syntax,
|
|
339 as far as commands, arguments, variables, pipes, comments etc. are concerned.
|
|
340 Unless the file's magic number indicates the shell, your usual shell is
|
|
341 assumed. Since filenames rarely give a clue, they are not further analyzed.
|
|
342
|
|
343 The syntax of the statements varies with the shell being used. The syntax of
|
|
344 statements can be modified by putting a property on the command or new ones
|
|
345 defined with `define-sh-skeleton'. For example
|
|
346
|
|
347 (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
|
|
348 or
|
|
349 (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
|
|
350
|
|
351 where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
|
|
352
|
|
353 The 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 ...
|
|
423 Prior definitions (e.g. from ~/.emacs) are maintained.
|
|
424 Each definition is built up as (SHELL PROMPT ELEMENT ...). Alternately
|
|
425 a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
|
|
426
|
|
427 For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
|
|
428 Each DEFINITION is actually stored as
|
|
429 (put COMMAND SHELL (PROMPT ELEMENT ...)),
|
|
430 which 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'.
|
|
460 If 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.
|
|
528 You 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.
|
|
717 Prefix arg 0 means don't insert `$' before the variable.
|
|
718 Prefix 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.
|
|
771 The 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.
|
|
796 Unquoted whitespace is stripped from the current line's end, unless a
|
|
797 prefix 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).
|
|
814 Calls 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.
|
|
878 Universal 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
|