Commit | Line | Data |
---|---|---|
ac59aed8 | 1 | ;;; sh-script.el --- shell-script editing commands for Emacs |
b578f267 | 2 | |
751d474c | 3 | ;; Copyright (C) 1993, 94, 95, 96, 97, 1999 by Free Software Foundation, Inc. |
ac59aed8 | 4 | |
3e910376 | 5 | ;; Author: Daniel Pfeiffer <occitan@esperanto.org> |
f964dfcb | 6 | ;; Version: 2.0f |
ac59aed8 | 7 | ;; Maintainer: FSF |
133693bc | 8 | ;; Keywords: languages, unix |
ac59aed8 RS |
9 | |
10 | ;; This file is part of GNU Emacs. | |
11 | ||
12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation; either version 2, or (at your option) | |
15 | ;; any later version. | |
16 | ||
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
b578f267 EN |
23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the |
24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
25 | ;; Boston, MA 02111-1307, USA. | |
ac59aed8 RS |
26 | |
27 | ;;; Commentary: | |
28 | ||
133693bc KH |
29 | ;; Major mode for editing shell scripts. Bourne, C and rc shells as well |
30 | ;; as various derivatives are supported and easily derived from. Structured | |
31 | ;; statements can be inserted with one command or abbrev. Completion is | |
32 | ;; available for filenames, variables known from the script, the shell and | |
33 | ;; the environment as well as commands. | |
ac59aed8 | 34 | |
133693bc KH |
35 | ;;; Known Bugs: |
36 | ||
bfc8e97b | 37 | ;; - In Bourne the keyword `in' is not anchored to case, for, select ... |
133693bc KH |
38 | ;; - Variables in `"' strings aren't fontified because there's no way of |
39 | ;; syntactically distinguishing those from `'' strings. | |
e932f2d2 | 40 | |
f964dfcb GM |
41 | ;; Indentation |
42 | ;; =========== | |
43 | ;; Indentation for rc and es modes is very limited, but for Bourne shells | |
44 | ;; and its derivatives it is quite customizable. | |
45 | ;; | |
46 | ;; The following description applies to sh and derived shells (bash, | |
47 | ;; zsh, ...). | |
48 | ;; | |
49 | ;; There are various customization variables which allow tailoring to | |
50 | ;; a wide variety of styles. Most of these variables are named | |
51 | ;; sh-indent-for-XXX and sh-indent-after-XXX. For example. | |
52 | ;; sh-indent-after-if controls the indenting of a line following | |
53 | ;; an if statement, and sh-indent-for-fi controls the indentation | |
54 | ;; of the line containing the fi. | |
55 | ;; | |
56 | ;; You can set each to a numeric value, but it is often more convenient | |
57 | ;; to a symbol such as `+' which uses the value of variable `sh-basic-offset'. | |
58 | ;; By changing this one variable you can increase or decrease how much | |
59 | ;; indentation there is. Valid symbols: | |
60 | ;; | |
61 | ;; + Indent right by sh-basic-offset | |
62 | ;; - Indent left by sh-basic-offset | |
63 | ;; ++ Indent right twice sh-basic-offset | |
64 | ;; -- Indent left twice sh-basic-offset | |
65 | ;; * Indent right half sh-basic-offset | |
66 | ;; / Indent left half sh-basic-offset. | |
67 | ;; | |
68 | ;; There are 4 commands to help set the indentation variables: | |
69 | ;; | |
70 | ;; `sh-show-indent' | |
71 | ;; This shows what variable controls the indentation of the current | |
72 | ;; line and its value. | |
73 | ;; | |
74 | ;; `sh-set-indent' | |
75 | ;; This allows you to set the value of the variable controlling the | |
76 | ;; current line's indentation. You can enter a number or one of a | |
77 | ;; number of special symbols to denote the value of sh-basic-offset, | |
78 | ;; or its negative, or half it, or twice it, etc. If you've used | |
79 | ;; cc-mode this should be familiar. If you forget which symbols are | |
80 | ;; valid simply press C-h at the prompt. | |
81 | ;; | |
82 | ;; `sh-learn-line-indent' | |
83 | ;; Simply make the line look the way you want it, then invoke this | |
84 | ;; command. It will set the variable to the value that makes the line | |
85 | ;; indent like that. If called with a prefix argument then it will set | |
86 | ;; the value to one of the symbols if applicable. | |
87 | ;; | |
88 | ;; `sh-learn-buffer-indent' | |
89 | ;; This is the deluxe function! It "learns" the whole buffer (use | |
90 | ;; narrowing if you want it to process only part). It outputs to a | |
91 | ;; buffer *indent* any conflicts it finds, and all the variables it has | |
92 | ;; learned. This buffer is a sort of Occur mode buffer, allowing you to | |
93 | ;; easily find where something was set. It is popped to automatically | |
94 | ;; if there are any conflicts found or if `sh-popup-occur-buffer' is | |
95 | ;; non-nil. | |
96 | ;; `sh-indent-comment' will be set if all comments follow the same | |
97 | ;; pattern; if they don't it will be set to nil. | |
98 | ;; Whether `sh-basic-offset' is set is determined by variable | |
99 | ;; `sh-learn-basic-offset'. | |
100 | ;; | |
101 | ;; Unfortunately, `sh-learn-buffer-indent' can take a long time to run | |
102 | ;; (e.g. if there are large case statements). Perhaps it does not make | |
103 | ;; sense to run it on large buffers: if lots of lines have different | |
104 | ;; indentation styles it will produce a lot of diagnostics in the | |
105 | ;; *indent* buffer; if there is a consistent style then running | |
106 | ;; `sh-learn-buffer-indent' on a small region of the buffer should | |
107 | ;; suffice. | |
108 | ;; | |
109 | ;; Saving indentation values | |
110 | ;; ------------------------- | |
111 | ;; After you've learned the values in a buffer, how to you remember | |
112 | ;; them? Originally I had hoped that `sh-learn-buffer-indent' | |
113 | ;; would make this unnecessary; simply learn the values when you visit | |
114 | ;; the buffer. | |
115 | ;; You can do this automatically like this: | |
116 | ; (add-hook 'sh-set-shell-hook 'sh-learn-buffer-indent) | |
117 | ;; | |
118 | ;; However... `sh-learn-buffer-indent' is extremely slow, | |
119 | ;; especially on large-ish buffer. Also, if there are conflicts the | |
120 | ;; "last one wins" which may not produce the desired setting. | |
121 | ;; | |
122 | ;; So...There is a minimal way of being able to save indentation values and | |
123 | ;; to reload them in another buffer or at another point in time. | |
124 | ;; | |
125 | ;; Use `sh-name-style' to give a name to the indentation settings of | |
126 | ;; the current buffer. | |
127 | ;; Use `sh-load-style' to load indentation settings for the current | |
128 | ;; buffer from a specific style. | |
129 | ;; Use `sh-save-styles-to-buffer' to write all the styles to a buffer | |
130 | ;; in lisp code. You can then store it in a file and later use | |
131 | ;; `load-file' to load it. | |
132 | ;; | |
133 | ;; Indentation variables - buffer local or global? | |
134 | ;; ---------------------------------------------- | |
135 | ;; I think that often having them buffer-local makes sense, | |
136 | ;; especially if one is using `sh-learn-buffer-indent'. However, if | |
137 | ;; a user sets values using customization, these changes won't appear | |
138 | ;; to work if the variables are already local! | |
139 | ;; | |
140 | ;; To get round this, there is a variable `sh-make-vars-local' and 2 | |
141 | ;; functions: `sh-make-vars-local' and `sh-reset-indent-vars-to-global-values'. | |
142 | ;; | |
143 | ;; If `sh-make-vars-local' is non-nil, then these variables become | |
144 | ;; buffer local when the mode is established. | |
145 | ;; If this is nil, then the variables are global. At any time you | |
146 | ;; can make them local with the command `sh-make-vars-local'. | |
147 | ;; Conversely, to update with the global values you can use the | |
148 | ;; command `sh-reset-indent-vars-to-global-values'. | |
149 | ;; | |
150 | ;; This may be awkward, but the intent is to cover all cases. | |
151 | ;; | |
152 | ;; Awkward things, pitfalls | |
153 | ;; ------------------------ | |
154 | ;; Indentation for a sh script is complicated for a number of reasons: | |
155 | ;; | |
156 | ;; 1. You can't format by simply looking at symbols, you need to look | |
157 | ;; at keywords. [This is not the case for rc and es shells.] | |
158 | ;; 2. The character ")" is used both as a matched pair "(" ... ")" and | |
159 | ;; as a stand-alone symbol (in a case alternative). This makes | |
160 | ;; things quite tricky! | |
161 | ;; 3. Here-documents in a script should be treated "as is", and when | |
162 | ;; they terminate we want to revert to the indentation of the line | |
163 | ;; containing the "<<" symbol. | |
164 | ;; 4. A line may be continued using the "\". | |
165 | ;; 5. The character "#" (outside a string) normally starts a comment, | |
166 | ;; but it doesn't in the sequence "$#"! | |
167 | ;; | |
168 | ;; To try and address points 2 3 and 5 I used a feature that cperl mode | |
169 | ;; uses, that of a text's syntax property. This, however, has 2 | |
170 | ;; disadvantages: | |
171 | ;; 1. We need to scan the buffer to find which ")" symbols belong to a | |
172 | ;; case alternative, to find any here documents, and handle "$#". | |
173 | ;; 2. Setting the text property makes the buffer modified. If the | |
174 | ;; buffer is read-only buffer we have to cheat and bypass the read-only | |
175 | ;; status. This is for cases where the buffer started read-only buffer | |
176 | ;; but the user issued `toggle-read-only'. | |
177 | ;; | |
178 | ;; Bugs | |
179 | ;; ---- | |
180 | ;; - Here-documents are marked with text properties face and syntax | |
181 | ;; table. This serves 2 purposes: stopping indentation while inside | |
182 | ;; them, and moving over them when finding the previous line to | |
183 | ;; indent to. However, if font-lock mode is active when there is | |
184 | ;; any change inside the here-document font-lock clears that | |
185 | ;; property. This causes several problems: lines after the here-doc | |
186 | ;; will not be re-indentation properly, words in the here-doc region | |
187 | ;; may be fontified, and indentation may occur within the | |
188 | ;; here-document. | |
189 | ;; I'm not sure how to fix this, perhaps using the point-entered | |
190 | ;; property. Anyway, if you use font lock and change a | |
191 | ;; here-document, I recommend using M-x sh-rescan-buffer after the | |
192 | ;; changes are made. Similarly, when using higlight-changes-mode, | |
193 | ;; changes inside a here-document may confuse shell indenting, but again | |
194 | ;; using `sh-rescan-buffer' should fix them. | |
195 | ;; | |
196 | ;; - Indenting many lines is slow. It currently does each line | |
197 | ;; independently, rather than saving state information. | |
198 | ;; | |
199 | ;; - `sh-learn-buffer-indent' is extremely slow. | |
200 | ;; | |
201 | ;; Richard Sharman <rsharman@pobox.com> June 1999. | |
202 | ||
ac59aed8 RS |
203 | ;;; Code: |
204 | ||
205 | ;; page 1: variables and settings | |
f964dfcb GM |
206 | ;; page 2: indentation stuff |
207 | ;; page 3: mode-command and utility functions | |
208 | ;; page 4: statement syntax-commands for various shells | |
209 | ;; page 5: various other commands | |
ac59aed8 | 210 | |
133693bc KH |
211 | (require 'executable) |
212 | ||
2bffb7c4 | 213 | |
2bffb7c4 | 214 | |
cd482e05 RS |
215 | (defgroup sh nil |
216 | "Shell programming utilities" | |
217 | :group 'unix | |
218 | :group 'languages) | |
219 | ||
220 | (defgroup sh-script nil | |
221 | "Shell script mode" | |
222 | :group 'sh | |
223 | :prefix "sh-") | |
224 | ||
225 | ||
226 | (defcustom sh-ancestor-alist | |
133693bc KH |
227 | '((ash . sh) |
228 | (bash . jsh) | |
229 | (dtksh . ksh) | |
230 | (es . rc) | |
231 | (itcsh . tcsh) | |
232 | (jcsh . csh) | |
233 | (jsh . sh) | |
234 | (ksh . ksh88) | |
235 | (ksh88 . jsh) | |
236 | (oash . sh) | |
237 | (pdksh . ksh88) | |
238 | (posix . sh) | |
239 | (tcsh . csh) | |
240 | (wksh . ksh88) | |
241 | (wsh . sh) | |
547745f5 RS |
242 | (zsh . ksh88) |
243 | (rpm . sh)) | |
133693bc KH |
244 | "*Alist showing the direct ancestor of various shells. |
245 | This is the basis for `sh-feature'. See also `sh-alias-alist'. | |
246 | By default we have the following three hierarchies: | |
247 | ||
248 | csh C Shell | |
249 | jcsh C Shell with Job Control | |
250 | tcsh Toronto C Shell | |
251 | itcsh ? Toronto C Shell | |
252 | rc Plan 9 Shell | |
253 | es Extensible Shell | |
254 | sh Bourne Shell | |
255 | ash ? Shell | |
256 | jsh Bourne Shell with Job Control | |
257 | bash GNU Bourne Again Shell | |
258 | ksh88 Korn Shell '88 | |
259 | ksh Korn Shell '93 | |
260 | dtksh CDE Desktop Korn Shell | |
261 | pdksh Public Domain Korn Shell | |
262 | wksh Window Korn Shell | |
263 | zsh Z Shell | |
264 | oash SCO OA (curses) Shell | |
265 | posix IEEE 1003.2 Shell Standard | |
cd482e05 RS |
266 | wsh ? Shell" |
267 | :type '(repeat (cons symbol symbol)) | |
268 | :group 'sh-script) | |
133693bc KH |
269 | |
270 | ||
cd482e05 | 271 | (defcustom sh-alias-alist |
6dd4407c | 272 | (nconc (if (eq system-type 'gnu/linux) |
133693bc | 273 | '((csh . tcsh) |
aafd074a | 274 | (ksh . pdksh))) |
133693bc KH |
275 | ;; for the time being |
276 | '((ksh . ksh88) | |
277 | (sh5 . sh))) | |
278 | "*Alist for transforming shell names to what they really are. | |
279 | Use this where the name of the executable doesn't correspond to the type of | |
cd482e05 RS |
280 | shell it really is." |
281 | :type '(repeat (cons symbol symbol)) | |
282 | :group 'sh-script) | |
133693bc KH |
283 | |
284 | ||
cd482e05 | 285 | (defcustom sh-shell-file |
d9de8c04 | 286 | (or |
5c449169 RS |
287 | ;; On MSDOS and Windows, collapse $SHELL to lower-case and remove |
288 | ;; the executable extension, so comparisons with the list of | |
d9de8c04 | 289 | ;; known shells work. |
5c449169 | 290 | (and (memq system-type '(ms-dos windows-nt)) |
eee86eff EZ |
291 | (let* ((shell (getenv "SHELL")) |
292 | (shell-base | |
293 | (and shell (file-name-nondirectory shell)))) | |
294 | ;; shell-script mode doesn't support DOS/Windows shells, | |
295 | ;; so use the default instead. | |
296 | (if (or (null shell) | |
297 | (member (downcase shell-base) | |
ced7b4a4 RS |
298 | '("command.com" "cmd.exe" "4dos.com" "ndos.com" |
299 | "cmdproxy.exe"))) | |
eee86eff EZ |
300 | "/bin/sh" |
301 | (file-name-sans-extension (downcase shell))))) | |
d9de8c04 RS |
302 | (getenv "SHELL") |
303 | "/bin/sh") | |
cd482e05 RS |
304 | "*The executable file name for the shell being programmed." |
305 | :type 'string | |
306 | :group 'sh-script) | |
133693bc KH |
307 | |
308 | ||
cd482e05 | 309 | (defcustom sh-shell-arg |
8d31ff15 | 310 | ;; bash does not need any options when run in a shell script, |
8e46e267 | 311 | '((bash) |
133693bc | 312 | (csh . "-f") |
133693bc | 313 | (pdksh) |
8d31ff15 | 314 | ;; Bill_Mann@praxisint.com says -p with ksh can do harm. |
8e46e267 | 315 | (ksh88) |
8d31ff15 | 316 | ;; -p means don't initialize functions from the environment. |
133693bc | 317 | (rc . "-p") |
8d31ff15 RS |
318 | ;; Someone proposed -motif, but we don't want to encourage |
319 | ;; use of a non-free widget set. | |
320 | (wksh) | |
321 | ;; -f means don't run .zshrc. | |
133693bc | 322 | (zsh . "-f")) |
cd482e05 RS |
323 | "*Single argument string for the magic number. See `sh-feature'." |
324 | :type '(repeat (cons (symbol :tag "Shell") | |
325 | (choice (const :tag "No Arguments" nil) | |
326 | (string :tag "Arguments") | |
327 | (cons :format "Evaluate: %v" | |
328 | (const :format "" eval) | |
329 | sexp)))) | |
330 | :group 'sh-script) | |
133693bc | 331 | |
aa2c2426 KH |
332 | (defcustom sh-imenu-generic-expression |
333 | (list | |
334 | (cons 'sh | |
335 | (concat | |
336 | "\\(^\\s-*function\\s-+[A-Za-z_][A-Za-z_0-9]*\\)" | |
337 | "\\|" | |
338 | "\\(^\\s-*[A-Za-z_][A-Za-z_0-9]*\\s-*()\\)"))) | |
339 | "*Regular expression for recognizing shell function definitions. | |
340 | See `sh-feature'." | |
341 | :type '(repeat (cons (symbol :tag "Shell") | |
342 | regexp)) | |
cd32a7ba | 343 | :group 'sh-script |
f964dfcb | 344 | :version "20.4") |
aa2c2426 | 345 | |
5a989d6e RS |
346 | (defvar sh-shell-variables nil |
347 | "Alist of shell variable names that should be included in completion. | |
348 | These are used for completion in addition to all the variables named | |
349 | in `process-environment'. Each element looks like (VAR . VAR), where | |
350 | the car and cdr are the same symbol.") | |
133693bc | 351 | |
5d73ac66 RS |
352 | (defvar sh-shell-variables-initialized nil |
353 | "Non-nil if `sh-shell-variables' is initialized.") | |
354 | ||
aafd074a KH |
355 | (defun sh-canonicalize-shell (shell) |
356 | "Convert a shell name SHELL to the one we should handle it as." | |
842cc0e6 | 357 | (if (string-match "\\.exe\\'" shell) |
c8b88e9f | 358 | (setq shell (substring shell 0 (match-beginning 0)))) |
aafd074a KH |
359 | (or (symbolp shell) |
360 | (setq shell (intern shell))) | |
361 | (or (cdr (assq shell sh-alias-alist)) | |
362 | shell)) | |
133693bc | 363 | |
aafd074a KH |
364 | (defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file)) |
365 | "The shell being programmed. This is set by \\[sh-set-shell].") | |
133693bc | 366 | |
aafd074a KH |
367 | ;;; I turned off this feature because it doesn't permit typing commands |
368 | ;;; in the usual way without help. | |
369 | ;;;(defvar sh-abbrevs | |
370 | ;;; '((csh eval sh-abbrevs shell | |
371 | ;;; "switch" 'sh-case | |
372 | ;;; "getopts" 'sh-while-getopts) | |
133693bc | 373 | |
aafd074a KH |
374 | ;;; (es eval sh-abbrevs shell |
375 | ;;; "function" 'sh-function) | |
133693bc | 376 | |
aafd074a KH |
377 | ;;; (ksh88 eval sh-abbrevs sh |
378 | ;;; "select" 'sh-select) | |
133693bc | 379 | |
aafd074a KH |
380 | ;;; (rc eval sh-abbrevs shell |
381 | ;;; "case" 'sh-case | |
382 | ;;; "function" 'sh-function) | |
133693bc | 383 | |
aafd074a KH |
384 | ;;; (sh eval sh-abbrevs shell |
385 | ;;; "case" 'sh-case | |
386 | ;;; "function" 'sh-function | |
387 | ;;; "until" 'sh-until | |
388 | ;;; "getopts" 'sh-while-getopts) | |
133693bc | 389 | |
aafd074a KH |
390 | ;;; ;; The next entry is only used for defining the others |
391 | ;;; (shell "for" sh-for | |
392 | ;;; "loop" sh-indexed-loop | |
393 | ;;; "if" sh-if | |
394 | ;;; "tmpfile" sh-tmp-file | |
395 | ;;; "while" sh-while) | |
133693bc | 396 | |
aafd074a KH |
397 | ;;; (zsh eval sh-abbrevs ksh88 |
398 | ;;; "repeat" 'sh-repeat)) | |
399 | ;;; "Abbrev-table used in Shell-Script mode. See `sh-feature'. | |
400 | ;;;Due to the internal workings of abbrev tables, the shell name symbol is | |
401 | ;;;actually defined as the table for the like of \\[edit-abbrevs].") | |
ac59aed8 | 402 | |
ac59aed8 RS |
403 | |
404 | ||
405 | (defvar sh-mode-syntax-table | |
c8005e70 RS |
406 | '((sh eval sh-mode-syntax-table () |
407 | ?\# "<" | |
408 | ?\^l ">#" | |
409 | ?\n ">#" | |
133693bc KH |
410 | ?\" "\"\"" |
411 | ?\' "\"'" | |
c8005e70 | 412 | ?\` "\"`" |
133693bc KH |
413 | ?! "_" |
414 | ?% "_" | |
415 | ?: "_" | |
416 | ?. "_" | |
417 | ?^ "_" | |
418 | ?~ "_") | |
c8005e70 RS |
419 | (csh eval identity sh) |
420 | (rc eval identity sh)) | |
133693bc KH |
421 | "Syntax-table used in Shell-Script mode. See `sh-feature'.") |
422 | ||
423 | ||
ac59aed8 RS |
424 | |
425 | (defvar sh-mode-map | |
bfc8e97b KH |
426 | (let ((map (make-sparse-keymap)) |
427 | (menu-map (make-sparse-keymap "Insert"))) | |
ac59aed8 RS |
428 | (define-key map "\C-c(" 'sh-function) |
429 | (define-key map "\C-c\C-w" 'sh-while) | |
430 | (define-key map "\C-c\C-u" 'sh-until) | |
133693bc | 431 | (define-key map "\C-c\C-t" 'sh-tmp-file) |
ac59aed8 | 432 | (define-key map "\C-c\C-s" 'sh-select) |
133693bc KH |
433 | (define-key map "\C-c\C-r" 'sh-repeat) |
434 | (define-key map "\C-c\C-o" 'sh-while-getopts) | |
ac59aed8 RS |
435 | (define-key map "\C-c\C-l" 'sh-indexed-loop) |
436 | (define-key map "\C-c\C-i" 'sh-if) | |
437 | (define-key map "\C-c\C-f" 'sh-for) | |
438 | (define-key map "\C-c\C-c" 'sh-case) | |
f964dfcb GM |
439 | (define-key map "\C-c?" 'sh-show-indent) |
440 | (define-key map "\C-c=" 'sh-set-indent) | |
441 | (define-key map "\C-c<" 'sh-learn-line-indent) | |
442 | (define-key map "\C-c>" 'sh-learn-buffer-indent) | |
133693bc | 443 | |
ac59aed8 RS |
444 | (define-key map "=" 'sh-assignment) |
445 | (define-key map "\C-c+" 'sh-add) | |
fd4ea9a2 RS |
446 | (define-key map "\C-\M-x" 'sh-execute-region) |
447 | (define-key map "\C-c\C-x" 'executable-interpret) | |
133693bc | 448 | (define-key map "<" 'sh-maybe-here-document) |
81ed2d75 KH |
449 | (define-key map "(" 'skeleton-pair-insert-maybe) |
450 | (define-key map "{" 'skeleton-pair-insert-maybe) | |
451 | (define-key map "[" 'skeleton-pair-insert-maybe) | |
452 | (define-key map "'" 'skeleton-pair-insert-maybe) | |
453 | (define-key map "`" 'skeleton-pair-insert-maybe) | |
454 | (define-key map "\"" 'skeleton-pair-insert-maybe) | |
f964dfcb GM |
455 | (define-key map ")" 'sh-electric-rparen) |
456 | (define-key map "<" 'sh-electric-less) | |
457 | (define-key map "#" 'sh-electric-hash) | |
ac59aed8 | 458 | |
133693bc | 459 | (substitute-key-definition 'complete-tag 'comint-dynamic-complete |
ac59aed8 RS |
460 | map (current-global-map)) |
461 | (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent | |
462 | map (current-global-map)) | |
ac59aed8 RS |
463 | (substitute-key-definition 'delete-backward-char |
464 | 'backward-delete-char-untabify | |
465 | map (current-global-map)) | |
466 | (define-key map "\C-c:" 'sh-set-shell) | |
467 | (substitute-key-definition 'beginning-of-defun | |
468 | 'sh-beginning-of-compound-command | |
469 | map (current-global-map)) | |
470 | (substitute-key-definition 'backward-sentence 'sh-beginning-of-command | |
471 | map (current-global-map)) | |
472 | (substitute-key-definition 'forward-sentence 'sh-end-of-command | |
473 | map (current-global-map)) | |
bfc8e97b KH |
474 | (define-key map [menu-bar insert] (cons "Insert" menu-map)) |
475 | (define-key menu-map [sh-while] '("While Loop" . sh-while)) | |
476 | (define-key menu-map [sh-until] '("Until Loop" . sh-until)) | |
477 | (define-key menu-map [sh-tmp-file] '("Temporary File" . sh-tmp-file)) | |
478 | (define-key menu-map [sh-select] '("Select Statement" . sh-select)) | |
479 | (define-key menu-map [sh-repeat] '("Repeat Loop" . sh-repeat)) | |
480 | (define-key menu-map [sh-while-getopts] | |
481 | '("Options Loop" . sh-while-getopts)) | |
482 | (define-key menu-map [sh-indexed-loop] | |
483 | '("Indexed Loop" . sh-indexed-loop)) | |
484 | (define-key menu-map [sh-if] '("If Statement" . sh-if)) | |
485 | (define-key menu-map [sh-for] '("For Loop" . sh-for)) | |
486 | (define-key menu-map [sh-case] '("Case Statement" . sh-case)) | |
ac59aed8 RS |
487 | map) |
488 | "Keymap used in Shell-Script mode.") | |
489 | ||
490 | ||
491 | ||
cd482e05 | 492 | (defcustom sh-dynamic-complete-functions |
133693bc KH |
493 | '(shell-dynamic-complete-environment-variable |
494 | shell-dynamic-complete-command | |
495 | comint-dynamic-complete-filename) | |
cd482e05 RS |
496 | "*Functions for doing TAB dynamic completion." |
497 | :type '(repeat function) | |
498 | :group 'sh-script) | |
ac59aed8 RS |
499 | |
500 | ||
cd482e05 | 501 | (defcustom sh-require-final-newline |
133693bc KH |
502 | '((csh . t) |
503 | (pdksh . t) | |
504 | (rc eval . require-final-newline) | |
505 | (sh eval . require-final-newline)) | |
506 | "*Value of `require-final-newline' in Shell-Script mode buffers. | |
cd482e05 RS |
507 | See `sh-feature'." |
508 | :type '(repeat (cons (symbol :tag "Shell") | |
509 | (choice (const :tag "require" t) | |
510 | (cons :format "Evaluate: %v" | |
511 | (const :format "" eval) | |
512 | sexp)))) | |
513 | :group 'sh-script) | |
ac59aed8 RS |
514 | |
515 | ||
c410bd65 | 516 | (defcustom sh-assignment-regexp |
133693bc KH |
517 | '((csh . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=") |
518 | ;; actually spaces are only supported in let/(( ... )) | |
519 | (ksh88 . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=") | |
520 | (rc . "\\<\\([a-zA-Z0-9_*]+\\)[ \t]*=") | |
521 | (sh . "\\<\\([a-zA-Z0-9_]+\\)=")) | |
522 | "*Regexp for the variable name and what may follow in an assignment. | |
523 | First grouping matches the variable name. This is upto and including the `=' | |
cd482e05 RS |
524 | sign. See `sh-feature'." |
525 | :type '(repeat (cons (symbol :tag "Shell") | |
526 | (choice regexp | |
527 | (cons :format "Evaluate: %v" | |
528 | (const :format "" eval) | |
529 | sexp)))) | |
530 | :group 'sh-script) | |
ac59aed8 | 531 | |
ac59aed8 | 532 | |
cd482e05 RS |
533 | (defcustom sh-indentation 4 |
534 | "The width for further indentation in Shell-Script mode." | |
535 | :type 'integer | |
536 | :group 'sh-script) | |
ac59aed8 | 537 | |
ac59aed8 | 538 | |
cd482e05 RS |
539 | (defcustom sh-remember-variable-min 3 |
540 | "*Don't remember variables less than this length for completing reads." | |
541 | :type 'integer | |
542 | :group 'sh-script) | |
ac59aed8 RS |
543 | |
544 | ||
133693bc | 545 | (defvar sh-header-marker nil |
f964dfcb | 546 | "When non-nil is the end of header for prepending by \\[sh-execute-region]. |
133693bc KH |
547 | That command is also used for setting this variable.") |
548 | ||
549 | ||
cd482e05 | 550 | (defcustom sh-beginning-of-command |
84bfbb44 | 551 | "\\([;({`|&]\\|\\`\\|[^\\]\n\\)[ \t]*\\([/~a-zA-Z0-9:]\\)" |
ac59aed8 | 552 | "*Regexp to determine the beginning of a shell command. |
cd482e05 RS |
553 | The actual command starts at the beginning of the second \\(grouping\\)." |
554 | :type 'regexp | |
555 | :group 'sh-script) | |
ac59aed8 | 556 | |
133693bc | 557 | |
cd482e05 | 558 | (defcustom sh-end-of-command |
84bfbb44 | 559 | "\\([/~a-zA-Z0-9:]\\)[ \t]*\\([;#)}`|&]\\|$\\)" |
ac59aed8 | 560 | "*Regexp to determine the end of a shell command. |
cd482e05 RS |
561 | The actual command ends at the end of the first \\(grouping\\)." |
562 | :type 'regexp | |
563 | :group 'sh-script) | |
ac59aed8 RS |
564 | |
565 | ||
566 | ||
133693bc | 567 | (defvar sh-here-document-word "EOF" |
ac59aed8 RS |
568 | "Word to delimit here documents.") |
569 | ||
225f6185 KH |
570 | (defvar sh-test |
571 | '((sh "[ ]" . 3) | |
572 | (ksh88 "[[ ]]" . 4)) | |
573 | "Initial input in Bourne if, while and until skeletons. See `sh-feature'.") | |
574 | ||
ac59aed8 | 575 | |
cd482e05 RS |
576 | ;; customized this out of sheer bravado. not for the faint of heart. |
577 | ;; but it *did* have an asterisk in the docstring! | |
578 | (defcustom sh-builtins | |
84bfbb44 KH |
579 | '((bash eval sh-append posix |
580 | "alias" "bg" "bind" "builtin" "declare" "dirs" "enable" "fc" "fg" | |
581 | "help" "history" "jobs" "kill" "let" "local" "popd" "pushd" "source" | |
582 | "suspend" "typeset" "unalias") | |
ac59aed8 | 583 | |
133693bc KH |
584 | ;; The next entry is only used for defining the others |
585 | (bourne eval sh-append shell | |
84bfbb44 KH |
586 | "eval" "export" "getopts" "newgrp" "pwd" "read" "readonly" |
587 | "times" "ulimit") | |
ac59aed8 | 588 | |
133693bc | 589 | (csh eval sh-append shell |
84bfbb44 KH |
590 | "alias" "chdir" "glob" "history" "limit" "nice" "nohup" "rehash" |
591 | "setenv" "source" "time" "unalias" "unhash") | |
592 | ||
593 | (dtksh eval identity wksh) | |
ac59aed8 | 594 | |
84bfbb44 KH |
595 | (es "access" "apids" "cd" "echo" "eval" "false" "let" "limit" "local" |
596 | "newpgrp" "result" "time" "umask" "var" "vars" "wait" "whatis") | |
ac59aed8 | 597 | |
133693bc KH |
598 | (jsh eval sh-append sh |
599 | "bg" "fg" "jobs" "kill" "stop" "suspend") | |
ac59aed8 | 600 | |
133693bc KH |
601 | (jcsh eval sh-append csh |
602 | "bg" "fg" "jobs" "kill" "notify" "stop" "suspend") | |
603 | ||
604 | (ksh88 eval sh-append bourne | |
84bfbb44 KH |
605 | "alias" "bg" "false" "fc" "fg" "jobs" "kill" "let" "print" "time" |
606 | "typeset" "unalias" "whence") | |
133693bc KH |
607 | |
608 | (oash eval sh-append sh | |
609 | "checkwin" "dateline" "error" "form" "menu" "newwin" "oadeinit" | |
610 | "oaed" "oahelp" "oainit" "pp" "ppfile" "scan" "scrollok" "wattr" | |
611 | "wclear" "werase" "win" "wmclose" "wmmessage" "wmopen" "wmove" | |
612 | "wmtitle" "wrefresh") | |
613 | ||
614 | (pdksh eval sh-append ksh88 | |
615 | "bind") | |
616 | ||
617 | (posix eval sh-append sh | |
618 | "command") | |
619 | ||
84bfbb44 KH |
620 | (rc "builtin" "cd" "echo" "eval" "limit" "newpgrp" "shift" "umask" "wait" |
621 | "whatis") | |
133693bc KH |
622 | |
623 | (sh eval sh-append bourne | |
624 | "hash" "test" "type") | |
625 | ||
626 | ;; The next entry is only used for defining the others | |
84bfbb44 KH |
627 | (shell "cd" "echo" "eval" "set" "shift" "umask" "unset" "wait") |
628 | ||
629 | (wksh eval sh-append ksh88 | |
630 | "Xt[A-Z][A-Za-z]*") | |
133693bc KH |
631 | |
632 | (zsh eval sh-append ksh88 | |
84bfbb44 KH |
633 | "autoload" "bindkey" "builtin" "chdir" "compctl" "declare" "dirs" |
634 | "disable" "disown" "echotc" "enable" "functions" "getln" "hash" | |
635 | "history" "integer" "limit" "local" "log" "popd" "pushd" "r" | |
636 | "readonly" "rehash" "sched" "setopt" "source" "suspend" "true" | |
637 | "ttyctl" "type" "unfunction" "unhash" "unlimit" "unsetopt" "vared" | |
638 | "which")) | |
133693bc KH |
639 | "*List of all shell builtins for completing read and fontification. |
640 | Note that on some systems not all builtins are available or some are | |
cd482e05 RS |
641 | implemented as aliases. See `sh-feature'." |
642 | :type '(repeat (cons (symbol :tag "Shell") | |
643 | (choice (repeat string) | |
644 | (cons :format "Evaluate: %v" | |
645 | (const :format "" eval) | |
646 | sexp)))) | |
647 | :group 'sh-script) | |
133693bc KH |
648 | |
649 | ||
84bfbb44 | 650 | |
cd482e05 | 651 | (defcustom sh-leading-keywords |
84bfbb44 KH |
652 | '((csh "else") |
653 | ||
654 | (es "true" "unwind-protect" "whatis") | |
655 | ||
656 | (rc "else") | |
657 | ||
658 | (sh "do" "elif" "else" "if" "then" "trap" "type" "until" "while")) | |
659 | "*List of keywords that may be immediately followed by a builtin or keyword. | |
660 | Given some confusion between keywords and builtins depending on shell and | |
661 | system, the distinction here has been based on whether they influence the | |
cd482e05 RS |
662 | flow of control or syntax. See `sh-feature'." |
663 | :type '(repeat (cons (symbol :tag "Shell") | |
664 | (choice (repeat string) | |
665 | (cons :format "Evaluate: %v" | |
666 | (const :format "" eval) | |
667 | sexp)))) | |
668 | :group 'sh-script) | |
84bfbb44 KH |
669 | |
670 | ||
cd482e05 | 671 | (defcustom sh-other-keywords |
84bfbb44 KH |
672 | '((bash eval sh-append bourne |
673 | "bye" "logout") | |
133693bc KH |
674 | |
675 | ;; The next entry is only used for defining the others | |
d9de8c04 RS |
676 | (bourne eval sh-append sh |
677 | "function") | |
133693bc | 678 | |
84bfbb44 KH |
679 | (csh eval sh-append shell |
680 | "breaksw" "default" "end" "endif" "endsw" "foreach" "goto" | |
681 | "if" "logout" "onintr" "repeat" "switch" "then" "while") | |
133693bc | 682 | |
84bfbb44 KH |
683 | (es "break" "catch" "exec" "exit" "fn" "for" "forever" "fork" "if" |
684 | "return" "throw" "while") | |
133693bc KH |
685 | |
686 | (ksh88 eval sh-append bourne | |
84bfbb44 | 687 | "select") |
133693bc | 688 | |
84bfbb44 KH |
689 | (rc "break" "case" "exec" "exit" "fn" "for" "if" "in" "return" "switch" |
690 | "while") | |
133693bc | 691 | |
d9de8c04 RS |
692 | (sh eval sh-append shell |
693 | "done" "esac" "fi" "for" "in" "return") | |
694 | ||
84bfbb44 KH |
695 | ;; The next entry is only used for defining the others |
696 | (shell "break" "case" "continue" "exec" "exit") | |
133693bc | 697 | |
84bfbb44 KH |
698 | (zsh eval sh-append bash |
699 | "select")) | |
700 | "*List of keywords not in `sh-leading-keywords'. | |
cd482e05 RS |
701 | See `sh-feature'." |
702 | :type '(repeat (cons (symbol :tag "Shell") | |
703 | (choice (repeat string) | |
704 | (cons :format "Evaluate: %v" | |
705 | (const :format "" eval) | |
706 | sexp)))) | |
707 | :group 'sh-script) | |
133693bc KH |
708 | |
709 | ||
710 | ||
711 | (defvar sh-variables | |
712 | '((bash eval sh-append sh | |
713 | "allow_null_glob_expansion" "auto_resume" "BASH" "BASH_VERSION" | |
714 | "cdable_vars" "ENV" "EUID" "FCEDIT" "FIGNORE" "glob_dot_filenames" | |
715 | "histchars" "HISTFILE" "HISTFILESIZE" "history_control" "HISTSIZE" | |
716 | "hostname_completion_file" "HOSTTYPE" "IGNOREEOF" "ignoreeof" | |
717 | "LINENO" "MAIL_WARNING" "noclobber" "nolinks" "notify" | |
718 | "no_exit_on_failed_exec" "NO_PROMPT_VARS" "OLDPWD" "OPTERR" "PPID" | |
719 | "PROMPT_COMMAND" "PS4" "pushd_silent" "PWD" "RANDOM" "REPLY" | |
720 | "SECONDS" "SHLVL" "TMOUT" "UID") | |
721 | ||
722 | (csh eval sh-append shell | |
723 | "argv" "cdpath" "child" "echo" "histchars" "history" "home" | |
724 | "ignoreeof" "mail" "noclobber" "noglob" "nonomatch" "path" "prompt" | |
725 | "shell" "status" "time" "verbose") | |
726 | ||
727 | (es eval sh-append shell | |
728 | "apid" "cdpath" "CDPATH" "history" "home" "ifs" "noexport" "path" | |
729 | "pid" "prompt" "signals") | |
730 | ||
731 | (jcsh eval sh-append csh | |
732 | "notify") | |
733 | ||
734 | (ksh88 eval sh-append sh | |
735 | "ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO" | |
736 | "OLDPWD" "PPID" "PS3" "PS4" "PWD" "RANDOM" "REPLY" "SECONDS" | |
737 | "TMOUT") | |
738 | ||
739 | (oash eval sh-append sh | |
740 | "FIELD" "FIELD_MAX" "LAST_KEY" "OALIB" "PP_ITEM" "PP_NUM") | |
741 | ||
742 | (rc eval sh-append shell | |
743 | "apid" "apids" "cdpath" "CDPATH" "history" "home" "ifs" "path" "pid" | |
744 | "prompt" "status") | |
745 | ||
746 | (sh eval sh-append shell | |
747 | "CDPATH" "IFS" "OPTARG" "OPTIND" "PS1" "PS2") | |
748 | ||
749 | ;; The next entry is only used for defining the others | |
750 | (shell "COLUMNS" "EDITOR" "HOME" "HUSHLOGIN" "LANG" "LC_COLLATE" | |
751 | "LC_CTYPE" "LC_MESSAGES" "LC_MONETARY" "LC_NUMERIC" "LC_TIME" | |
752 | "LINES" "LOGNAME" "MAIL" "MAILCHECK" "MAILPATH" "PAGER" "PATH" | |
753 | "SHELL" "TERM" "TERMCAP" "TERMINFO" "VISUAL") | |
754 | ||
755 | (tcsh eval sh-append csh | |
756 | "addsuffix" "ampm" "autocorrect" "autoexpand" "autolist" | |
757 | "autologout" "chase_symlinks" "correct" "dextract" "edit" "el" | |
758 | "fignore" "gid" "histlit" "HOST" "HOSTTYPE" "HPATH" | |
759 | "ignore_symlinks" "listjobs" "listlinks" "listmax" "matchbeep" | |
760 | "nobeep" "NOREBIND" "oid" "printexitvalue" "prompt2" "prompt3" | |
761 | "pushdsilent" "pushdtohome" "recexact" "recognize_only_executables" | |
762 | "rmstar" "savehist" "SHLVL" "showdots" "sl" "SYSTYPE" "tcsh" "term" | |
763 | "tperiod" "tty" "uid" "version" "visiblebell" "watch" "who" | |
764 | "wordchars") | |
765 | ||
766 | (zsh eval sh-append ksh88 | |
767 | "BAUD" "bindcmds" "cdpath" "DIRSTACKSIZE" "fignore" "FIGNORE" "fpath" | |
768 | "HISTCHARS" "hostcmds" "hosts" "HOSTS" "LISTMAX" "LITHISTSIZE" | |
769 | "LOGCHECK" "mailpath" "manpath" "NULLCMD" "optcmds" "path" "POSTEDIT" | |
770 | "prompt" "PROMPT" "PROMPT2" "PROMPT3" "PROMPT4" "psvar" "PSVAR" | |
771 | "READNULLCMD" "REPORTTIME" "RPROMPT" "RPS1" "SAVEHIST" "SPROMPT" | |
772 | "STTY" "TIMEFMT" "TMOUT" "TMPPREFIX" "varcmds" "watch" "WATCH" | |
773 | "WATCHFMT" "WORDCHARS" "ZDOTDIR")) | |
774 | "List of all shell variables available for completing read. | |
775 | See `sh-feature'.") | |
776 | ||
777 | ||
778 | ||
779 | (defvar sh-font-lock-keywords | |
780 | '((csh eval sh-append shell | |
781 | '("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1 | |
225f6185 | 782 | font-lock-variable-name-face)) |
133693bc | 783 | |
133693bc KH |
784 | (es eval sh-append executable-font-lock-keywords |
785 | '("\\$#?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\)" 1 | |
225f6185 | 786 | font-lock-variable-name-face)) |
133693bc | 787 | |
84bfbb44 | 788 | (rc eval identity es) |
133693bc KH |
789 | |
790 | (sh eval sh-append shell | |
4d7ce99c | 791 | ;; Variable names. |
133693bc | 792 | '("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2 |
4d7ce99c RS |
793 | font-lock-variable-name-face) |
794 | ;; Function names. | |
795 | '("^\\(\\sw+\\)[ \t]*(" 1 font-lock-function-name-face) | |
796 | '("\\<\\(function\\)\\>[ \t]*\\(\\sw+\\)?" | |
797 | (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t))) | |
133693bc KH |
798 | |
799 | ;; The next entry is only used for defining the others | |
800 | (shell eval sh-append executable-font-lock-keywords | |
b48a16d2 | 801 | '("\\\\[^A-Za-z0-9]" 0 font-lock-string-face) |
133693bc | 802 | '("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1 |
547745f5 RS |
803 | font-lock-variable-name-face)) |
804 | (rpm eval sh-append rpm2 | |
805 | '("%{?\\(\\sw+\\)" 1 font-lock-keyword-face)) | |
806 | (rpm2 eval sh-append shell | |
807 | '("^\\(\\sw+\\):" 1 font-lock-variable-name-face))) | |
38c979d3 | 808 | "Default expressions to highlight in Shell Script modes. See `sh-feature'.") |
133693bc | 809 | |
84bfbb44 | 810 | (defvar sh-font-lock-keywords-1 |
bfc8e97b | 811 | '((sh "[ \t]in\\>")) |
38c979d3 | 812 | "Subdued level highlighting for Shell Script modes.") |
84bfbb44 KH |
813 | |
814 | (defvar sh-font-lock-keywords-2 () | |
38c979d3 | 815 | "Gaudy level highlighting for Shell Script modes.") |
84bfbb44 | 816 | |
38c979d3 SM |
817 | (defconst sh-font-lock-syntactic-keywords |
818 | ;; Mark a `#' character as having punctuation syntax in a variable reference. | |
87002f70 SM |
819 | ;; Really we should do this properly. From Chet Ramey and Brian Fox: |
820 | ;; "A `#' begins a comment when it is unquoted and at the beginning of a | |
821 | ;; word. In the shell, words are separated by metacharacters." | |
822 | ;; To do this in a regexp would be slow as it would be anchored to the right. | |
823 | ;; But I can't be bothered to write a function to do it properly and | |
824 | ;; efficiently. So we only do it properly for `#' in variable references and | |
825 | ;; do it efficiently by anchoring the regexp to the left. | |
826 | '(("\\${?[^}#\n\t ]*\\(##?\\)" 1 (1 . nil)))) | |
f964dfcb GM |
827 | |
828 | (defgroup sh-indentation nil | |
829 | "Variables controlling indentation in shell scripts. | |
830 | ||
831 | Note: customizing these variables will not affect existing buffers if | |
832 | `sh-make-vars-local' is no-nil. See the documentation for | |
833 | variable `sh-make-vars-local', command `sh-make-vars-local' | |
834 | and command `sh-reset-indent-vars-to-global-values'." | |
835 | :group 'sh-script) | |
836 | ||
837 | ||
838 | (defcustom sh-set-shell-hook nil | |
839 | "*Hook run by `sh-set-shell'." | |
840 | :type 'hook | |
841 | :group 'sh-script) | |
842 | ||
843 | (defcustom sh-mode-hook nil | |
844 | "*Hook run by `sh-mode'." | |
845 | :type 'hook | |
846 | :group 'sh-script) | |
847 | ||
848 | (defcustom sh-learn-basic-offset nil | |
849 | "*When `sh-guess-basic-offset' should learn `sh-basic-offset'. | |
850 | ||
851 | nil mean: never. | |
852 | t means: only if there seems to be an obvious value. | |
853 | Anything else means: whenever we have a \"good guess\" as to the value." | |
854 | :type '(choice | |
855 | (const :tag "Never" nil) | |
856 | (const :tag "Only if sure" t) | |
857 | (const :tag "If have a good guess" usually) | |
858 | ) | |
859 | :group 'sh-indentation) | |
860 | ||
861 | (defcustom sh-popup-occur-buffer nil | |
862 | "*Controls when `sh-learn-buffer-indent' poos the *indent* buffer. | |
863 | If t it is always shown. If nil, it is shown only when there | |
864 | are conflicts." | |
865 | :type '(choice | |
866 | (const :tag "Only when there are conflicts." nil) | |
867 | (const :tag "Always" t) | |
868 | ) | |
869 | :group 'sh-indentation) | |
870 | ||
871 | (defcustom sh-blink t | |
872 | "*If non-nil, `sh-show-indent' shows the line indentation is relative to. | |
873 | The position on the line is not necessarily meaningful. | |
874 | In some cases the line will be the matching keyword, but this is not | |
875 | always the case." | |
876 | :type 'boolean | |
877 | :group 'sh-indentation) | |
878 | ||
879 | (defcustom sh-first-lines-indent 0 | |
880 | "*The indentation of the first non-blank non-comment line. | |
881 | Usually 0 meaning first column. | |
882 | Can be set to a number, or to nil which means leave it as is." | |
883 | :type '(choice | |
884 | (const :tag "Leave as is" nil) | |
885 | (integer :tag "Column number" | |
886 | :menu-tag "Indent to this col (0 means first col)" ) | |
887 | ) | |
888 | :group 'sh-indentation) | |
889 | ||
890 | ||
891 | (defcustom sh-basic-offset 4 | |
892 | "*The default indentation incrementation. | |
893 | This value is used for the + and - symbols in an indentation variable." | |
894 | :type 'integer | |
895 | :group 'sh-indentation) | |
896 | ||
897 | (defcustom sh-indent-comment nil | |
898 | "*How a comment line is to be indented. | |
899 | nil means leave it as it is; | |
900 | t means indent it as a normal line, aligning it to previous non-blank | |
901 | non-comment line; | |
902 | a number means align to that column, e.g. 0 means fist column." | |
903 | :type '(choice | |
904 | (const :tag "Leave as is." nil) | |
905 | (const :tag "Indent as a normal line." t) | |
906 | (integer :menu-tag "Indent to this col (0 means first col)." | |
907 | :tag "Indent to column number.") ) | |
908 | :group 'sh-indentation) | |
909 | ||
910 | ||
911 | (defvar sh-debug nil | |
912 | "Enable lots of debug messages - if function `sh-debug' is enabled.") | |
913 | ||
914 | ||
915 | ;; Uncomment this defun and comment the defmacro for debugging. | |
916 | ;; (defun sh-debug (&rest args) | |
917 | ;; "For debugging: display message ARGS if variable SH-DEBUG is non-nil." | |
918 | ;; (if sh-debug | |
919 | ;; (apply 'message args))) | |
920 | (defmacro sh-debug (&rest args)) | |
921 | ||
922 | (setq sh-symbol-list | |
923 | '( | |
924 | (const :tag "+ " :value + | |
925 | :menu-tag "+ Indent right by sh-basic-offset") | |
926 | (const :tag "- " :value - | |
927 | :menu-tag "- Indent left by sh-basic-offset") | |
928 | (const :tag "++" :value ++ | |
929 | :menu-tag "++ Indent right twice sh-basic-offset") | |
930 | (const :tag "--" :value -- | |
931 | :menu-tag "-- Indent left twice sh-basic-offset") | |
932 | (const :tag "* " :value * | |
933 | :menu-tag "* Indent right half sh-basic-offset") | |
934 | (const :tag "/ " :value / | |
935 | :menu-tag "/ Indent left half sh-basic-offset") | |
936 | )) | |
937 | ||
938 | (defcustom sh-indent-for-else 0 | |
939 | "*How much to indent an else relative to an if. Usually 0." | |
940 | :type `(choice | |
941 | (integer :menu-tag "A number (positive=>indent right)" | |
942 | :tag "A number") | |
943 | (const :tag "--") ;; separator! | |
944 | ,@ sh-symbol-list | |
945 | ) | |
946 | :group 'sh-indentation) | |
947 | ||
948 | (setq sh-number-or-symbol-list | |
949 | (append (list '( | |
950 | integer :menu-tag "A number (positive=>indent right)" | |
951 | :tag "A number") | |
952 | '(const :tag "--") ;; separator | |
953 | ) | |
954 | sh-symbol-list)) | |
955 | ||
956 | (defcustom sh-indent-for-fi 0 | |
957 | "*How much to indent a fi relative to an if. Usually 0." | |
958 | :type `(choice ,@ sh-number-or-symbol-list ) | |
959 | :group 'sh-indentation) | |
960 | ||
961 | (defcustom sh-indent-for-done '0 | |
962 | "*How much to indent a done relative to its matching stmt. Usually 0." | |
963 | :type `(choice ,@ sh-number-or-symbol-list ) | |
964 | :group 'sh-indentation) | |
965 | ||
966 | (defcustom sh-indent-after-else '+ | |
967 | "*How much to indent a statement after an else statement." | |
968 | :type `(choice ,@ sh-number-or-symbol-list ) | |
969 | :group 'sh-indentation) | |
970 | ||
971 | (defcustom sh-indent-after-if '+ | |
972 | "*How much to indent a statement after an if statement. | |
973 | This includes lines after else and elif statements, too, but | |
974 | does not affect then else elif or fi statements themselves." | |
975 | :type `(choice ,@ sh-number-or-symbol-list ) | |
976 | :group 'sh-indentation) | |
977 | ||
978 | (defcustom sh-indent-for-then '+ | |
979 | "*How much to indent an then relative to an if." | |
980 | :type `(choice ,@ sh-number-or-symbol-list ) | |
981 | :group 'sh-indentation) | |
982 | ||
983 | (defcustom sh-indent-for-do '* | |
984 | "*How much to indent a do statement. | |
985 | This is relative to the statement before the do, i.e. the | |
986 | while until or for statement." | |
987 | :type `(choice ,@ sh-number-or-symbol-list) | |
988 | :group 'sh-indentation) | |
989 | ||
990 | (defcustom sh-indent-after-do '* | |
991 | "*How much to indent a line after a do statement. | |
992 | This is used when the do is the first word of the line. | |
993 | This is relative to the statement before the do, e.g. a | |
994 | while for repeat or select statement." | |
995 | :type `(choice ,@ sh-number-or-symbol-list) | |
996 | :group 'sh-indentation) | |
997 | ||
998 | (defcustom sh-indent-after-loop-construct '+ | |
999 | "*How much to indent a statement after a loop construct. | |
1000 | ||
1001 | This variable is used when the keyword \"do\" is on the same line as the | |
1002 | loop statement (e.g. \"until\", \"while\" or \"for\"). | |
1003 | If the do is on a line by itself, then `sh-indent-after-do' is used instead." | |
1004 | :type `(choice ,@ sh-number-or-symbol-list) | |
1005 | :group 'sh-indentation) | |
1006 | ||
1007 | ||
1008 | (defcustom sh-indent-after-done 0 | |
1009 | "*How much to indent a statement after a \"done\" keyword. | |
1010 | Normally this is 0, which aligns the \"done\" to the matching | |
1011 | looping construct line. | |
1012 | Setting it non-zero allows you to have the \"do\" statement on a line | |
1013 | by itself and align the done under to do." | |
1014 | :type `(choice ,@ sh-number-or-symbol-list) | |
1015 | :group 'sh-indentation) | |
1016 | ||
1017 | (defcustom sh-indent-for-case-label '+ | |
1018 | "*How much to indent a case label statement. | |
1019 | This is relative to the line containing the case statement." | |
1020 | :type `(choice ,@ sh-number-or-symbol-list) | |
1021 | :group 'sh-indentation) | |
1022 | ||
1023 | (defcustom sh-indent-for-case-alt '++ | |
1024 | "*How much to indent statements after the case label. | |
1025 | This is relative to the line containing the case statement." | |
1026 | :type `(choice ,@ sh-number-or-symbol-list) | |
1027 | :group 'sh-indentation) | |
1028 | ||
1029 | ||
1030 | (defcustom sh-indent-for-continuation '+ | |
1031 | "*How much to indent for a continuation statement." | |
1032 | :type `(choice ,@ sh-number-or-symbol-list) | |
1033 | :group 'sh-indentation) | |
1034 | ||
1035 | (defcustom sh-indent-after-open '+ | |
1036 | "*How much to indent after a line with an opening parenthesis or brace. | |
1037 | For an open paren after a function `sh-indent-after-function' is used." | |
1038 | :type `(choice ,@ sh-number-or-symbol-list) | |
1039 | :group 'sh-indentation) | |
1040 | ||
1041 | (defcustom sh-indent-after-function '+ | |
1042 | "*How much to indent after a function line." | |
1043 | :type `(choice ,@ sh-number-or-symbol-list) | |
1044 | :group 'sh-indentation) | |
1045 | ||
1046 | ;; These 2 are for the rc shell: | |
1047 | ||
1048 | (defcustom sh-indent-after-switch '+ | |
1049 | "*How much to indent a case statement relative to the switch statement. | |
1050 | This is for the rc shell." | |
1051 | :type `(choice ,@ sh-number-or-symbol-list) | |
1052 | :group 'sh-indentation) | |
1053 | ||
1054 | (defcustom sh-indent-after-case '+ | |
1055 | "*How much to indent a statement relative to the case statement. | |
1056 | This is for the rc shell." | |
1057 | :type `(choice ,@ sh-number-or-symbol-list) | |
1058 | :group 'sh-indentation) | |
1059 | ||
1060 | (defface sh-heredoc-face | |
1061 | '((((class color) | |
1062 | (background dark)) | |
1063 | (:foreground "yellow" :bold t)) | |
1064 | (((class color) | |
1065 | (background light)) | |
1066 | (:foreground "tan" )) | |
1067 | (t | |
1068 | (:bold t))) | |
1069 | "Face to show a here-document" | |
1070 | :group 'sh-indentation) | |
1071 | ||
1072 | (defface sh-st-face | |
1073 | '((((class color) | |
1074 | (background dark)) | |
1075 | (:foreground "yellow" :bold t)) | |
1076 | (((class color) | |
1077 | (background light)) | |
1078 | (:foreground "tan" )) | |
1079 | (t | |
1080 | (:bold t))) | |
1081 | "Face to show characters with special syntax properties." | |
1082 | :group 'sh-indentation) | |
1083 | ||
1084 | ||
1085 | ;; Internal use - not designed to be changed by the user: | |
1086 | ||
1087 | ;; These are used for the syntax table stuff (derived from cperl-mode). | |
1088 | ;; Note: parse-sexp-lookup-properties must be set to t for it to work. | |
1089 | (defconst sh-here-doc-syntax '(15)) ;; generic string | |
1090 | (defconst sh-st-punc '(1)) | |
1091 | (defconst sh-special-syntax sh-st-punc) | |
1092 | ||
1093 | (defun sh-mkword-regexpr (word) | |
1094 | "Make a regexp which matches WORD as a word. | |
1095 | This specifically excludes an occurance of WORD followed by | |
1096 | punctuation characters like '-'." | |
1097 | (concat word "\\([^-a-z0-9_]\\|$\\)")) | |
1098 | ||
1099 | (defun sh-mkword-regexp (word) | |
1100 | "Make a regexp which matches WORD as a word. | |
1101 | This specifically excludes an occurance of WORD followed by | |
1102 | or preceded by punctuation characters like '-'." | |
1103 | (concat "\\(^\\|[^-a-z0-9_]\\)" word "\\([^-a-z0-9_]\\|$\\)")) | |
1104 | ||
1105 | (setq sh-re-done (sh-mkword-regexpr "done")) | |
1106 | ||
1107 | ||
1108 | (defconst sh-kws-for-done | |
1109 | '( | |
1110 | (sh . ( "while" "until" "for" ) ) | |
1111 | (bash . ( "while" "until" "for" "select" ) ) | |
1112 | (ksh88 . ( "while" "until" "for" "select" ) ) | |
1113 | (zsh . ( "while" "until" "for" "repeat" "select" ) ) | |
1114 | ) | |
1115 | "Which keywords can match the word `done' in this shell." | |
1116 | ) | |
1117 | ||
1118 | ||
1119 | (defconst sh-indent-supported | |
1120 | '( | |
1121 | (sh . t) | |
1122 | (csh . nil) | |
1123 | (rc . t) | |
1124 | ) | |
1125 | "Shell types that shell indenting can do something with." | |
1126 | ) | |
1127 | ||
1128 | (defconst sh-electric-rparen-needed | |
1129 | '( | |
1130 | (sh . t)) | |
1131 | "Non-nil if the shell type needs an electric handling of case alternatives." | |
1132 | ) | |
1133 | ||
1134 | (defconst sh-var-list | |
1135 | '( | |
1136 | sh-basic-offset sh-first-lines-indent sh-indent-after-case | |
1137 | sh-indent-after-do sh-indent-after-done | |
1138 | sh-indent-after-else | |
1139 | sh-indent-after-if | |
1140 | sh-indent-after-loop-construct | |
1141 | sh-indent-after-open | |
1142 | sh-indent-comment | |
1143 | sh-indent-for-case-alt | |
1144 | sh-indent-for-case-label | |
1145 | sh-indent-for-continuation | |
1146 | sh-indent-for-do | |
1147 | sh-indent-for-done | |
1148 | sh-indent-for-else | |
1149 | sh-indent-for-fi | |
1150 | sh-indent-for-then | |
1151 | ) | |
1152 | "A list of variables used by script mode to control indentation. | |
1153 | This list is used when switching between buffer-local and global | |
1154 | values of variables, and for the commands using indenation styles.") | |
1155 | ||
1156 | (defvar sh-make-vars-local t | |
1157 | "*Controls whether indentation variables are local to the buffer. | |
1158 | If non-nil, indentation variables are made local initially. | |
1159 | If nil, you can later make the variables local by invoking | |
1160 | command `sh-make-vars-local'. | |
1161 | The default is t because I assume that in one Emacs session one is | |
1162 | frequently editing existing scripts with different styles.") | |
1163 | ||
133693bc KH |
1164 | \f |
1165 | ;; mode-command and utility functions | |
1166 | ||
1167 | ;;;###autoload | |
5a989d6e | 1168 | (put 'sh-mode 'mode-class 'special) |
fc8318f6 EN |
1169 | |
1170 | ;;;###autoload | |
ac59aed8 RS |
1171 | (defun sh-mode () |
1172 | "Major mode for editing shell scripts. | |
1173 | This mode works for many shells, since they all have roughly the same syntax, | |
1174 | as far as commands, arguments, variables, pipes, comments etc. are concerned. | |
1175 | Unless the file's magic number indicates the shell, your usual shell is | |
1176 | assumed. Since filenames rarely give a clue, they are not further analyzed. | |
1177 | ||
133693bc KH |
1178 | This mode adapts to the variations between shells (see `sh-set-shell') by |
1179 | means of an inheritance based feature lookup (see `sh-feature'). This | |
1180 | mechanism applies to all variables (including skeletons) that pertain to | |
1181 | shell-specific features. | |
ac59aed8 | 1182 | |
133693bc KH |
1183 | The default style of this mode is that of Rosenblatt's Korn shell book. |
1184 | The syntax of the statements varies with the shell being used. The | |
1185 | following commands are available, based on the current shell's syntax: | |
ac59aed8 RS |
1186 | |
1187 | \\[sh-case] case statement | |
1188 | \\[sh-for] for loop | |
1189 | \\[sh-function] function definition | |
1190 | \\[sh-if] if statement | |
1191 | \\[sh-indexed-loop] indexed loop from 1 to n | |
133693bc KH |
1192 | \\[sh-while-getopts] while getopts loop |
1193 | \\[sh-repeat] repeat loop | |
1194 | \\[sh-select] select loop | |
ac59aed8 RS |
1195 | \\[sh-until] until loop |
1196 | \\[sh-while] while loop | |
1197 | ||
f964dfcb GM |
1198 | For sh and rc shells indentation commands are: |
1199 | \\[sh-show-indent] Show the variable controlling this line's indentation. | |
1200 | \\[sh-set-indent] Set then variable controlling this line's indentation. | |
1201 | \\[sh-learn-line-indent] Change the indentation variable so this line | |
1202 | would indent to the way it currently is. | |
1203 | \\[sh-learn-buffer-indent] Set the indentation variables so the | |
1204 | buffer indents as it currently is indendeted. | |
1205 | ||
1206 | ||
ac59aed8 RS |
1207 | \\[backward-delete-char-untabify] Delete backward one position, even if it was a tab. |
1208 | \\[sh-newline-and-indent] Delete unquoted space and indent new line same as this one. | |
1209 | \\[sh-end-of-command] Go to end of successive commands. | |
1210 | \\[sh-beginning-of-command] Go to beginning of successive commands. | |
1211 | \\[sh-set-shell] Set this buffer's shell, and maybe its magic number. | |
133693bc | 1212 | \\[sh-execute-region] Have optional header and region be executed in a subshell. |
ac59aed8 | 1213 | |
ac59aed8 RS |
1214 | \\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document. |
1215 | {, (, [, ', \", ` | |
133693bc KH |
1216 | Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``. |
1217 | ||
1218 | If you generally program a shell different from your login shell you can | |
aafd074a | 1219 | set `sh-shell-file' accordingly. If your shell's file name doesn't correctly |
133693bc KH |
1220 | indicate what shell it is use `sh-alias-alist' to translate. |
1221 | ||
1222 | If your shell gives error messages with line numbers, you can use \\[executable-interpret] | |
1223 | with your script for an edit-interpret-debug cycle." | |
ac59aed8 RS |
1224 | (interactive) |
1225 | (kill-all-local-variables) | |
ac59aed8 RS |
1226 | (use-local-map sh-mode-map) |
1227 | (make-local-variable 'indent-line-function) | |
133693bc | 1228 | (make-local-variable 'indent-region-function) |
84bfbb44 | 1229 | (make-local-variable 'skeleton-end-hook) |
133693bc KH |
1230 | (make-local-variable 'paragraph-start) |
1231 | (make-local-variable 'paragraph-separate) | |
ac59aed8 RS |
1232 | (make-local-variable 'comment-start) |
1233 | (make-local-variable 'comment-start-skip) | |
ac59aed8 | 1234 | (make-local-variable 'require-final-newline) |
133693bc | 1235 | (make-local-variable 'sh-header-marker) |
aafd074a | 1236 | (make-local-variable 'sh-shell-file) |
ac59aed8 | 1237 | (make-local-variable 'sh-shell) |
81ed2d75 KH |
1238 | (make-local-variable 'skeleton-pair-alist) |
1239 | (make-local-variable 'skeleton-pair-filter) | |
133693bc KH |
1240 | (make-local-variable 'comint-dynamic-complete-functions) |
1241 | (make-local-variable 'comint-prompt-regexp) | |
84bfbb44 | 1242 | (make-local-variable 'font-lock-defaults) |
133693bc | 1243 | (make-local-variable 'skeleton-filter) |
cd76025c | 1244 | (make-local-variable 'skeleton-newline-indent-rigidly) |
5d73ac66 RS |
1245 | (make-local-variable 'sh-shell-variables) |
1246 | (make-local-variable 'sh-shell-variables-initialized) | |
aa2c2426 | 1247 | (make-local-variable 'imenu-generic-expression) |
f964dfcb GM |
1248 | (make-local-variable 'sh-electric-rparen-needed-here) |
1249 | (make-local-variable 'sh-indent-supported-here) | |
b0622822 | 1250 | (make-local-variable 'font-lock-unfontify-region-function) |
ac59aed8 RS |
1251 | (setq major-mode 'sh-mode |
1252 | mode-name "Shell-script" | |
133693bc KH |
1253 | ;; not very clever, but enables wrapping skeletons around regions |
1254 | indent-region-function (lambda (b e) | |
84bfbb44 KH |
1255 | (save-excursion |
1256 | (goto-char b) | |
1257 | (skip-syntax-backward "-") | |
1258 | (setq b (point)) | |
1259 | (goto-char e) | |
1260 | (skip-syntax-backward "-") | |
1261 | (indent-rigidly b (point) sh-indentation))) | |
1262 | skeleton-end-hook (lambda () | |
1263 | (or (eolp) (newline) (indent-relative))) | |
bfc8e97b | 1264 | paragraph-start (concat page-delimiter "\\|$") |
133693bc | 1265 | paragraph-separate paragraph-start |
ac59aed8 | 1266 | comment-start "# " |
133693bc KH |
1267 | comint-dynamic-complete-functions sh-dynamic-complete-functions |
1268 | ;; we can't look if previous line ended with `\' | |
1269 | comint-prompt-regexp "^[ \t]*" | |
84bfbb44 | 1270 | font-lock-defaults |
38c979d3 SM |
1271 | '((sh-font-lock-keywords |
1272 | sh-font-lock-keywords-1 sh-font-lock-keywords-2) | |
1273 | nil nil | |
1274 | ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil | |
1275 | (font-lock-syntactic-keywords . sh-font-lock-syntactic-keywords)) | |
b0622822 GM |
1276 | font-lock-unfontify-region-function |
1277 | 'sh-font-lock-unfontify-region-function | |
81ed2d75 KH |
1278 | skeleton-pair-alist '((?` _ ?`)) |
1279 | skeleton-pair-filter 'sh-quoted-p | |
133693bc KH |
1280 | skeleton-further-elements '((< '(- (min sh-indentation |
1281 | (current-column))))) | |
cd76025c | 1282 | skeleton-filter 'sh-feature |
f964dfcb GM |
1283 | skeleton-newline-indent-rigidly t |
1284 | sh-electric-rparen-needed-here nil | |
1285 | sh-indent-supported-here nil) | |
a8fa6fc2 RS |
1286 | (make-local-variable 'parse-sexp-ignore-comments) |
1287 | (setq parse-sexp-ignore-comments t) | |
e3dce9ba RS |
1288 | ;; Parse or insert magic number for exec, and set all variables depending |
1289 | ;; on the shell thus determined. | |
1290 | (let ((interpreter | |
1291 | (save-excursion | |
1292 | (goto-char (point-min)) | |
547745f5 | 1293 | (cond ((looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)") |
1448f589 KH |
1294 | (match-string 2)) |
1295 | ((and buffer-file-name | |
1296 | (string-match "\\.m?spec$" buffer-file-name)) | |
547745f5 | 1297 | "rpm"))))) |
e3dce9ba | 1298 | (if interpreter |
22d5cb50 | 1299 | (sh-set-shell interpreter nil nil) |
1a8eb6d5 | 1300 | (sh-set-shell sh-shell-file nil nil)) |
5bd471e8 | 1301 | (run-hooks 'sh-mode-hook))) |
133693bc | 1302 | ;;;###autoload |
ac59aed8 RS |
1303 | (defalias 'shell-script-mode 'sh-mode) |
1304 | ||
1305 | ||
84bfbb44 KH |
1306 | (defun sh-font-lock-keywords (&optional keywords) |
1307 | "Function to get simple fontification based on `sh-font-lock-keywords'. | |
1308 | This adds rules for comments and assignments." | |
1309 | (sh-feature sh-font-lock-keywords | |
b946fbad RS |
1310 | (when (stringp (sh-feature sh-assignment-regexp)) |
1311 | (lambda (list) | |
1312 | `((,(sh-feature sh-assignment-regexp) | |
1313 | 1 font-lock-variable-name-face) | |
1314 | ,@keywords | |
1315 | ,@list))))) | |
84bfbb44 KH |
1316 | |
1317 | (defun sh-font-lock-keywords-1 (&optional builtins) | |
1318 | "Function to get better fontification including keywords." | |
1319 | (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\(\\(" | |
1320 | (mapconcat 'identity | |
1321 | (sh-feature sh-leading-keywords) | |
1322 | "\\|") | |
1323 | "\\)[ \t]+\\)?\\(" | |
1324 | (mapconcat 'identity | |
1325 | (append (sh-feature sh-leading-keywords) | |
1326 | (sh-feature sh-other-keywords)) | |
1327 | "\\|") | |
1328 | "\\)"))) | |
1329 | (sh-font-lock-keywords | |
1330 | `(,@(if builtins | |
1331 | `((,(concat keywords "[ \t]+\\)?\\(" | |
1332 | (mapconcat 'identity (sh-feature sh-builtins) "\\|") | |
1333 | "\\)\\>") | |
1334 | (2 font-lock-keyword-face nil t) | |
f802bd02 | 1335 | (6 font-lock-builtin-face)) |
84bfbb44 KH |
1336 | ,@(sh-feature sh-font-lock-keywords-2))) |
1337 | (,(concat keywords "\\)\\>") | |
1338 | 2 font-lock-keyword-face) | |
1339 | ,@(sh-feature sh-font-lock-keywords-1))))) | |
1340 | ||
1341 | (defun sh-font-lock-keywords-2 () | |
1342 | "Function to get better fontification including keywords and builtins." | |
1343 | (sh-font-lock-keywords-1 t)) | |
1344 | ||
ac59aed8 | 1345 | |
616db04b | 1346 | (defun sh-set-shell (shell &optional no-query-flag insert-flag) |
133693bc | 1347 | "Set this buffer's shell to SHELL (a string). |
e3dce9ba RS |
1348 | Makes this script executable via `executable-set-magic', and sets up the |
1349 | proper starting #!-line, if INSERT-FLAG is non-nil. | |
133693bc | 1350 | Calls the value of `sh-set-shell-hook' if set." |
84bfbb44 KH |
1351 | (interactive (list (completing-read "Name or path of shell: " |
1352 | interpreter-mode-alist | |
616db04b RS |
1353 | (lambda (x) (eq (cdr x) 'sh-mode))) |
1354 | (eq executable-query 'function) | |
1355 | t)) | |
842cc0e6 | 1356 | (if (string-match "\\.exe\\'" shell) |
c8b88e9f | 1357 | (setq shell (substring shell 0 (match-beginning 0)))) |
133693bc KH |
1358 | (setq sh-shell (intern (file-name-nondirectory shell)) |
1359 | sh-shell (or (cdr (assq sh-shell sh-alias-alist)) | |
616db04b | 1360 | sh-shell)) |
e3dce9ba RS |
1361 | (if insert-flag |
1362 | (setq sh-shell-file | |
1363 | (executable-set-magic shell (sh-feature sh-shell-arg) | |
1364 | no-query-flag insert-flag))) | |
616db04b | 1365 | (setq require-final-newline (sh-feature sh-require-final-newline) |
aafd074a | 1366 | ;;; local-abbrev-table (sh-feature sh-abbrevs) |
2718efdd RS |
1367 | ;; Packages should not need to set these variables directly. sm. |
1368 | ; font-lock-keywords nil ; force resetting | |
1369 | ; font-lock-syntax-table nil | |
c8005e70 | 1370 | comment-start-skip "#+[\t ]*" |
133693bc | 1371 | mode-line-process (format "[%s]" sh-shell) |
5a989d6e | 1372 | sh-shell-variables nil |
5d73ac66 | 1373 | sh-shell-variables-initialized nil |
aa2c2426 | 1374 | imenu-generic-expression (sh-feature sh-imenu-generic-expression) |
c8b88e9f | 1375 | imenu-case-fold-search nil) |
0775c032 RS |
1376 | (set-syntax-table (or (sh-feature sh-mode-syntax-table) |
1377 | (standard-syntax-table))) | |
c8b88e9f RS |
1378 | (let ((vars (sh-feature sh-variables))) |
1379 | (while vars | |
1380 | (sh-remember-variable (car vars)) | |
1381 | (setq vars (cdr vars)))) | |
2718efdd RS |
1382 | ;; Packages should not need to toggle Font Lock mode. sm. |
1383 | ; (and (boundp 'font-lock-mode) | |
1384 | ; font-lock-mode | |
1385 | ; (font-lock-mode (font-lock-mode 0))) | |
f964dfcb GM |
1386 | (if (setq sh-indent-supported-here (sh-feature sh-indent-supported)) |
1387 | (progn | |
1388 | (message "Setting up indent for shell type %s" sh-shell) | |
1389 | (make-local-variable 'sh-kw-alist) | |
1390 | (make-local-variable 'sh-regexp-for-done) | |
1391 | (make-local-variable 'parse-sexp-lookup-properties) | |
1392 | (setq sh-electric-rparen-needed-here | |
1393 | (sh-feature sh-electric-rparen-needed)) | |
1394 | (setq parse-sexp-lookup-properties t) | |
1395 | (sh-scan-buffer) | |
1396 | (setq sh-kw-alist (sh-feature sh-kw)) | |
1397 | (let ((regexp (sh-feature sh-kws-for-done))) | |
1398 | (if regexp | |
1399 | (setq sh-regexp-for-done | |
1400 | (sh-mkword-regexpr (regexp-opt regexp t))))) | |
1401 | (message "setting up indent stuff") | |
1402 | ;; sh-mode has already made indent-line-function local | |
1403 | ;; but do it in case this is called before that. | |
1404 | (make-local-variable 'indent-line-function) | |
1405 | (setq indent-line-function 'sh-indent-line) | |
1406 | ;; This is very inefficient, but this at least makes indent-region work: | |
1407 | (make-local-variable 'indent-region-function) | |
1408 | (setq indent-region-function nil) | |
1409 | (if sh-make-vars-local | |
1410 | (sh-make-vars-local)) | |
1411 | (message "Indentation setup for shell type %s" sh-shell)) | |
1412 | (message "No indentation for this shell type.") | |
1413 | (setq indent-line-function 'sh-basic-indent-line)) | |
133693bc KH |
1414 | (run-hooks 'sh-set-shell-hook)) |
1415 | ||
1416 | ||
ac59aed8 | 1417 | |
133693bc KH |
1418 | (defun sh-feature (list &optional function) |
1419 | "Index ALIST by the current shell. | |
1420 | If ALIST isn't a list where every element is a cons, it is returned as is. | |
1421 | Else indexing follows an inheritance logic which works in two ways: | |
1422 | ||
1423 | - Fall back on successive ancestors (see `sh-ancestor-alist') as long as | |
1424 | the alist contains no value for the current shell. | |
1c64011b | 1425 | The ultimate default is always `sh'. |
133693bc KH |
1426 | |
1427 | - If the value thus looked up is a list starting with `eval' its `cdr' is | |
1428 | first evaluated. If that is also a list and the first argument is a | |
1429 | symbol in ALIST it is not evaluated, but rather recursively looked up in | |
1430 | ALIST to allow the function called to define the value for one shell to be | |
1431 | derived from another shell. While calling the function, is the car of the | |
1432 | alist element is the current shell. | |
1433 | The value thus determined is physically replaced into the alist. | |
1434 | ||
1435 | Optional FUNCTION is applied to the determined value and the result is cached | |
1436 | in ALIST." | |
1437 | (or (if (consp list) | |
1438 | (let ((l list)) | |
1439 | (while (and l (consp (car l))) | |
1440 | (setq l (cdr l))) | |
1441 | (if l list))) | |
1442 | (if function | |
1443 | (cdr (assoc (setq function (cons sh-shell function)) list))) | |
1444 | (let ((sh-shell sh-shell) | |
1445 | elt val) | |
1446 | (while (and sh-shell | |
1447 | (not (setq elt (assq sh-shell list)))) | |
1448 | (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist)))) | |
1c64011b RS |
1449 | ;; If the shell is not known, treat it as sh. |
1450 | (unless elt | |
1451 | (setq elt (assq 'sh list))) | |
133693bc KH |
1452 | (if (and (consp (setq val (cdr elt))) |
1453 | (eq (car val) 'eval)) | |
1454 | (setcdr elt | |
1455 | (setq val | |
1456 | (eval (if (consp (setq val (cdr val))) | |
1457 | (let ((sh-shell (car (cdr val))) | |
1458 | function) | |
1459 | (if (assq sh-shell list) | |
1460 | (setcar (cdr val) | |
1461 | (list 'quote | |
1462 | (sh-feature list)))) | |
1463 | val) | |
1464 | val))))) | |
1465 | (if function | |
1466 | (nconc list | |
1467 | (list (cons function | |
1468 | (setq sh-shell (car function) | |
1469 | val (funcall (cdr function) val)))))) | |
1470 | val))) | |
1471 | ||
1472 | ||
1473 | ||
aafd074a KH |
1474 | ;;; I commented this out because nobody calls it -- rms. |
1475 | ;;;(defun sh-abbrevs (ancestor &rest list) | |
1476 | ;;; "Iff it isn't, define the current shell as abbrev table and fill that. | |
1477 | ;;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev | |
1478 | ;;;table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs | |
1479 | ;;;according to the remaining arguments NAMEi EXPANSIONi ... | |
1480 | ;;;EXPANSION may be either a string or a skeleton command." | |
1481 | ;;; (or (if (boundp sh-shell) | |
1482 | ;;; (symbol-value sh-shell)) | |
1483 | ;;; (progn | |
1484 | ;;; (if (listp ancestor) | |
1485 | ;;; (nconc list ancestor)) | |
1486 | ;;; (define-abbrev-table sh-shell ()) | |
1487 | ;;; (if (vectorp ancestor) | |
1488 | ;;; (mapatoms (lambda (atom) | |
1489 | ;;; (or (eq atom 0) | |
1490 | ;;; (define-abbrev (symbol-value sh-shell) | |
1491 | ;;; (symbol-name atom) | |
1492 | ;;; (symbol-value atom) | |
1493 | ;;; (symbol-function atom)))) | |
1494 | ;;; ancestor)) | |
1495 | ;;; (while list | |
1496 | ;;; (define-abbrev (symbol-value sh-shell) | |
1497 | ;;; (car list) | |
1498 | ;;; (if (stringp (car (cdr list))) | |
1499 | ;;; (car (cdr list)) | |
1500 | ;;; "") | |
1501 | ;;; (if (symbolp (car (cdr list))) | |
1502 | ;;; (car (cdr list)))) | |
1503 | ;;; (setq list (cdr (cdr list))))) | |
1504 | ;;; (symbol-value sh-shell))) | |
133693bc KH |
1505 | |
1506 | ||
1507 | (defun sh-mode-syntax-table (table &rest list) | |
f29601ad | 1508 | "Copy TABLE and set syntax for successive CHARs according to strings S." |
133693bc KH |
1509 | (setq table (copy-syntax-table table)) |
1510 | (while list | |
1511 | (modify-syntax-entry (car list) (car (cdr list)) table) | |
1512 | (setq list (cdr (cdr list)))) | |
1513 | table) | |
1514 | ||
1515 | ||
1516 | (defun sh-append (ancestor &rest list) | |
1517 | "Return list composed of first argument (a list) physically appended to rest." | |
1518 | (nconc list ancestor)) | |
1519 | ||
1520 | ||
1521 | (defun sh-modify (skeleton &rest list) | |
1522 | "Modify a copy of SKELETON by replacing I1 with REPL1, I2 with REPL2 ..." | |
1523 | (setq skeleton (copy-sequence skeleton)) | |
1524 | (while list | |
1525 | (setcar (or (nthcdr (car list) skeleton) | |
1526 | (error "Index %d out of bounds" (car list))) | |
1527 | (car (cdr list))) | |
1528 | (setq list (nthcdr 2 list))) | |
1529 | skeleton) | |
ac59aed8 RS |
1530 | |
1531 | ||
f964dfcb | 1532 | (defun sh-basic-indent-line () |
54c87e27 RS |
1533 | "Indent a line for Sh mode (shell script mode). |
1534 | Indent as far as preceding non-empty line, then by steps of `sh-indentation'. | |
133693bc | 1535 | Lines containing only comments are considered empty." |
ac59aed8 RS |
1536 | (interactive) |
1537 | (let ((previous (save-excursion | |
54c87e27 RS |
1538 | (while (and (progn (beginning-of-line) |
1539 | (not (bobp))) | |
b46c06de RS |
1540 | (progn |
1541 | (forward-line -1) | |
1542 | (back-to-indentation) | |
1543 | (or (eolp) | |
1544 | (eq (following-char) ?#))))) | |
133693bc KH |
1545 | (current-column))) |
1546 | current) | |
ac59aed8 RS |
1547 | (save-excursion |
1548 | (indent-to (if (eq this-command 'newline-and-indent) | |
1549 | previous | |
1550 | (if (< (current-column) | |
133693bc KH |
1551 | (setq current (progn (back-to-indentation) |
1552 | (current-column)))) | |
ac59aed8 | 1553 | (if (eolp) previous 0) |
133693bc KH |
1554 | (delete-region (point) |
1555 | (progn (beginning-of-line) (point))) | |
ac59aed8 | 1556 | (if (eolp) |
133693bc KH |
1557 | (max previous (* (1+ (/ current sh-indentation)) |
1558 | sh-indentation)) | |
1559 | (* (1+ (/ current sh-indentation)) sh-indentation)))))) | |
1560 | (if (< (current-column) (current-indentation)) | |
1561 | (skip-chars-forward " \t")))) | |
1562 | ||
1563 | ||
1564 | (defun sh-execute-region (start end &optional flag) | |
1565 | "Pass optional header and region to a subshell for noninteractive execution. | |
1566 | The working directory is that of the buffer, and only environment variables | |
1567 | are already set which is why you can mark a header within the script. | |
1568 | ||
1569 | With a positive prefix ARG, instead of sending region, define header from | |
1570 | beginning of buffer to point. With a negative prefix ARG, instead of sending | |
1571 | region, clear header." | |
1572 | (interactive "r\nP") | |
1573 | (if flag | |
1574 | (setq sh-header-marker (if (> (prefix-numeric-value flag) 0) | |
1575 | (point-marker))) | |
1576 | (if sh-header-marker | |
1577 | (save-excursion | |
1578 | (let (buffer-undo-list) | |
1579 | (goto-char sh-header-marker) | |
1580 | (append-to-buffer (current-buffer) start end) | |
1581 | (shell-command-on-region (point-min) | |
1582 | (setq end (+ sh-header-marker | |
1583 | (- end start))) | |
aafd074a | 1584 | sh-shell-file) |
133693bc | 1585 | (delete-region sh-header-marker end))) |
aafd074a | 1586 | (shell-command-on-region start end (concat sh-shell-file " -"))))) |
ac59aed8 RS |
1587 | |
1588 | ||
1589 | (defun sh-remember-variable (var) | |
1590 | "Make VARIABLE available for future completing reads in this buffer." | |
1591 | (or (< (length var) sh-remember-variable-min) | |
133693bc | 1592 | (getenv var) |
5a989d6e RS |
1593 | (assoc var sh-shell-variables) |
1594 | (setq sh-shell-variables (cons (cons var var) sh-shell-variables))) | |
ac59aed8 RS |
1595 | var) |
1596 | ||
1597 | ||
ac59aed8 RS |
1598 | |
1599 | (defun sh-quoted-p () | |
1600 | "Is point preceded by an odd number of backslashes?" | |
133693bc | 1601 | (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2))) |
ac59aed8 | 1602 | \f |
f964dfcb GM |
1603 | ;; Indentation stuff. |
1604 | (defun sh-must-be-shell-mode () | |
1605 | "Signal an error if not in Shell-script mode." | |
1606 | (unless (eq major-mode 'sh-mode) | |
1607 | (error "This buffer is not in Shell-script mode"))) | |
1608 | ||
1609 | (defun sh-must-support-indent () | |
1610 | "*Signal an error if the shell type for this buffer is not supported. | |
1611 | Also, the buffer must be in Shell-script mode." | |
1612 | (sh-must-be-shell-mode) | |
1613 | (unless sh-indent-supported-here | |
1614 | (error "This buffer's shell type is not supported for this command"))) | |
1615 | ||
1616 | (defun sh-make-vars-local () | |
1617 | "Make the indentation variables local to this buffer. | |
1618 | Normally they already are local. This command is provided in case | |
1619 | variable `sh-make-vars-local' has been set to nil. | |
1620 | ||
1621 | To revert all these variables to the global values, use | |
1622 | command `sh-reset-indent-vars-to-global-values'." | |
1623 | (interactive) | |
1624 | (sh-must-be-shell-mode) | |
1625 | (mapcar 'make-local-variable sh-var-list) | |
1626 | (message "Indentation variable are now local.")) | |
1627 | ||
1628 | (defun sh-reset-indent-vars-to-global-values () | |
1629 | "Reset local indenatation variables to the global values. | |
1630 | Then, if variable `sh-make-vars-local' is non-nil, make them local." | |
1631 | (interactive) | |
1632 | (sh-must-be-shell-mode) | |
1633 | (mapcar 'kill-local-variable sh-var-list) | |
1634 | (if sh-make-vars-local | |
1635 | (mapcar 'make-local-variable sh-var-list))) | |
1636 | ||
1637 | ||
1638 | (defvar sh-kw-alist nil | |
1639 | "A buffer-local, since it is shell-type dependent, list of keywords.") | |
1640 | ||
1641 | (defvar sh-regexp-for-done nil | |
1642 | "A buffer-local regexp to match opening keyword for done.") | |
1643 | ||
1644 | ;; Theoretically these are only needed in shell and derived modes. | |
1645 | ;; However, the routines which use them are only called in those modes. | |
1646 | (defconst sh-special-keywords "then\\|do") | |
1647 | ||
1648 | ;; ( key-word first-on-this on-prev-line ) | |
1649 | ;; This is used to set `sh-kw-alist' which is a list of sublists each | |
1650 | ;; having 3 elements: | |
1651 | ;; a keyword | |
1652 | ;; a rule to check when the keyword apepars on "this" line | |
1653 | ;; a rule to check when the keyword apepars on "the previous" line | |
1654 | ;; The keyword is usually a string and is the first word on a line. | |
1655 | ;; If this keyword appears on the line whose indenation is to be | |
1656 | ;; calculated, the rule in element 2 is called. If this returns | |
1657 | ;; non-zero, the resulting point (which may be changed by the rule) | |
1658 | ;; is used as the default indentation. | |
1659 | ;; If it returned false or the keyword was not found in the table, | |
1660 | ;; then the keyword from the previous line is looked up and the rule | |
1661 | ;; in element 3 is called. In this case, however, | |
1662 | ;; `sh-get-indent-info' does not stop but may keepp going and test | |
1663 | ;; other keywords against rules in element 3. This is because the | |
1664 | ;; precending line could have, for example, an opening "if" and an | |
1665 | ;; opening "while" keyword and we need to add the indentation offsets | |
1666 | ;; for both. | |
1667 | ;; | |
1668 | (defconst sh-kw | |
1669 | '( | |
1670 | (sh | |
1671 | ( "if" | |
1672 | nil | |
1673 | sh-handle-prev-if ) | |
1674 | ( "elif" | |
1675 | sh-handle-this-else | |
1676 | sh-handle-prev-else ) | |
1677 | ( "else" | |
1678 | sh-handle-this-else | |
1679 | sh-handle-prev-else ) | |
1680 | ( "fi" | |
1681 | sh-handle-this-fi | |
1682 | sh-handle-prev-fi ) | |
1683 | ( "then" | |
1684 | sh-handle-this-then | |
1685 | sh-handle-prev-then ) | |
1686 | ( "(" | |
1687 | nil | |
1688 | sh-handle-prev-open ) | |
1689 | ( "{" | |
1690 | nil | |
1691 | sh-handle-prev-open ) | |
1692 | ( "[" | |
1693 | nil | |
1694 | sh-handle-prev-open ) | |
1695 | ( "}" | |
1696 | sh-handle-this-close | |
1697 | nil ) | |
1698 | ( ")" | |
1699 | sh-handle-this-close | |
1700 | nil ) | |
1701 | ( "]" | |
1702 | sh-handle-this-close | |
1703 | nil ) | |
1704 | ( "case" | |
1705 | nil | |
1706 | sh-handle-prev-case ) | |
1707 | ( "esac" | |
1708 | sh-handle-this-esac | |
1709 | sh-handle-prev-esac ) | |
1710 | ( case-label | |
1711 | nil ;; ??? | |
1712 | sh-handle-after-case-label ) | |
1713 | ( ";;" | |
1714 | nil ;; ??? | |
1715 | sh-handle-prev-case-alt-end ;; ?? | |
1716 | ) | |
1717 | ( "done" | |
1718 | sh-handle-this-done | |
1719 | sh-handle-prev-done ) | |
1720 | ( "do" | |
1721 | sh-handle-this-do | |
1722 | sh-handle-prev-do ) | |
1723 | ) ;; end of sh | |
1724 | ||
1725 | ;; Note: we don't need specific stuff for bash and zsh shells; | |
1726 | ;; the regexp `sh-regexp-for-done' handles the extra keywords | |
1727 | ;; these shells use. | |
1728 | (rc | |
1729 | ( "{" | |
1730 | nil | |
1731 | sh-handle-prev-open ) | |
1732 | ( "}" | |
1733 | sh-handle-this-close | |
1734 | nil ) | |
1735 | ( "case" | |
1736 | sh-handle-this-rc-case | |
1737 | sh-handle-prev-rc-case ) | |
1738 | ) ;; end of rc | |
1739 | )) | |
1740 | ||
1741 | ||
1742 | (defun sh-help-string-for-variable (var) | |
1743 | "Construct a string for `sh-read-variable' when changing variable VAR ." | |
1744 | (let ((msg (documentation-property var 'variable-documentation)) | |
1745 | (msg2 "")) | |
1746 | (unless (or | |
1747 | (eq var 'sh-first-lines-indent) | |
1748 | (eq var 'sh-indent-comment)) | |
1749 | (setq msg2 | |
1750 | (format "\n | |
1751 | You can enter a number (positive to increase indentenation, | |
1752 | negative to decrease indentation, zero for no change to indentnation). | |
1753 | ||
1754 | Or, you can enter one of the following symbols which are relative to | |
1755 | the value of variable `sh-basic-offset' | |
1756 | which in this buffer is currently %s. | |
1757 | ||
1758 | \t%s." | |
1759 | sh-basic-offset | |
1760 | (mapconcat '(lambda (x) | |
1761 | (nth (1- (length x)) x) ) | |
1762 | sh-symbol-list "\n\t") | |
1763 | ))) | |
1764 | ||
1765 | (concat | |
1766 | ;; The following shows the global not the local value! | |
1767 | ;; (format "Current value of %s is %s\n\n" var (symbol-value var)) | |
1768 | msg msg2))) | |
1769 | ||
1770 | (defun sh-read-variable (var) | |
1771 | "Read a new value for indentation variable VAR." | |
1772 | (interactive "*variable? ") ;; to test | |
1773 | (let ((minibuffer-help-form `(sh-help-string-for-variable | |
1774 | (quote ,var))) | |
1775 | val) | |
1776 | (setq val (read-from-minibuffer | |
1777 | (format "New value for %s (press %s for help): " | |
1778 | var (single-key-description help-char)) | |
1779 | (format "%s" (symbol-value var)) | |
1780 | nil t)) | |
1781 | val)) | |
1782 | ||
1783 | ||
1784 | ||
1785 | (defun sh-in-comment-or-string (start) | |
1786 | "Return non-nil if START is in a comment or string." | |
1787 | (save-excursion | |
1788 | (let (state) | |
1789 | (beginning-of-line) | |
1790 | (setq state (parse-partial-sexp (point) start nil nil nil t)) | |
1791 | (or (nth 3 state)(nth 4 state))))) | |
1792 | ||
1793 | (defun sh-goto-matching-if () | |
1794 | "Go to the matching if for a fi. | |
1795 | This handles nested if..fi pairs." | |
1796 | (let ((found (sh-find-prev-matching "\\bif\\b" "\\bfi\\b" 1))) | |
1797 | (if found | |
1798 | (goto-char found)))) | |
1799 | ||
1800 | ||
1801 | ;; Functions named sh-handle-this-XXX are called when the keyword on the | |
1802 | ;; line whose indentation is being handled contain XXX; | |
1803 | ;; those named sh-handle-prev-XXX are when XXX appears on the prevoius line. | |
1804 | ||
1805 | (defun sh-handle-prev-if () | |
1806 | (list '(+ sh-indent-after-if))) | |
1807 | ||
1808 | (defun sh-handle-this-else () | |
1809 | (if (sh-goto-matching-if) | |
1810 | ;; (list "aligned to if") | |
1811 | (list "aligned to if" '(+ sh-indent-for-else)) | |
1812 | nil | |
1813 | )) | |
1814 | ||
1815 | (defun sh-handle-prev-else () | |
1816 | (if (sh-goto-matching-if) | |
1817 | (list '(+ sh-indent-after-if)) | |
1818 | )) | |
1819 | ||
1820 | (defun sh-handle-this-fi () | |
1821 | (if (sh-goto-matching-if) | |
1822 | (list "aligned to if" '(+ sh-indent-for-fi)) | |
1823 | nil | |
1824 | )) | |
1825 | ||
1826 | (defun sh-handle-prev-fi () | |
1827 | ;; Why do we have this rule? Because we must go back to the if | |
1828 | ;; to get its indent. We may continue back from there. | |
1829 | ;; We return nil because we don't have anything to add to result, | |
1830 | ;; the side affect of setting align-point is all that matters. | |
1831 | ;; we could return a comment (a string) but I can't think of a good one... | |
1832 | (sh-goto-matching-if) | |
1833 | nil) | |
1834 | ||
1835 | (defun sh-handle-this-then () | |
1836 | (let ((p (sh-goto-matching-if))) | |
1837 | (if p | |
1838 | (list '(+ sh-indent-for-then)) | |
1839 | ))) | |
1840 | ||
1841 | (defun sh-handle-prev-then () | |
1842 | (let ((p (sh-goto-matching-if))) | |
1843 | (if p | |
1844 | (list '(+ sh-indent-after-if)) | |
1845 | ))) | |
1846 | ||
1847 | (defun sh-handle-prev-open () | |
1848 | (save-excursion | |
1849 | (let ((x (sh-prev-stmt))) | |
1850 | (if (and x | |
1851 | (progn | |
1852 | (goto-char x) | |
1853 | (or | |
1854 | (looking-at "function\\b") | |
1855 | (looking-at "\\s-*\\S-+\\s-*()") | |
1856 | ))) | |
1857 | (list '(+ sh-indent-after-function)) | |
1858 | (list '(+ sh-indent-after-open))) | |
1859 | ))) | |
1860 | ||
1861 | (defun sh-handle-this-close () | |
1862 | (forward-char 1) ;; move over ")" | |
1863 | (let ((p (sh-safe-backward-sexp))) | |
1864 | (if p | |
1865 | (list "aligned to opening paren") | |
1866 | nil | |
1867 | ))) | |
1868 | ||
1869 | (defun sh-goto-matching-case () | |
1870 | (let ((found (sh-find-prev-matching "\\bcase\\b" "\\besac\\b" 1))) | |
1871 | (if found | |
1872 | (goto-char found)))) | |
1873 | ||
1874 | (defun sh-handle-prev-case () | |
1875 | ;; This is typically called when point is on same line as a case | |
1876 | ;; we shouldn't -- and can't find prev-case | |
1877 | (if (looking-at ".*\\bcase\\b") | |
1878 | (list '(+ sh-indent-for-case-label)) | |
1879 | (error "We don't see to be on a line with a case") ;; debug | |
1880 | )) | |
1881 | ||
1882 | (defun sh-handle-this-esac () | |
1883 | (let ((p (sh-goto-matching-case))) | |
1884 | (if p | |
1885 | (list "aligned to matching case") | |
1886 | nil | |
1887 | ))) | |
1888 | ||
1889 | ||
1890 | (defun sh-handle-prev-esac () | |
1891 | (let ((p (sh-goto-matching-case))) | |
1892 | (if p | |
1893 | (list "matching case") | |
1894 | nil | |
1895 | ))) | |
1896 | ||
1897 | (defun sh-handle-after-case-label () | |
1898 | (let ((p (sh-goto-matching-case))) | |
1899 | (if p | |
1900 | (list '( + sh-indent-for-case-alt )) | |
1901 | nil | |
1902 | ))) | |
1903 | ||
1904 | (defun sh-handle-prev-case-alt-end () | |
1905 | (let ((p (sh-goto-matching-case))) | |
1906 | (if p | |
1907 | (list '( + sh-indent-for-case-label )) | |
1908 | nil | |
1909 | ))) | |
1910 | ||
1911 | (defun sh-safe-backward-sexp () | |
1912 | "Try and do a `backward-sexp', but do not error. | |
1913 | Return new point if successful, nil if an error occurred." | |
1914 | (condition-case nil | |
1915 | (progn | |
1916 | (backward-sexp 1) | |
1917 | (point) ;; return point if successful | |
1918 | ) | |
1919 | (error | |
1920 | (sh-debug "oops!(0) %d" (point)) | |
1921 | nil ;; return nil if fail | |
1922 | ))) | |
1923 | ||
1924 | (defun sh-safe-forward-sexp () | |
1925 | "Try and do a `forward-sexp', but do not error. | |
1926 | Return new point if successful, nil if an error occurred." | |
1927 | (condition-case nil | |
1928 | (progn | |
1929 | (forward-sexp 1) | |
1930 | (point) ;; return point if successful | |
1931 | ) | |
1932 | (error | |
1933 | (sh-debug "oops!(1) %d" (point)) | |
1934 | nil ;; return nil if fail | |
1935 | ))) | |
1936 | ||
1937 | (defun sh-goto-match-for-done () | |
1938 | (let ((found (sh-find-prev-matching sh-regexp-for-done sh-re-done 1))) | |
1939 | (if found | |
1940 | (goto-char found)))) | |
1941 | ||
1942 | (defun sh-handle-this-done () | |
1943 | (if (sh-goto-match-for-done) | |
1944 | (list "aligned to do stmt" '(+ sh-indent-for-done)) | |
1945 | nil | |
1946 | )) | |
1947 | ||
1948 | (defun sh-handle-prev-done () | |
1949 | (if (sh-goto-match-for-done) | |
1950 | (list "previous done") | |
1951 | nil | |
1952 | )) | |
1953 | ||
1954 | (defun sh-handle-this-do () | |
1955 | (let ( (p (sh-goto-match-for-done)) ) | |
1956 | (if p | |
1957 | (list '(+ sh-indent-for-do)) | |
1958 | nil | |
1959 | ))) | |
1960 | ||
1961 | (defun sh-handle-prev-do () | |
1962 | (let ( (p) ) | |
1963 | (cond | |
1964 | ((save-restriction | |
1965 | (narrow-to-region | |
1966 | (point) | |
1967 | (save-excursion | |
1968 | (beginning-of-line) | |
1969 | (point))) | |
1970 | (sh-goto-match-for-done)) | |
1971 | (sh-debug "match for done found on THIS line") | |
1972 | (list '(+ sh-indent-after-loop-construct))) | |
1973 | ((sh-goto-match-for-done) | |
1974 | (sh-debug "match for done found on PREV line") | |
1975 | (list '(+ sh-indent-after-do))) | |
1976 | (t | |
1977 | (message "match for done NOT found") | |
1978 | nil)))) | |
1979 | ||
1980 | ;; for rc: | |
1981 | (defun sh-find-prev-switch () | |
1982 | "Find the line for the switch keyword matching this line's case keyword." | |
1983 | (re-search-backward "\\bswitch\\b" nil t)) | |
1984 | ||
1985 | (defun sh-handle-this-rc-case () | |
1986 | (if (sh-find-prev-switch) | |
1987 | (list '(+ sh-indent-after-switch)) | |
1988 | ;; (list '(+ sh-indent-for-case-label)) | |
1989 | nil)) | |
1990 | ||
1991 | (defun sh-handle-prev-rc-case () | |
1992 | (list '(+ sh-indent-after-case))) | |
1993 | ||
1994 | (defun sh-check-rule (n thing) | |
1995 | (let ((rule (nth n (assoc thing sh-kw-alist))) | |
1996 | (val nil)) | |
1997 | (if rule | |
1998 | (progn | |
1999 | (setq val (funcall rule)) | |
2000 | (sh-debug "rule (%d) for %s at %d is %s\n-> returned %s" | |
2001 | n thing (point) rule val))) | |
2002 | val)) | |
2003 | ||
2004 | ||
2005 | (defun sh-get-indent-info () | |
2006 | "Return indent-info for this line. | |
2007 | This is a list. nil means the line is to be left as is. | |
2008 | Otherwise it contains one or more of the following sublists: | |
2009 | \(t NUMBER\) NUMBER is the base location in the buffer that indendation is | |
2010 | relative to. If present, this is always the first of the | |
2011 | sublists. The indentation of the line in question is | |
2012 | derived from the indentation of this point, possibly | |
2013 | modified by subsequent sublists. | |
2014 | \(+ VAR\) | |
2015 | \(- VAR\) Get the value of variable VAR and add to or subtract from | |
2016 | the indentation calculated so far. | |
2017 | \(= VAR\) Get the value of variable VAR and *replace* the | |
2018 | indentation with itss value. This only occurs for | |
2019 | special variables such as `sh-indent-comment'. | |
2020 | STRING This is ignored for the purposes of calculating | |
2021 | indentation, it is printed in certain cases to help show | |
2022 | what the indentation is based on." | |
2023 | ;; See comments before `sh-kw'. | |
2024 | (save-excursion | |
2025 | (let ((prev-kw nil) | |
2026 | (prev-stmt nil) | |
2027 | (have-result nil) | |
2028 | depth-bol depth-eol | |
2029 | this-kw | |
2030 | (state nil) | |
2031 | state-bol | |
2032 | (depth-prev-bol nil) | |
2033 | start | |
2034 | func val | |
2035 | (result nil) | |
2036 | prev-lines-indent | |
2037 | (prev-list nil) | |
2038 | (this-list nil) | |
2039 | (align-point nil) | |
2040 | prev-line-end x) | |
2041 | (beginning-of-line) | |
2042 | ;; Note: setting result to t means we are done and will return nil. | |
2043 | ;;( This function never returns just t.) | |
2044 | (cond | |
2045 | ((equal (get-text-property (point) 'syntax-table) sh-here-doc-syntax) | |
2046 | (setq result t) | |
2047 | (setq have-result t)) | |
2048 | ((looking-at "\\s-*#") ; was (equal this-kw "#") | |
2049 | (if (bobp) | |
2050 | (setq result t);; return nil if 1st line! | |
2051 | (setq result (list '(= sh-indent-comment))) | |
2052 | ;; we still need to get previous line in case | |
2053 | ;; sh-indent-comnent is t (indent as normal) | |
2054 | (setq align-point (sh-prev-line nil)) | |
2055 | (setq have-result nil) | |
2056 | )) | |
2057 | );; cond | |
2058 | ||
2059 | (unless have-result | |
2060 | ;; Continuation lines are handled specially | |
2061 | (if (sh-this-is-a-continuation) | |
2062 | (progn | |
2063 | ;; We assume the line being continued is already | |
2064 | ;; properly indented... | |
2065 | ;; (setq prev-line-end (sh-prev-line)) | |
2066 | (setq align-point (sh-prev-line nil)) | |
2067 | (setq result (list '(+ sh-indent-for-continuation))) | |
2068 | (setq have-result t)) | |
2069 | (beginning-of-line) | |
2070 | (skip-chars-forward " \t") | |
2071 | (setq this-kw (sh-get-kw))) | |
2072 | ||
2073 | ;; Handle "this" keyword: first word on the line we're | |
2074 | ;; calculating indentation info for. | |
2075 | (if this-kw | |
2076 | (if (setq val (sh-check-rule 1 this-kw)) | |
2077 | (progn | |
2078 | (setq align-point (point)) | |
2079 | (sh-debug | |
2080 | "this - setting align-point to %d" align-point) | |
2081 | (setq result (append result val)) | |
2082 | (setq have-result t) | |
2083 | ;; set prev-line to continue processing remainder | |
2084 | ;; of this line as a previous l ine | |
2085 | (setq prev-line-end (point)) | |
2086 | )))) | |
2087 | ||
2088 | (unless have-result | |
2089 | (setq prev-line-end (sh-prev-line 'end))) | |
2090 | ||
2091 | (if prev-line-end | |
2092 | (save-excursion | |
2093 | ;; We start off at beginning of this line. | |
2094 | ;; Scan previous statements while this is <= | |
2095 | ;; start of previous line. | |
2096 | (setq start (point));; for debug only | |
2097 | (goto-char prev-line-end) | |
2098 | (setq x t) | |
2099 | (while (and x (setq x (sh-prev-thing))) | |
2100 | (sh-debug "at %d x is: %s result is: %s" (point) x result) | |
2101 | (cond | |
2102 | ((and (equal x ")") | |
2103 | (equal (get-text-property (1- (point)) 'syntax-table) | |
2104 | sh-special-syntax)) | |
2105 | (sh-debug "Case label) here") | |
2106 | (setq x 'case-label) | |
2107 | (if (setq val (sh-check-rule 2 x)) | |
2108 | (progn | |
2109 | (setq result (append result val)) | |
2110 | (setq align-point (point)))) | |
2111 | (forward-char -1) | |
2112 | (skip-chars-forward "[a-z0-9]*?") | |
2113 | ) | |
2114 | ((string-match "[])}]" x) | |
2115 | (setq x (sh-safe-backward-sexp)) | |
2116 | (if x | |
2117 | (progn | |
2118 | (setq align-point (point)) | |
2119 | (setq result (append result | |
2120 | (list "aligned to opening paren"))) | |
2121 | ))) | |
2122 | ((string-match "[[({]" x) | |
2123 | (sh-debug "Checking special thing: %s" x) | |
2124 | (if (setq val (sh-check-rule 2 x)) | |
2125 | (setq result (append result val))) | |
2126 | (forward-char -1) | |
2127 | (setq align-point (point))) | |
2128 | ((string-match "[\"'`]" x) | |
2129 | (sh-debug "Skipping back for %s" x) | |
2130 | ;; this was oops-2 | |
2131 | (setq x (sh-safe-backward-sexp))) | |
2132 | ((stringp x) | |
2133 | (sh-debug "Checking string %s at %s" x (point)) | |
2134 | (if (setq val (sh-check-rule 2 x)) | |
2135 | ;; (or (eq t (car val)) | |
2136 | ;; (eq t (car (car val)))) | |
2137 | (setq result (append result val))) | |
2138 | ;; not sure about this test Wed Jan 27 23:48:35 1999 | |
2139 | (setq align-point (point)) | |
2140 | (unless (bolp) | |
2141 | (forward-char -1))) | |
2142 | (t | |
2143 | (error "Don't know what to do with %s" x)) | |
2144 | ) | |
2145 | );; while | |
2146 | (sh-debug "result is %s" result) | |
2147 | ) | |
2148 | (sh-debug "No prev line!") | |
2149 | (sh-debug "result: %s align-point: %s" result align-point) | |
2150 | ) | |
2151 | ||
2152 | (if align-point | |
2153 | ;; was: (setq result (append result (list (list t align-point)))) | |
2154 | (setq result (append (list (list t align-point)) result)) | |
2155 | ) | |
2156 | (sh-debug "result is now: %s" result) | |
2157 | ||
2158 | (or result | |
2159 | (if prev-line-end | |
2160 | (setq result (list (list t prev-line-end))) | |
2161 | (setq result (list (list '= 'sh-first-lines-indent))) | |
2162 | )) | |
2163 | ||
2164 | (if (eq result t) | |
2165 | (setq result nil)) | |
2166 | (sh-debug "result is: %s" result) | |
2167 | result | |
2168 | );; let | |
2169 | )) | |
2170 | ||
2171 | ||
2172 | (defun sh-get-indent-var-for-line (&optional info) | |
2173 | "Return the variable controlling indentation for this line. | |
2174 | If there is not [just] one such variable, return a string | |
2175 | indicating the problem. | |
2176 | If INFO is supplied it is used, else it is calculated." | |
2177 | (let ((var nil) | |
2178 | (result nil) | |
2179 | (reason nil) | |
2180 | sym elt) | |
2181 | (or info | |
2182 | (setq info (sh-get-indent-info))) | |
2183 | (if (null info) | |
2184 | (setq result "this line to be left as is") | |
2185 | (while (and info (null result)) | |
2186 | (setq elt (car info)) | |
2187 | (cond | |
2188 | ((stringp elt) | |
2189 | (setq reason elt) | |
2190 | ) | |
2191 | ((not (listp elt)) | |
2192 | (error "sh-get-indent-var-for-line invalid elt: %s" elt)) | |
2193 | ;; so it is a list | |
2194 | ((eq t (car elt)) | |
2195 | );; nothing | |
2196 | ((symbolp (setq sym (nth 1 elt))) | |
2197 | ;; A bit of a kludge - when we see the sh-indent-comment | |
2198 | ;; ignore other variables. Otherwise it is tricky to | |
2199 | ;; "learn" the comment indentation. | |
2200 | (if (eq var 'sh-indent-comment) | |
2201 | (setq result var) | |
2202 | (if var | |
2203 | (setq result | |
2204 | "this line is controlled by more than 1 variable.") | |
2205 | (setq var sym)))) | |
2206 | (t | |
2207 | (error "sh-get-indent-var-for-line invalid list elt: %s" elt))) | |
2208 | (setq info (cdr info)) | |
2209 | )) | |
2210 | (or result | |
2211 | (setq result var)) | |
2212 | (or result | |
2213 | (setq result reason)) | |
2214 | (if (null result) | |
2215 | ;; e.g. just had (t POS) | |
2216 | (setq result "line has default indentation")) | |
2217 | result)) | |
2218 | ||
2219 | ||
2220 | ||
2221 | ;; Finding the previous line isn't trivial. | |
2222 | ;; We must *always* go back one more and see if that is a continuation | |
2223 | ;; line -- it is the PREVIOUS line which is continued, not the one | |
2224 | ;; we are going to! | |
2225 | ;; Also, we want to treat a whole "here document" as one big line, | |
2226 | ;; because we may want to a align to the beginning of it. | |
2227 | ;; | |
2228 | ;; What we do: | |
2229 | ;; - go back a line, if empty repeat | |
2230 | ;; - (we are now at a previous non empty line) | |
2231 | ;; - save this | |
2232 | ;; - if this is in a here-document, go to the beginning of it | |
2233 | ;; and save that | |
2234 | ;; - go back one more physcial line and see if it is a continuation line | |
2235 | ;; - if yes, save it and repeat | |
2236 | ;; - if no, go back to where we last saved. | |
2237 | (defun sh-prev-line (&optional end) | |
2238 | "Back to end of previous non-comment non-empty line. | |
2239 | Go to beginning of logical line unless END is non-nil, in which case | |
2240 | we go to the end of the previous line and do not check for continuations." | |
2241 | (sh-must-be-shell-mode) | |
2242 | (let ((going t) | |
2243 | (last-contin-line nil) | |
2244 | (result nil) | |
2245 | bol eol state) | |
2246 | (save-excursion | |
2247 | (beginning-of-line) | |
2248 | (while (and going | |
2249 | (not (bobp)) | |
2250 | (>= 0 (forward-line -1)) | |
2251 | ) | |
2252 | (setq bol (point)) | |
2253 | (end-of-line) | |
2254 | (setq eol (point)) | |
2255 | (save-restriction | |
2256 | (setq state (parse-partial-sexp bol eol nil nil nil t)) | |
2257 | (if (nth 4 state) | |
2258 | (setq eol (nth 8 state))) | |
2259 | (narrow-to-region bol eol) | |
2260 | (goto-char bol) | |
2261 | (cond | |
2262 | ((looking-at "\\s-*$")) | |
2263 | (t | |
2264 | (if end | |
2265 | (setq result eol) | |
2266 | (setq result bol)) | |
2267 | (setq going nil)) | |
2268 | ))) | |
2269 | (if (and result | |
2270 | (equal (get-text-property (1- result) 'syntax-table) | |
2271 | sh-here-doc-syntax)) | |
2272 | (let ((p1 (previous-single-property-change | |
2273 | (1- result) 'syntax-table))) | |
2274 | (if p1 | |
2275 | (progn | |
2276 | (goto-char p1) | |
2277 | (forward-line -1) | |
2278 | (if end | |
2279 | (end-of-line)) | |
2280 | (setq result (point))) | |
2281 | ))) | |
2282 | (unless end | |
2283 | ;; we must check previous lines to see if they are continuation lines | |
2284 | ;; if so, we must return position of first of them | |
2285 | (while (and (sh-this-is-a-continuation) | |
2286 | (>= 0 (forward-line -1))) | |
2287 | (setq result (point))) | |
2288 | (if result | |
2289 | (progn | |
2290 | (goto-char result) | |
2291 | (beginning-of-line) | |
2292 | (skip-chars-forward " \t") | |
2293 | (setq result (point)) | |
2294 | ))) | |
2295 | ) ;; save-excursion | |
2296 | result | |
2297 | )) | |
2298 | ||
2299 | ||
2300 | (defun sh-prev-stmt () | |
2301 | "Return the address of the previous stmt or nil." | |
2302 | ;; This is used when we are trying to find a matching keyword. | |
2303 | ;; Searching backward for the keyword would certainly be quicker, but | |
2304 | ;; it is hard to remove "false matches" -- such as if the keyword | |
2305 | ;; appears in a string or quote. This way is slower, but (I think) safer. | |
2306 | (interactive) | |
2307 | (save-excursion | |
2308 | (let ((going t) | |
2309 | (start (point)) | |
2310 | (found nil) | |
2311 | (prev nil)) | |
2312 | (skip-chars-backward " \t;|&({[") | |
2313 | (while (and (not found) | |
2314 | (not (bobp)) | |
2315 | going) | |
2316 | ;; Do a backward-sexp if possible, else backup bit by bit... | |
2317 | (if (sh-safe-backward-sexp) | |
2318 | (progn | |
2319 | (if (looking-at sh-special-keywords) | |
2320 | (progn | |
2321 | (setq found prev)) | |
2322 | (setq prev (point)) | |
2323 | )) | |
2324 | ;; backward-sexp failed | |
2325 | (if (zerop (skip-chars-backward " \t()[\]{};`'")) | |
2326 | (forward-char -1)) | |
2327 | (if (bolp) | |
2328 | (let ((back (sh-prev-line nil))) | |
2329 | (if back | |
2330 | (goto-char back) | |
2331 | (setq going nil))))) | |
2332 | (unless found | |
2333 | (skip-chars-backward " \t") | |
2334 | (if (or (and (bolp) (not (sh-this-is-a-continuation))) | |
2335 | (eq (char-before) ?\;) | |
2336 | (looking-at "\\s-*[|&]")) | |
2337 | (setq found (point))))) | |
2338 | (if found | |
2339 | (goto-char found)) | |
2340 | (if found | |
2341 | (progn | |
2342 | (skip-chars-forward " \t|&({[") | |
2343 | (setq found (point)))) | |
2344 | (if (>= (point) start) | |
2345 | (progn | |
2346 | (debug "We didn't move!") | |
2347 | (setq found nil)) | |
2348 | (or found | |
2349 | (sh-debug "Did not find prev stmt."))) | |
2350 | found | |
2351 | ))) | |
2352 | ||
2353 | ||
2354 | (defun sh-get-word () | |
2355 | "Get a shell word skipping whitespace from point." | |
2356 | (interactive) | |
2357 | (skip-chars-forward "\t ") | |
2358 | (let ((start (point))) | |
2359 | (while | |
2360 | (if (looking-at "[\"'`]") | |
2361 | (sh-safe-forward-sexp) | |
2362 | ;; (> (skip-chars-forward "^ \t\n\"'`") 0) | |
2363 | (> (skip-chars-forward "-_a-zA-Z\$0-9") 0) | |
2364 | )) | |
2365 | (buffer-substring start (point)) | |
2366 | )) | |
2367 | ||
2368 | (defun sh-prev-thing () | |
2369 | "Return the previous thing this logical line." | |
2370 | ;; This is called when `sh-get-indent-info' is working backwards on | |
2371 | ;; the previous line(s) finding what keywords may be relevant for | |
2372 | ;; indenting. It moves over sexps if possible, and will stop | |
2373 | ;; on a ; and at the beginning of a line if it is not a continuation | |
2374 | ;; line. | |
2375 | ;; | |
2376 | ;; Added a kludge for ";;" | |
2377 | ;; Possible return values: | |
2378 | ;; nil - nothing | |
2379 | ;; a string - possibly a keyword | |
2380 | ;; | |
2381 | (if (bolp) | |
2382 | nil | |
2383 | (let ((going t) | |
2384 | c n | |
2385 | min-point | |
2386 | (start (point)) | |
2387 | (found nil)) | |
2388 | (save-restriction | |
2389 | (narrow-to-region | |
2390 | (if (sh-this-is-a-continuation) | |
2391 | (setq min-point (sh-prev-line nil)) | |
2392 | (save-excursion | |
2393 | (beginning-of-line) | |
2394 | (setq min-point (point)))) | |
2395 | (point)) | |
2396 | (skip-chars-backward " \t;") | |
2397 | (unless (looking-at "\\s-*;;") | |
2398 | (skip-chars-backward "^)}];\"'`({[") | |
2399 | (setq c (char-before)))) | |
2400 | (sh-debug "stopping at %d c is %s start=%d min-point=%d" | |
2401 | (point) c start min-point) | |
2402 | (if (< (point) min-point) | |
2403 | (error "point %d < min-point %d" (point) min-point)) | |
2404 | (cond | |
2405 | ((looking-at "\\s-*;;") | |
2406 | ;; (message "Found ;; !") | |
2407 | ";;") | |
2408 | ((or (eq c ?\n) | |
2409 | (eq c nil) | |
2410 | (eq c ?\;)) | |
2411 | (save-excursion | |
2412 | ;; skip forward over white space newline and \ at eol | |
2413 | (skip-chars-forward " \t\n\\\\") | |
2414 | (sh-debug "Now at %d start=%d" (point) start) | |
2415 | (if (>= (point) start) | |
2416 | (progn | |
2417 | (sh-debug "point: %d >= start: %d" (point) start) | |
2418 | nil) | |
2419 | (sh-get-word)) | |
2420 | )) | |
2421 | (t | |
2422 | ;; c -- return a string | |
2423 | (char-to-string c) | |
2424 | )) | |
2425 | ))) | |
2426 | ||
2427 | ||
2428 | (defun sh-this-is-a-continuation () | |
2429 | "Return non-nil if current line is a continuation of previous line." | |
2430 | (let ((result nil) | |
2431 | bol eol state) | |
2432 | (save-excursion | |
2433 | (if (and (zerop (forward-line -1)) | |
2434 | (looking-at ".*\\\\$")) | |
2435 | (progn | |
2436 | (setq bol (point)) | |
2437 | (end-of-line) | |
2438 | (setq eol (point)) | |
2439 | (setq state (parse-partial-sexp bol eol nil nil nil t)) | |
2440 | (unless (nth 4 state) | |
2441 | (setq result t)) | |
2442 | ))))) | |
2443 | ||
2444 | (defun sh-get-kw (&optional where and-move) | |
2445 | "Return first word of line from WHERE. | |
2446 | If AND-MOVE is non-nil then move to end of word." | |
2447 | (let ((start (point))) | |
2448 | (if where | |
2449 | (goto-char where)) | |
2450 | (prog1 | |
2451 | (buffer-substring (point) | |
2452 | (progn (skip-chars-forward "^ \t\n;")(point))) | |
2453 | (unless and-move | |
2454 | (goto-char start))) | |
2455 | )) | |
2456 | ||
2457 | (defun sh-find-prev-matching (open close &optional depth) | |
2458 | "Find a matching token for a set of opening and closing keywords. | |
2459 | This takes into account that there may be nested open..close pairings. | |
2460 | OPEN and CLOSE are regexps denoting the tokens to be matched. | |
2461 | Optional parameter DEPTH (usually 1) says how many to look for." | |
2462 | (let ((parse-sexp-ignore-comments t) | |
2463 | prev) | |
2464 | (setq depth (or depth 1)) | |
2465 | (save-excursion | |
2466 | (condition-case nil | |
2467 | (while (and | |
2468 | (/= 0 depth) | |
2469 | (not (bobp)) | |
2470 | (setq prev (sh-prev-stmt))) | |
2471 | (goto-char prev) | |
2472 | (save-excursion | |
2473 | (if (looking-at "\\\\\n") | |
2474 | (progn | |
2475 | (forward-char 2) | |
2476 | (skip-chars-forward " \t"))) | |
2477 | (cond | |
2478 | ((looking-at open) | |
2479 | (setq depth (1- depth)) | |
2480 | (sh-debug "found open at %d - depth = %d" (point) depth)) | |
2481 | ((looking-at close) | |
2482 | (setq depth (1+ depth)) | |
2483 | (sh-debug "found close - depth = %d" depth)) | |
2484 | (t | |
2485 | )))) | |
2486 | (error nil)) | |
2487 | (if (eq depth 0) | |
2488 | prev ;; (point) | |
2489 | nil) | |
2490 | ))) | |
2491 | ||
2492 | ||
2493 | (defun sh-var-value (var &optional ignore-error) | |
2494 | "Return the value of variable VAR, interpreting symbols. | |
2495 | It can also return t or nil. | |
2496 | If an illegal value is found, throw an error unless Optional argument | |
2497 | IGNORE-ERROR is non-nil." | |
2498 | (let ((val (symbol-value var))) | |
2499 | (cond | |
2500 | ((numberp val) | |
2501 | val) | |
2502 | ((eq val t) | |
2503 | val) | |
2504 | ((null val) | |
2505 | val) | |
2506 | ((eq val '+) | |
2507 | sh-basic-offset) | |
2508 | ((eq val '-) | |
2509 | (- sh-basic-offset)) | |
2510 | ((eq val '++) | |
2511 | (* 2 sh-basic-offset)) | |
2512 | ((eq val '--) | |
2513 | (* 2 (- sh-basic-offset))) | |
2514 | ((eq val '*) | |
2515 | (/ sh-basic-offset 2)) | |
2516 | ((eq val '/) | |
2517 | (/ (- sh-basic-offset) 2)) | |
2518 | (t | |
2519 | (if ignore-error | |
2520 | (progn | |
2521 | (message "Don't konw how to handle %s's value of %s" var val) | |
2522 | 0) | |
2523 | (error "Don't know how to handle %s's value of %s" var val)) | |
2524 | )))) | |
2525 | ||
2526 | (defun sh-set-var-value (var value &optional no-symbol) | |
2527 | "Set variable VAR to VALUE. | |
2528 | Unless optional argument NO-SYMBOL is non-nil, then if VALUE is | |
2529 | can be represented by a symbol then do so." | |
2530 | (cond | |
2531 | (no-symbol | |
2532 | (set var value)) | |
2533 | ((= value sh-basic-offset) | |
2534 | (set var '+)) | |
2535 | ((= value (- sh-basic-offset)) | |
2536 | (set var '-)) | |
2537 | ((eq value (* 2 sh-basic-offset)) | |
2538 | (set var '++)) | |
2539 | ((eq value (* 2 (- sh-basic-offset))) | |
2540 | (set var '--)) | |
2541 | ((eq value (/ sh-basic-offset 2)) | |
2542 | (set var '*)) | |
2543 | ((eq value (/ (- sh-basic-offset) 2)) | |
2544 | (set var '/)) | |
2545 | (t | |
2546 | (set var value))) | |
2547 | ) | |
2548 | ||
2549 | ||
2550 | (defun sh-calculate-indent (&optional info) | |
2551 | "Return the indentation for the current line. | |
2552 | If INFO is supplied it is used, else it is calculated from current line." | |
2553 | (let ( | |
2554 | (ofs 0) | |
2555 | (base-value 0) | |
2556 | elt a b var val) | |
2557 | (or info | |
2558 | (setq info (sh-get-indent-info))) | |
2559 | (if (null info) | |
2560 | nil | |
2561 | (while info | |
2562 | (sh-debug "info: %s ofs=%s" info ofs) | |
2563 | (setq elt (car info)) | |
2564 | (cond | |
2565 | ((stringp elt) | |
2566 | ;; do nothing? | |
2567 | ) | |
2568 | ((listp elt) | |
2569 | (setq a (car (car info))) | |
2570 | (setq b (nth 1 (car info))) | |
2571 | (cond | |
2572 | ((eq a t) | |
2573 | (save-excursion | |
2574 | (goto-char b) | |
2575 | (setq val (current-indentation))) | |
2576 | (setq base-value val)) | |
2577 | ((symbolp b) | |
2578 | (setq val (sh-var-value b)) | |
2579 | (cond | |
2580 | ((eq a '=) | |
2581 | (cond | |
2582 | ((null val) | |
2583 | ;; no indentation | |
2584 | ;; set info to nil so we stop immediately | |
2585 | (setq base-value nil ofs nil info nil)) | |
2586 | ((eq val t) | |
2587 | ;; indent as normal line | |
2588 | (setq ofs 0)) | |
2589 | (t | |
2590 | ;; The following assume the (t POS) come first! | |
2591 | (setq ofs val base-value 0) | |
2592 | (setq info nil) ;; ? stop now | |
2593 | )) | |
2594 | ) | |
2595 | ((eq a '+) | |
2596 | (setq ofs (+ ofs val))) | |
2597 | ((eq a '-) | |
2598 | (setq ofs (- ofs val))) | |
2599 | (t | |
2600 | (error "sh-calculate-indent invalid a a=%s b=%s" a b)))) | |
2601 | (t | |
2602 | (error "sh-calculate-indent invalid elt: a=%s b=%s" a b))) | |
2603 | ) | |
2604 | (t | |
2605 | (error "sh-calculate-indent invalid elt %s" elt)) | |
2606 | ) | |
2607 | (sh-debug "a=%s b=%s val=%s base-value=%s ofs=%s" | |
2608 | a b val base-value ofs) | |
2609 | (setq info (cdr info)) | |
2610 | ) | |
2611 | ;; return value: | |
2612 | (sh-debug "at end: base-value: %s ofs: %s" base-value ofs) | |
2613 | ||
2614 | (cond | |
2615 | ((or (null base-value)(null ofs)) | |
2616 | nil) | |
2617 | ((and (numberp base-value)(numberp ofs)) | |
2618 | (sh-debug "base (%d) + ofs (%d) = %d" | |
2619 | base-value ofs (+ base-value ofs)) | |
2620 | (+ base-value ofs)) ;; return value | |
2621 | (t | |
2622 | (error "sh-calculate-indent: Help. base-value=%s ofs=%s" | |
2623 | base-value ofs) | |
2624 | nil)) | |
2625 | ))) | |
2626 | ||
2627 | ||
2628 | (defun sh-indent-line () | |
2629 | "Indent the current line." | |
2630 | (interactive) | |
2631 | (sh-must-be-shell-mode) | |
2632 | (let ((indent (sh-calculate-indent)) shift-amt beg end | |
2633 | (pos (- (point-max) (point)))) | |
2634 | (if indent | |
2635 | (progn | |
2636 | (beginning-of-line) | |
2637 | (setq beg (point)) | |
2638 | (skip-chars-forward " \t") | |
2639 | (setq shift-amt (- indent (current-column))) | |
2640 | (if (zerop shift-amt) | |
2641 | nil | |
2642 | (delete-region beg (point)) | |
2643 | (indent-to indent)) | |
2644 | ;; If initial point was within line's indentation, | |
2645 | ;; position after the indentation. Else stay at same point in text. | |
2646 | (if (> (- (point-max) pos) (point)) | |
2647 | (goto-char (- (point-max) pos))) | |
2648 | )))) | |
2649 | ||
2650 | ||
2651 | (defun sh-blink (blinkpos &optional msg) | |
2652 | "Move cursor momentarily to BLINKPOS and display MSG." | |
2653 | ;; We can get here without it being a number on first line | |
2654 | (if (numberp blinkpos) | |
2655 | (save-excursion | |
2656 | (goto-char blinkpos) | |
2657 | (message msg) | |
2658 | (sit-for blink-matching-delay)) | |
2659 | (message msg) | |
2660 | )) | |
2661 | ||
2662 | (defun sh-show-indent (arg) | |
2663 | "Show the how the currently line would be indented. | |
2664 | This tells you which variable, if any, controls the indentation of | |
2665 | this line. | |
2666 | If optional arg ARG is non-null (called interactively with a prefix), | |
2667 | a pop up window describes this variable. | |
2668 | If variable `sh-blink' is non-nil then momentarily go to the line | |
2669 | we are indenting relative to, if applicable." | |
2670 | (interactive "P") | |
2671 | (sh-must-support-indent) | |
2672 | (let* ((info (sh-get-indent-info)) | |
2673 | (var (sh-get-indent-var-for-line info)) | |
2674 | val msg | |
2675 | (curr-indent (current-indentation)) | |
2676 | ) | |
2677 | (if (stringp var) | |
2678 | (message (setq msg var)) | |
2679 | (setq val (sh-calculate-indent info)) | |
2680 | ||
2681 | (if (eq curr-indent val) | |
2682 | (setq msg (format "%s is %s" var (symbol-value var))) | |
2683 | (setq msg | |
2684 | (if val | |
2685 | (format "%s (%s) would change indent from %d to: %d" | |
2686 | var (symbol-value var) curr-indent val) | |
2687 | (format "%s (%s) would leave line as is" | |
2688 | var (symbol-value var))) | |
2689 | )) | |
2690 | (if (and arg var) | |
2691 | (describe-variable var))) | |
2692 | (if sh-blink | |
2693 | (let ((info (sh-get-indent-info))) | |
2694 | (if (and info (listp (car info)) | |
2695 | (eq (car (car info)) t)) | |
2696 | (sh-blink (nth 1 (car info)) msg) | |
2697 | (message msg))) | |
2698 | (message msg)) | |
2699 | )) | |
2700 | ||
2701 | (defun sh-set-indent () | |
2702 | "Set the indentation for the current line. | |
2703 | If the current line is controlled by an indentation variable, prompt | |
2704 | for a new value for it." | |
2705 | (interactive) | |
2706 | (sh-must-support-indent) | |
2707 | (let* ((info (sh-get-indent-info)) | |
2708 | (var (sh-get-indent-var-for-line info)) | |
2709 | val val0 new-val old-val indent-val) | |
2710 | (if (stringp var) | |
2711 | (message (format "Cannot set indent - %s" var)) | |
2712 | (setq old-val (symbol-value var)) | |
2713 | (setq val (sh-read-variable var)) | |
2714 | (condition-case nil | |
2715 | (progn | |
2716 | (set var val) | |
2717 | (setq indent-val (sh-calculate-indent info)) | |
2718 | (if indent-val | |
2719 | (message "Variable: %s Value: %s would indent to: %d" | |
2720 | var (symbol-value var) indent-val) | |
2721 | (message "Variable: %s Value: %s would leave line as is." | |
2722 | var (symbol-value var))) | |
2723 | ;; I'm not sure about this, indenting it now? | |
2724 | ;; No. Because it would give the impression that an undo would | |
2725 | ;; restore thing, but the value has been altered. | |
2726 | ;; (sh-indent-line) | |
2727 | ) | |
2728 | (error | |
2729 | (set var old-val) | |
2730 | (message "Bad value for %s, restoring to previous value %s" | |
2731 | var old-val) | |
2732 | (sit-for 1) | |
2733 | nil)) | |
2734 | ))) | |
2735 | ||
2736 | ||
2737 | (defun sh-learn-line-indent (arg) | |
2738 | "Learn how to indent a line as it currently is indented. | |
2739 | ||
2740 | If there is an indentation variable which controls this line's indentation, | |
2741 | then set it to a value which would indent the line the way it | |
2742 | presently is. | |
2743 | ||
2744 | If the value can be represented by one of the symbols then do so | |
2745 | unless optional argument ARG (the prefix when interactive) is non-nil." | |
2746 | (interactive "*P") | |
2747 | (sh-must-support-indent) | |
2748 | ;; I'm not sure if we show allow learning on an empty line. | |
2749 | ;; Though it might occasionally be useful I think it usually | |
2750 | ;; would just be confusing. | |
2751 | (if (save-excursion | |
2752 | (beginning-of-line) | |
2753 | (looking-at "\\s-*$")) | |
2754 | (message "sh-learn-line-indent ignores empty lines.") | |
2755 | (let* ((info (sh-get-indent-info)) | |
2756 | (var (sh-get-indent-var-for-line info)) | |
2757 | ival sval diff new-val | |
2758 | (no-symbol arg) | |
2759 | (curr-indent (current-indentation))) | |
2760 | (cond | |
2761 | ((stringp var) | |
2762 | (message (format "Cannot learn line - %s" var))) | |
2763 | ((eq var 'sh-indent-comment) | |
2764 | ;; This is arbitrary... | |
2765 | ;; - if curr-indent is 0, set to curr-indent | |
2766 | ;; - else if it has the indentation of a "normal" line, | |
2767 | ;; then set to t | |
2768 | ;; - else set to curr-indent. | |
2769 | (setq sh-indent-comment | |
2770 | (if (= curr-indent 0) | |
2771 | 0 | |
2772 | (let* ((sh-indent-comment t) | |
2773 | (val2 (sh-calculate-indent info))) | |
2774 | (if (= val2 curr-indent) | |
2775 | t | |
2776 | curr-indent)))) | |
2777 | (message "%s set to %s" var (symbol-value var)) | |
2778 | ) | |
2779 | ((numberp (setq sval (sh-var-value var))) | |
2780 | (setq ival (sh-calculate-indent info)) | |
2781 | (setq diff (- curr-indent ival)) | |
2782 | ||
2783 | (sh-debug "curr-indent: %d ival: %d diff: %d var:%s sval %s" | |
2784 | curr-indent ival diff var sval) | |
2785 | (setq new-val (+ sval diff)) | |
2786 | ;;; I commented out this because someone might want to replace | |
2787 | ;;; a value of `+' with the current value of sh-basic-offset | |
2788 | ;;; or vice-versa. | |
2789 | ;;; (if (= 0 diff) | |
2790 | ;;; (message "No change needed!") | |
2791 | (sh-set-var-value var new-val no-symbol) | |
2792 | (message "%s set to %s" var (symbol-value var)) | |
2793 | ) | |
2794 | (t | |
2795 | (debug) | |
2796 | (message "Cannot change %s" var)) | |
2797 | )))) | |
2798 | ||
2799 | ||
2800 | ||
2801 | (defun sh-mark-init (buffer) | |
2802 | "Initialize a BUFFER to be used by `sh-mark-line'." | |
2803 | (let ((main-buffer (current-buffer))) | |
2804 | (save-excursion | |
2805 | (set-buffer (get-buffer-create buffer)) | |
2806 | (erase-buffer) | |
2807 | (occur-mode) | |
2808 | (setq occur-buffer main-buffer) | |
2809 | ))) | |
2810 | ||
2811 | ||
2812 | (defun sh-mark-line (message point buffer &optional add-linenum occur-point) | |
2813 | "Insert MESSAGE referring to location POINT in current buffer into BUFFER. | |
2814 | Buffer BUFFER is in `occur-mode'. | |
2815 | If ADD-LINENUM is non-nil the message is preceded by the line number. | |
2816 | If OCCUR-POINT is non-nil then the line is marked as a new occurence | |
2817 | so that `occur-next' and `occur-prev' will work." | |
2818 | (let ((m1 (make-marker)) | |
2819 | (main-buffer (current-buffer)) | |
2820 | start | |
2821 | (line "") ) | |
2822 | (if point | |
2823 | (progn | |
2824 | (set-marker m1 point (current-buffer)) | |
2825 | (if add-linenum | |
2826 | (setq line (format "%d: " (1+ (count-lines 1 point))))))) | |
2827 | (save-excursion | |
2828 | (if (get-buffer buffer) | |
2829 | (set-buffer (get-buffer buffer)) | |
2830 | (set-buffer (get-buffer-create buffer)) | |
2831 | (occur-mode) | |
2832 | (setq occur-buffer main-buffer) | |
2833 | ) | |
2834 | (goto-char (point-max)) | |
2835 | (setq start (point)) | |
2836 | (insert line) | |
2837 | (if occur-point | |
2838 | (setq occur-point (point))) | |
2839 | (insert message) | |
2840 | (if point | |
2841 | (put-text-property start (point) 'mouse-face 'highlight)) | |
2842 | (insert "\n") | |
2843 | (if point | |
2844 | (progn | |
2845 | (put-text-property start (point) 'occur m1) | |
2846 | (if occur-point | |
2847 | (put-text-property occur-point (1+ occur-point) | |
2848 | 'occur-point t)) | |
2849 | )) | |
2850 | ))) | |
2851 | ||
2852 | ||
2853 | ||
2854 | ;; Is this really worth having? | |
2855 | (defvar sh-learned-buffer-hook nil | |
2856 | "*An abnormal hook, called with an alist of leared variables.") | |
2857 | ;;; Example of how to use sh-learned-buffer-hook | |
2858 | ;; | |
2859 | ;; (defun what-i-learned (list) | |
2860 | ;; (let ((p list)) | |
2861 | ;; (save-excursion | |
2862 | ;; (set-buffer "*scratch*") | |
2863 | ;; (goto-char (point-max)) | |
2864 | ;; (insert "(setq\n") | |
2865 | ;; (while p | |
2866 | ;; (insert (format " %s %s \n" | |
2867 | ;; (nth 0 (car p)) (nth 1 (car p)))) | |
2868 | ;; (setq p (cdr p))) | |
2869 | ;; (insert ")\n") | |
2870 | ;; ))) | |
2871 | ;; | |
2872 | ;; (add-hook 'sh-learned-buffer-hook 'what-i-learned) | |
2873 | ||
2874 | ||
2875 | ;; Originally this was sh-learn-region-indent (beg end) | |
2876 | ;; However, in practise this was awkward so I changed it to | |
2877 | ;; use the whole buffer. Use narrowing if needbe. | |
2878 | (defun sh-learn-buffer-indent (&optional arg) | |
2879 | "Learn how to indent the buffer the way it currently is. | |
2880 | ||
2881 | Output in buffer \"*indent*\" shows any lines which have conflicting | |
2882 | values of a variable, and the final value of all variables learnt. | |
2883 | This buffer is popped to automatically if there are any discrepencies. | |
2884 | ||
2885 | If no prefix ARG is given, then variables are set to numbers. | |
2886 | If a prefix arg is given, then variables are set to symbols when | |
2887 | applicable -- e.g. to symbol `+' if the value is that of the | |
2888 | basic indent. | |
2889 | If a positive numerical prefix is given, then `sh-basic-offset' | |
2890 | is set to the prefix's numerical value. | |
2891 | Otherwise, sh-basic-offset may or may not be changed, according | |
2892 | to the value of variable `sh-learn-basic-offset'. | |
2893 | ||
2894 | Abnormal hook `sh-learned-buffer-hook' if non-nil is called when the | |
2895 | function completes. The function is abnormal because it is called | |
2896 | with an alist of variables learnt. This feature may be changed or | |
2897 | removed in the future. | |
2898 | ||
2899 | This command can often take a long time to run." | |
2900 | (interactive "P") | |
2901 | (sh-must-support-indent) | |
2902 | (save-excursion | |
2903 | (goto-char (point-min)) | |
2904 | (let ((learned-var-list nil) | |
2905 | (out-buffer "*indent*") | |
2906 | (num-diffs 0) | |
2907 | last-pos | |
2908 | previous-set-info | |
2909 | (max 17) | |
2910 | vec | |
2911 | msg | |
2912 | (comment-col nil) ;; number if all same, t if seen diff values | |
2913 | (comments-always-default t) ;; nil if we see one not default | |
2914 | initial-msg | |
2915 | (specified-basic-offset (and arg (numberp arg) | |
2916 | (> arg 0))) | |
2917 | (linenum 0) | |
2918 | suggested) | |
2919 | (setq vec (make-vector max 0)) | |
2920 | (sh-mark-init out-buffer) | |
2921 | ||
2922 | (if specified-basic-offset | |
2923 | (progn | |
2924 | (setq sh-basic-offset arg) | |
2925 | (setq initial-msg | |
2926 | (format "Using specified sh-basic-offset of %d" | |
2927 | sh-basic-offset))) | |
2928 | (setq initial-msg | |
2929 | (format "Initial value of sh-basic-offset: %s" | |
2930 | sh-basic-offset))) | |
2931 | ||
2932 | (while (< (point) (point-max)) | |
2933 | (setq linenum (1+ linenum)) | |
2934 | ;; (if (zerop (% linenum 10)) | |
2935 | (message "line %d" linenum) | |
2936 | ;; ) | |
2937 | (unless (looking-at "\\s-*$") ;; ignore empty lines! | |
2938 | (let* ((sh-indent-comment t) ;; info must return default indent | |
2939 | (info (sh-get-indent-info)) | |
2940 | (var (sh-get-indent-var-for-line info)) | |
2941 | sval ival diff new-val | |
2942 | (curr-indent (current-indentation))) | |
2943 | (cond | |
2944 | ((null var) | |
2945 | nil) | |
2946 | ((stringp var) | |
2947 | nil) | |
2948 | ((numberp (setq sval (sh-var-value var 'no-error))) | |
2949 | ;; the numberp excludes comments since sval will be t. | |
2950 | (setq ival (sh-calculate-indent)) | |
2951 | (setq diff (- curr-indent ival)) | |
2952 | (setq new-val (+ sval diff)) | |
2953 | (sh-set-var-value var new-val 'no-symbol) | |
2954 | (unless (looking-at "\\s-*#");; don't learn from comments | |
2955 | (if (setq previous-set-info (assoc var learned-var-list)) | |
2956 | (progn | |
2957 | ;; it was already there, is it same value ? | |
2958 | (unless (eq (symbol-value var) | |
2959 | (nth 1 previous-set-info)) | |
2960 | (sh-mark-line | |
2961 | (format "Variable %s was set to %s" | |
2962 | var (symbol-value var)) | |
2963 | (point) out-buffer t t) | |
2964 | (sh-mark-line | |
2965 | (format " but was previously set to %s" | |
2966 | (nth 1 previous-set-info)) | |
2967 | (nth 2 previous-set-info) out-buffer t) | |
2968 | (setq num-diffs (1+ num-diffs)) | |
2969 | ;; (delete previous-set-info learned-var-list) | |
2970 | (setcdr previous-set-info | |
2971 | (list (symbol-value var) (point))) | |
2972 | ) | |
2973 | ) | |
2974 | (setq learned-var-list | |
2975 | (append (list (list var (symbol-value var) | |
2976 | (point))) | |
2977 | learned-var-list))) | |
2978 | (if (numberp new-val) | |
2979 | (progn | |
2980 | (sh-debug | |
2981 | "This line's indent value: %d" new-val) | |
2982 | (if (< new-val 0) | |
2983 | (setq new-val (- new-val))) | |
2984 | (if (< new-val max) | |
2985 | (aset vec new-val (1+ (aref vec new-val)))))) | |
2986 | )) | |
2987 | ((eq var 'sh-indent-comment) | |
2988 | (unless (= curr-indent (sh-calculate-indent info)) | |
2989 | ;; this is not the default indentation | |
2990 | (setq comments-always-default nil) | |
2991 | (if comment-col;; then we have see one before | |
2992 | (or (eq comment-col curr-indent) | |
2993 | (setq comment-col t));; seen a different one | |
2994 | (setq comment-col curr-indent)) | |
2995 | )) | |
2996 | (t | |
2997 | (sh-debug "Cannot learn this line!!!") | |
2998 | )) | |
2999 | (sh-debug | |
3000 | "at %s learned-var-list is %s" (point) learned-var-list) | |
3001 | )) | |
3002 | (forward-line 1) | |
3003 | ) ;; while | |
3004 | (if sh-debug | |
3005 | (progn | |
3006 | (setq msg (format | |
3007 | "comment-col = %s comments-always-default = %s" | |
3008 | comment-col comments-always-default)) | |
3009 | ;; (message msg) | |
3010 | (sh-mark-line msg nil out-buffer))) | |
3011 | (cond | |
3012 | ((eq comment-col 0) | |
3013 | (setq msg "\nComments are all in 1st column.\n")) | |
3014 | (comments-always-default | |
3015 | (setq msg "\nComments follow default indentation.\n") | |
3016 | (setq comment-col t)) | |
3017 | ((numberp comment-col) | |
3018 | (setq msg (format "\nComments are in col %d." comment-col))) | |
3019 | (t | |
3020 | (setq msg "\nComments seem to be mixed, leaving them as is.\n") | |
3021 | (setq comment-col nil) | |
3022 | )) | |
3023 | (sh-debug msg) | |
3024 | (sh-mark-line msg nil out-buffer) | |
3025 | ||
3026 | (sh-mark-line initial-msg nil out-buffer t t) | |
3027 | ||
3028 | (setq suggested (sh-guess-basic-offset vec)) | |
3029 | ||
3030 | (if (and suggested (not specified-basic-offset)) | |
3031 | (let ((new-value | |
3032 | (cond | |
3033 | ;; t => set it if we have a single value as a number | |
3034 | ((and (eq sh-learn-basic-offset t) (numberp suggested)) | |
3035 | suggested) | |
3036 | ;; other non-nil => set it if only one value was found | |
3037 | (sh-learn-basic-offset | |
3038 | (if (numberp suggested) | |
3039 | suggested | |
3040 | (if (= (length suggested) 1) | |
3041 | (car suggested)))) | |
3042 | (t | |
3043 | nil)))) | |
3044 | (if new-value | |
3045 | (progn | |
3046 | (setq learned-var-list | |
3047 | (append (list (list 'sh-basic-offset | |
3048 | (setq sh-basic-offset new-value) | |
3049 | (point-max))) | |
3050 | learned-var-list)) | |
3051 | ;; Not sure if we need to put this line in, since | |
3052 | ;; it will appear in the "Learned variable settings". | |
3053 | (sh-mark-line | |
3054 | (format "Changed sh-basic-offset to: %d" sh-basic-offset) | |
3055 | nil out-buffer)) | |
3056 | (sh-mark-line | |
3057 | (if (listp suggested) | |
3058 | (format "Possible value(s) for sh-basic-offset: %s" | |
3059 | (mapconcat 'int-to-string suggested " ")) | |
3060 | (format "Suggested sh-basic-offset: %d" suggested)) | |
3061 | nil out-buffer)))) | |
3062 | ||
3063 | ||
3064 | (setq learned-var-list | |
3065 | (append (list (list 'sh-indent-comment comment-col (point-max))) | |
3066 | learned-var-list)) | |
3067 | (setq sh-indent-comment comment-col) | |
3068 | (let ((name (buffer-name)) | |
3069 | (lines (if (and (eq (point-min) 1) | |
3070 | (eq (point-max) (1+ (buffer-size)))) | |
3071 | "" | |
3072 | (format "lines %d to %d of " | |
3073 | (1+ (count-lines 1 (point-min))) | |
3074 | (1+ (count-lines 1 (point-max)))))) | |
3075 | ) | |
3076 | (sh-mark-line "\nLearned variable settings:" nil out-buffer) | |
3077 | (if arg | |
3078 | ;; Set learned variables to symbolic rather than numeric | |
3079 | ;; values where possible. | |
3080 | (progn | |
3081 | (let ((p (reverse learned-var-list)) | |
3082 | var val) | |
3083 | (while p | |
3084 | (setq var (car (car p))) | |
3085 | (setq val (nth 1 (car p))) | |
3086 | (cond | |
3087 | ((eq var 'sh-basic-offset) | |
3088 | ) | |
3089 | ((numberp val) | |
3090 | (sh-set-var-value var val)) | |
3091 | (t | |
3092 | )) | |
3093 | (setq p (cdr p)) | |
3094 | )))) | |
3095 | (let ((p (reverse learned-var-list)) | |
3096 | var) | |
3097 | (while p | |
3098 | (setq var (car (car p))) | |
3099 | (sh-mark-line (format " %s %s" var (symbol-value var)) | |
3100 | (nth 2 (car p)) out-buffer) | |
3101 | (setq p (cdr p)))) | |
3102 | (save-excursion | |
3103 | (set-buffer out-buffer) | |
3104 | (goto-char (point-min)) | |
3105 | (insert | |
3106 | (format "Indentation values for buffer %s.\n" name) | |
3107 | (format "%d indentation variable%s different values%s\n\n" | |
3108 | num-diffs | |
3109 | (if (= num-diffs 1) | |
3110 | " has" "s have") | |
3111 | (if (zerop num-diffs) | |
3112 | "." ":")) | |
3113 | ))) | |
3114 | ;; Are abnormal hooks considered bad form? | |
3115 | (run-hook-with-args 'sh-learned-buffer-hook learned-var-list) | |
3116 | (if (or sh-popup-occur-buffer (> num-diffs 0)) | |
3117 | (pop-to-buffer out-buffer)) | |
3118 | ))) | |
3119 | ||
3120 | (defun sh-guess-basic-offset (vec) | |
3121 | "See if we can determine a reasonbable value for `sh-basic-offset'. | |
3122 | This is experimental, heuristic and arbitrary! | |
3123 | Argument VEC is a vector of information collected by | |
3124 | `sh-learn-buffer-indent'. | |
3125 | Return values: | |
3126 | number - there appears to be a good single value | |
3127 | list of numbers - no obvious one, here is a list of one or more | |
3128 | reasonable choices | |
3129 | nil - we couldn't find a reasonable one." | |
3130 | (let* ((max (1- (length vec))) | |
3131 | (i 1) | |
3132 | (totals (make-vector max 0)) | |
3133 | (return nil) | |
3134 | j) | |
3135 | (while (< i max) | |
3136 | (aset totals i (+ (aref totals i) (* 4 (aref vec i)))) | |
3137 | (setq j (/ i 2)) | |
3138 | (if (zerop (% i 2)) | |
3139 | (aset totals i (+ (aref totals i) (aref vec (/ i 2))))) | |
3140 | (if (< (* i 2) max) | |
3141 | (aset totals i (+ (aref totals i) (aref vec (* i 2))))) | |
3142 | (setq i (1+ i)) | |
3143 | ) | |
3144 | (let ((x nil) | |
3145 | (result nil) | |
3146 | tot sum p) | |
3147 | (setq i 1) | |
3148 | (while (< i max) | |
3149 | (if (/= (aref totals i) 0) | |
3150 | (setq x (append x (list (cons i (aref totals i)))))) | |
3151 | (setq i (1+ i))) | |
3152 | ||
3153 | (setq x (sort x '(lambda (a b) | |
3154 | (> (cdr a)(cdr b))))) | |
3155 | (setq tot (apply '+ (append totals nil))) | |
3156 | (sh-debug (format "vec: %s\ntotals: %s\ntot: %d" | |
3157 | vec totals tot)) | |
3158 | (cond | |
3159 | ((zerop (length x)) | |
3160 | (message "no values!")) ;; we return nil | |
3161 | ((= (length x) 1) | |
3162 | (message "only value is %d" (car (car x))) | |
3163 | (setq result (car (car x)))) ;; return single value | |
3164 | ((> (cdr (car x)) (/ tot 2)) | |
3165 | ;; 1st is > 50% | |
3166 | (message "basic-offset is probably %d" (car (car x))) | |
3167 | (setq result (car (car x)))) ;; again, return a single value | |
3168 | ((>= (cdr (car x)) (* 2 (cdr (car (cdr x))))) | |
3169 | ;; 1st is >= 2 * 2nd | |
3170 | (message "basic-offset could be %d" (car (car x))) | |
3171 | (setq result (car (car x)))) | |
3172 | ((>= (+ (cdr (car x))(cdr (car (cdr x)))) (/ tot 2)) | |
3173 | ;; 1st & 2nd together >= 50% - return a list | |
3174 | (setq p x sum 0 result nil) | |
3175 | (while (and p | |
3176 | (<= (setq sum (+ sum (cdr (car p)))) (/ tot 2))) | |
3177 | (setq result (append result (list (car (car p))))) | |
3178 | (setq p (cdr p))) | |
3179 | (message "Possible choices for sh-basic-offset: %s" | |
3180 | (mapconcat 'int-to-string result " "))) | |
3181 | (t | |
3182 | (message "No obvious value for sh-basic-offset. Perhaps %d" | |
3183 | (car (car x))) | |
3184 | ;; result is nil here | |
3185 | )) | |
3186 | result | |
3187 | ))) | |
3188 | ||
3189 | ||
3190 | (defun sh-do-nothing (a b c) | |
3191 | ;; checkdoc-params: (a b c) | |
3192 | "A dummy function to prevent font-lock from re-fontifying a change. | |
3193 | Otherwise, we fontify something and font-lock overwrites it." | |
3194 | ) | |
3195 | ||
b0622822 GM |
3196 | ;; The default font-lock-unfontify-region-function removes |
3197 | ;; syntax-table properties, and so removes our information. | |
3198 | (defun sh-font-lock-unfontify-region-function (beg end) | |
3199 | (let* ((modified (buffer-modified-p)) (buffer-undo-list t) | |
3200 | (inhibit-read-only t) (inhibit-point-motion-hooks t) | |
3201 | before-change-functions after-change-functions | |
3202 | deactivate-mark buffer-file-name buffer-file-truename) | |
3203 | (remove-text-properties beg end '(face nil)) | |
3204 | (when (and (not modified) (buffer-modified-p)) | |
3205 | (set-buffer-modified-p nil)))) | |
3206 | ||
f964dfcb GM |
3207 | (defun sh-set-char-syntax (where new-prop) |
3208 | "Set the character's syntax table property at WHERE to be NEW-PROP." | |
3209 | (or where | |
3210 | (setq where (point))) | |
3211 | (let ((font-lock-fontify-region-function 'sh-do-nothing)) | |
3212 | (put-text-property where (1+ where) 'syntax-table new-prop) | |
3213 | (add-text-properties where (1+ where) | |
3214 | '(face sh-st-face rear-nonsticky t)) | |
3215 | )) | |
3216 | ||
3217 | ||
3218 | (defun sh-check-paren-in-case () | |
3219 | "Make syntax class of case label's right parenthesis not close parenthesis. | |
3220 | If this parenthesis is a case alternative, set its syntax class to a word." | |
3221 | (let ((start (point)) | |
3222 | state prev-line) | |
3223 | ;; First test if this is a possible candidate, the first "(" or ")" | |
3224 | ;; on the line; then, if go, check prev line is ;; or case. | |
3225 | (save-excursion | |
3226 | (beginning-of-line) | |
3227 | ;; stop at comment or when depth becomes -1 | |
3228 | (setq state (parse-partial-sexp (point) start -1 nil nil t)) | |
3229 | (if (and | |
3230 | (= (car state) -1) | |
3231 | (= (point) start) | |
3232 | (setq prev-line (sh-prev-line nil))) | |
3233 | (progn | |
3234 | (goto-char prev-line) | |
3235 | (beginning-of-line) | |
3236 | ;; (setq case-stmt-start (point)) | |
3237 | ;; (if (looking-at "\\(^\\s-*case[^-a-z0-9_]\\|[^#]*;;\\s-*$\\)") | |
3238 | (if (sh-search-word "\\(case\\|;;\\)" start) | |
3239 | (sh-set-char-syntax (1- start) sh-special-syntax) | |
3240 | )))))) | |
3241 | ||
3242 | (defun sh-electric-rparen () | |
3243 | "Insert a right parethese, and check if it is a case alternative. | |
3244 | If so, its syntax class is set to word, and its text proerty | |
3245 | is set to have face `sh-st-face'." | |
3246 | (interactive) | |
3247 | (insert ")") | |
3248 | (if sh-electric-rparen-needed-here | |
3249 | (sh-check-paren-in-case))) | |
3250 | ||
3251 | (defun sh-electric-hash () | |
3252 | "Insert a hash, but check it is preceded by \"$\". | |
3253 | If so, it is given a syntax type of comment. | |
3254 | Its text proerty has face `sh-st-face'." | |
3255 | (interactive) | |
3256 | (let ((pos (point))) | |
3257 | (insert "#") | |
3258 | (if (eq (char-before pos) ?$) | |
3259 | (sh-set-char-syntax pos sh-st-punc)))) | |
3260 | ||
3261 | (defun sh-electric-less (arg) | |
3262 | "Insert a \"<\" and see if this is the start of a here-document. | |
3263 | If so, the syntax class is set so that it will not be automatically | |
3264 | reindented. | |
3265 | Argument ARG if non-nil disables this test." | |
3266 | (interactive "*P") | |
3267 | (let ((p1 (point)) p2 p3) | |
3268 | (sh-maybe-here-document arg) ;; call the original fn in sh-script.el. | |
3269 | (setq p2 (point)) | |
3270 | (if (/= (+ p1 (prefix-numeric-value arg)) p2) | |
3271 | (save-excursion | |
3272 | (forward-line 1) | |
3273 | (end-of-line) | |
3274 | (setq p3 (point)) | |
3275 | (sh-set-here-doc-region p2 p3)) | |
3276 | ))) | |
3277 | ||
3278 | (defun sh-set-here-doc-region (start end) | |
3279 | "Mark a here-document from START to END so that it will not be reindented." | |
3280 | (interactive "r") | |
3281 | ;; Make the whole thing have syntax type word... | |
3282 | ;; That way sexp movement doens't worry about any parentheses. | |
3283 | ;; A disadvantage of this is we can't use forward-word within a | |
3284 | ;; here-doc, which is annoying. | |
3285 | (let ((font-lock-fontify-region-function 'sh-do-nothing)) | |
3286 | (put-text-property start end 'syntax-table sh-here-doc-syntax) | |
3287 | (put-text-property start end 'face 'sh-heredoc-face) | |
3288 | (put-text-property (1- end) end 'rear-nonsticky t) | |
3289 | (put-text-property start (1+ start) 'front-sticky t) | |
3290 | )) | |
3291 | ||
3292 | (defun sh-remove-our-text-properties () | |
3293 | "Remove text properties relating to right parentheses and here documents." | |
3294 | (interactive) | |
3295 | (save-excursion | |
3296 | (goto-char (point-min)) | |
3297 | (while (not (eobp)) | |
3298 | (let ((plist (text-properties-at (point))) | |
3299 | (next-change | |
3300 | (or (next-single-property-change (point) 'syntax-table | |
3301 | (current-buffer) ) | |
3302 | (point-max)))) | |
3303 | ;; Process text from point to NEXT-CHANGE... | |
3304 | (if (get-text-property (point) 'syntax-table) | |
3305 | (progn | |
3306 | (sh-debug "-- removing props from %d to %d --" | |
3307 | (point) next-change) | |
3308 | (remove-text-properties (point) next-change | |
3309 | '(syntax-table nil)) | |
3310 | (remove-text-properties (point) next-change '(face nil)) | |
3311 | )) | |
3312 | (goto-char next-change))) | |
3313 | )) | |
3314 | ||
3315 | (defun sh-search-word (word &optional limit) | |
3316 | "Search forward for regexp WORD occuring as a word not in string nor comment. | |
3317 | If found, returns non nil with the match available in \(match-string 2\). | |
3318 | Yes 2, not 1, since we build a regexp to guard against false matches | |
3319 | such as matching \"a-case\" when we are searching for \"case\". | |
3320 | If not found, it returns nil. | |
3321 | The search maybe limited by optional argument LIMIT." | |
3322 | (interactive "sSearch for: ") | |
3323 | (let ((found nil) | |
3324 | ;; Cannot use \\b here since it matches "-" and "_" | |
3325 | (regexp (sh-mkword-regexp word)) | |
3326 | start state where) | |
3327 | (setq start (point)) | |
3328 | (while (and (setq start (point)) | |
3329 | (not found) | |
3330 | (re-search-forward regexp limit t)) | |
3331 | ;; Found str; check it is not in a comment or string. | |
3332 | (setq state | |
3333 | ;; Stop on comment: | |
3334 | (parse-partial-sexp start (point) nil nil nil 'syntax_table)) | |
3335 | (if (setq where (nth 8 state)) | |
3336 | ;; in comment or string | |
3337 | (if (= where -1) | |
3338 | (setq found (point)) | |
3339 | (if (eq (char-after where) ?#) | |
3340 | (end-of-line) | |
3341 | (goto-char where) | |
3342 | (unless (sh-safe-forward-sexp) | |
3343 | ;; If the above fails we must either give up or | |
3344 | ;; move forward and try again. | |
3345 | (forward-line 1)) | |
3346 | )) | |
3347 | ;; not in comment or string, so accept it | |
3348 | (setq found (point)) | |
3349 | )) | |
3350 | found | |
3351 | )) | |
3352 | ||
3353 | (defun sh-scan-case () | |
3354 | "Scan a case statement for right parens belonging to case alternatives. | |
3355 | Mark each as having syntax `sh-special-syntax'. | |
3356 | Called from scan-buff. If ok, return non-nil." | |
3357 | (let (end | |
3358 | state | |
3359 | (depth 1) ;; we are called at a "case" | |
3360 | (start (point)) | |
3361 | (return t)) | |
3362 | ;; We enter here at a case statement | |
3363 | ;; First, find limits of the case. | |
3364 | (while (and (> depth 0) | |
3365 | (sh-search-word "\\(case\\|esac\\)")) | |
3366 | (if (equal (match-string 2) "case") | |
3367 | (setq depth (1+ depth)) | |
3368 | (setq depth (1- depth)))) | |
3369 | ;; (message "end of search for esac at %d depth=%d" (point) depth) | |
3370 | (setq end (point)) | |
3371 | (goto-char start) | |
3372 | ;; if we found the esac, then fix all appropriate ')'s in the region | |
3373 | (if (zerop depth) | |
3374 | (progn | |
3375 | (while (< (point) end) | |
3376 | ;; search for targetdepth of -1 meaning extra right paren | |
3377 | (setq state (parse-partial-sexp (point) end -1 nil nil nil)) | |
3378 | (if (and (= (car state) -1) | |
3379 | (= (char-before) ?\))) | |
3380 | (progn | |
3381 | ;; (message "At %d state is %s" (point) state) | |
3382 | ;; (message "Fixing %d" (point)) | |
3383 | (sh-set-char-syntax (1- (point)) sh-special-syntax) | |
3384 | ;; we could advance to the next ";;" perhaps | |
3385 | ) | |
3386 | ;; (message "? Not found at %d" (point)) ; ok, could be "]" | |
3387 | )) | |
3388 | (goto-char end)) | |
3389 | (message "No matching esac for case at %d" start) | |
3390 | (setq return nil) | |
3391 | ) | |
3392 | return | |
3393 | )) | |
3394 | ||
3395 | ||
3396 | (defun sh-scan-buffer () | |
3397 | "Scan a sh buffer for case statements and here-documents. | |
3398 | ||
3399 | For each case alternative found, mark its \")\" with a text property | |
3400 | so that its syntax class is no longer a close parenthesis character. | |
3401 | ||
3402 | Each here-document is also marked so that it is effectively immune | |
3403 | from indenation changes." | |
3404 | ;; Do not call this interactively, call `sh-rescan-buffer' instead. | |
3405 | (sh-must-be-shell-mode) | |
3406 | (let ((n 0) | |
3407 | (initial-buffer-modified-p (buffer-modified-p)) | |
3408 | start end where label ws) | |
3409 | (save-excursion | |
3410 | (goto-char (point-min)) | |
3411 | ;; 1. Scan for ")" in case statements. | |
3412 | (while (and ;; (re-search-forward "^[^#]*\\bcase\\b" nil t) | |
3413 | (sh-search-word "\\(case\\|esac\\)") | |
3414 | ;; (progn (message "Found a case at %d" (point)) t) | |
3415 | (sh-scan-case))) | |
3416 | ;; 2. Scan for here docs | |
3417 | (goto-char (point-min)) | |
3418 | ;; while (re-search-forward "<<\\(-?\\)\\(\\s-*\\)\\(.*\\)$" nil t) | |
3419 | (while (re-search-forward "<<\\(-?\\)" nil t) | |
3420 | (unless (sh-in-comment-or-string (match-beginning 0)) | |
3421 | ;; (setq label (match-string 3)) | |
3422 | (setq label (sh-get-word)) | |
3423 | (if (string= (match-string 1) "-") | |
3424 | ;; if <<- then we allow whitespace | |
3425 | (setq ws "\\s-*") | |
3426 | ;; otherwise we don't | |
3427 | (setq ws "")) | |
3428 | (while (string-match "['\"\\]" label) | |
3429 | (setq label (replace-match "" nil nil label))) | |
3430 | (if (setq n (string-match "\\s-+$" label)) | |
3431 | (setq label (substring label 0 n))) | |
3432 | (forward-line 1) | |
3433 | ;; the line containing the << could be continued... | |
3434 | (while (sh-this-is-a-continuation) | |
3435 | (forward-line 1)) | |
3436 | (setq start (point)) | |
3437 | (if (re-search-forward (concat "^" ws (regexp-quote label) | |
3438 | "\\s-*$") | |
3439 | nil t) | |
3440 | (sh-set-here-doc-region start (point)) | |
3441 | (sh-debug "missing here-doc delimiter `%s'" label)))) | |
3442 | ;; 3. Scan for $# -- make the "#" a punctuation not a comment | |
3443 | (goto-char (point-min)) | |
3444 | (let (state) | |
3445 | (while (and (not (eobp)) | |
3446 | (setq state (parse-partial-sexp | |
3447 | (1+ (point))(point-max) nil nil nil t)) | |
3448 | (nth 4 state)) | |
3449 | (goto-char (nth 8 state)) | |
3450 | (sh-debug "At %d %s" (point) (eq (char-before) ?$)) | |
3451 | (if (eq (char-before) ?$) | |
3452 | (sh-set-char-syntax (point) sh-st-punc) ;; not a comment! | |
3453 | (end-of-line) ;; if this *was* a comment, ignore rest of line! | |
3454 | ))) | |
3455 | ;; 4. Hide these changes from making a previously unmodified | |
3456 | ;; buffer into a modified buffer. | |
3457 | (if sh-debug | |
3458 | (if initial-buffer-modified-p | |
3459 | (message "buffer was initially modified") | |
3460 | (message | |
3461 | "buffer not initially modified - so clearing modified flag"))) | |
3462 | (set-buffer-modified-p initial-buffer-modified-p) | |
3463 | ))) | |
3464 | ||
3465 | (defun sh-rescan-buffer () | |
3466 | "Rescan the buffer for case alternative parentheses and here documents." | |
3467 | (interactive) | |
3468 | (if (eq major-mode 'sh-mode) | |
3469 | (let ((inhibit-read-only t)) | |
3470 | (sh-remove-our-text-properties) | |
3471 | (message "Re-scanning buffer...") | |
3472 | (sh-scan-buffer) | |
3473 | (message "Re-scanning buffer...done") | |
3474 | ))) | |
3475 | ||
3476 | ;; ======================================================================== | |
3477 | ||
3478 | ;; Styles -- a quick and dirty way of saving the indenation settings. | |
3479 | ||
3480 | (defvar sh-styles-alist nil | |
3481 | "A list of all known shell indentation styles.") | |
3482 | ||
3483 | (defun sh-name-style (name &optional confirm-overwrite) | |
3484 | "Name the current indentation settings as a style called NAME. | |
3485 | If this name exists, the command will prompt whether it should be | |
3486 | overwritten if | |
3487 | - - it was called interactively with a prefix argument, or | |
3488 | - - called non-interactively with optional CONFIRM-OVERWRITE non-nil." | |
3489 | ;; (interactive "sName for this style: ") | |
3490 | (interactive | |
3491 | (list | |
3492 | (read-from-minibuffer "Name for this style? " ) | |
3493 | (not current-prefix-arg))) | |
3494 | (let ((slist (list name)) | |
3495 | (p sh-var-list) | |
3496 | var style) | |
3497 | (while p | |
3498 | (setq var (car p)) | |
3499 | (setq slist (append slist (list (cons var (symbol-value var))))) | |
3500 | (setq p (cdr p))) | |
3501 | (if (setq style (assoc name sh-styles-alist)) | |
3502 | (if (or (not confirm-overwrite) | |
3503 | (y-or-n-p "This style exists. Overwrite it? ")) | |
3504 | (progn | |
3505 | (message "Updating style %s" name) | |
3506 | (setcdr style (cdr slist))) | |
3507 | (message "Not changing style %s" name)) | |
3508 | (message "Creating new style %s" name) | |
3509 | (setq sh-styles-alist (append sh-styles-alist | |
3510 | (list slist))) | |
3511 | ))) | |
3512 | ||
3513 | (defun sh-load-style (name) | |
3514 | "Set shell indentation values for this buffer from those in style NAME." | |
3515 | (interactive (list (completing-read | |
3516 | "Which style to use for this buffer? " | |
3517 | sh-styles-alist nil t))) | |
3518 | (let ((sl (assoc name sh-styles-alist))) | |
3519 | (if (null sl) | |
3520 | (error "sh-load-style - style %s not known" name) | |
3521 | (setq sl (cdr sl)) | |
3522 | (while sl | |
3523 | (set (car (car sl)) (cdr (car sl))) | |
3524 | (setq sl (cdr sl)) | |
3525 | )))) | |
3526 | ||
3527 | (defun sh-save-styles-to-buffer (buff) | |
3528 | "Save all current styles in elisp to buffer BUFF. | |
3529 | This is always added to the end of the buffer." | |
3530 | (interactive (list | |
3531 | (read-from-minibuffer "Buffer to save styles in? " "*scratch*"))) | |
3532 | ;; This is an attempt to sort of pretty print it... | |
3533 | (save-excursion | |
3534 | (set-buffer (get-buffer-create buff)) | |
3535 | (goto-char (point-max)) | |
3536 | (insert "\n") | |
3537 | (let ((p sh-styles-alist) q) | |
3538 | (insert "(setq sh-styles-alist '(\n") | |
3539 | (while p | |
3540 | (setq q (car p)) | |
3541 | (insert " ( " (prin1-to-string (car q)) "\n") | |
3542 | (setq q (cdr q)) | |
3543 | (while q | |
3544 | (insert " "(prin1-to-string (car q)) "\n") | |
3545 | (setq q (cdr q))) | |
3546 | (insert " )\n") | |
3547 | (setq p (cdr p)) | |
3548 | ) | |
3549 | (insert "))\n") | |
3550 | ))) | |
3551 | ||
3552 | ||
3553 | ||
3554 | \f | |
ac59aed8 RS |
3555 | ;; statement syntax-commands for various shells |
3556 | ||
3557 | ;; You are welcome to add the syntax or even completely new statements as | |
3558 | ;; appropriate for your favorite shell. | |
3559 | ||
c410bd65 RS |
3560 | (define-skeleton sh-case |
3561 | "Insert a case/switch statement. See `sh-feature'." | |
cef926f3 RS |
3562 | (csh "expression: " |
3563 | "switch( " str " )" \n | |
3564 | > "case " (read-string "pattern: ") ?: \n | |
c410bd65 | 3565 | > _ \n |
cef926f3 | 3566 | "breaksw" \n |
c410bd65 | 3567 | ( "other pattern, %s: " |
cef926f3 | 3568 | < "case " str ?: \n |
c410bd65 | 3569 | > _ \n |
cef926f3 RS |
3570 | "breaksw" \n) |
3571 | < "default:" \n | |
c410bd65 RS |
3572 | > _ \n |
3573 | resume: | |
cef926f3 RS |
3574 | < < "endsw") |
3575 | (es) | |
3576 | (rc "expression: " | |
f964dfcb | 3577 | > "switch( " str " ) {" \n |
cef926f3 RS |
3578 | > "case " (read-string "pattern: ") \n |
3579 | > _ \n | |
3580 | ( "other pattern, %s: " | |
f964dfcb | 3581 | "case " str > \n |
cef926f3 | 3582 | > _ \n) |
f964dfcb | 3583 | "case *" > \n |
cef926f3 RS |
3584 | > _ \n |
3585 | resume: | |
f964dfcb | 3586 | ?} > ) |
cef926f3 | 3587 | (sh "expression: " |
f964dfcb GM |
3588 | > "case " str " in" \n |
3589 | > (read-string "pattern: ") | |
3590 | '(sh-electric-rparen) | |
3591 | \n | |
cef926f3 RS |
3592 | > _ \n |
3593 | ";;" \n | |
3594 | ( "other pattern, %s: " | |
f964dfcb | 3595 | > str '(sh-electric-rparen) \n |
cef926f3 RS |
3596 | > _ \n |
3597 | ";;" \n) | |
f964dfcb | 3598 | > "*" '(sh-electric-rparen) \n |
cef926f3 RS |
3599 | > _ \n |
3600 | resume: | |
f964dfcb | 3601 | "esac" > )) |
133693bc KH |
3602 | |
3603 | (define-skeleton sh-for | |
3604 | "Insert a for loop. See `sh-feature'." | |
3605 | (csh eval sh-modify sh | |
f964dfcb GM |
3606 | 1 "" |
3607 | 2 "foreach " | |
3608 | 4 " ( " | |
3609 | 6 " )" | |
3610 | 15 '< | |
3611 | 16 "end" | |
3612 | ) | |
133693bc | 3613 | (es eval sh-modify rc |
f964dfcb | 3614 | 4 " = ") |
133693bc | 3615 | (rc eval sh-modify sh |
f964dfcb GM |
3616 | 2 "for( " |
3617 | 6 " ) {" | |
3618 | 15 ?} ) | |
ac59aed8 | 3619 | (sh "Index variable: " |
f964dfcb | 3620 | > "for " str " in " _ "; do" \n |
133693bc | 3621 | > _ | ?$ & (sh-remember-variable str) \n |
f964dfcb | 3622 | "done" > )) |
ac59aed8 RS |
3623 | |
3624 | ||
3625 | ||
133693bc KH |
3626 | (define-skeleton sh-indexed-loop |
3627 | "Insert an indexed loop from 1 to n. See `sh-feature'." | |
3628 | (bash eval identity posix) | |
ac59aed8 RS |
3629 | (csh "Index variable: " |
3630 | "@ " str " = 1" \n | |
133693bc KH |
3631 | "while( $" str " <= " (read-string "upper limit: ") " )" \n |
3632 | > _ ?$ str \n | |
ac59aed8 RS |
3633 | "@ " str "++" \n |
3634 | < "end") | |
133693bc | 3635 | (es eval sh-modify rc |
f964dfcb | 3636 | 4 " =") |
133693bc | 3637 | (ksh88 "Index variable: " |
f964dfcb GM |
3638 | > "integer " str "=0" \n |
3639 | > "while (( ( " str " += 1 ) <= " | |
133693bc KH |
3640 | (read-string "upper limit: ") |
3641 | " )); do" \n | |
f964dfcb GM |
3642 | > _ ?$ (sh-remember-variable str) > \n |
3643 | "done" > ) | |
133693bc | 3644 | (posix "Index variable: " |
f964dfcb | 3645 | > str "=1" \n |
133693bc KH |
3646 | "while [ $" str " -le " |
3647 | (read-string "upper limit: ") | |
3648 | " ]; do" \n | |
3649 | > _ ?$ str \n | |
3650 | str ?= (sh-add (sh-remember-variable str) 1) \n | |
f964dfcb | 3651 | "done" > ) |
133693bc | 3652 | (rc "Index variable: " |
f964dfcb | 3653 | > "for( " str " in" " `{awk 'BEGIN { for( i=1; i<=" |
133693bc | 3654 | (read-string "upper limit: ") |
f964dfcb | 3655 | "; i++ ) print i }'`}) {" \n |
133693bc | 3656 | > _ ?$ (sh-remember-variable str) \n |
f964dfcb | 3657 | ?} >) |
133693bc | 3658 | (sh "Index variable: " |
f964dfcb | 3659 | > "for " str " in `awk 'BEGIN { for( i=1; i<=" |
133693bc KH |
3660 | (read-string "upper limit: ") |
3661 | "; i++ ) print i }'`; do" \n | |
3662 | > _ ?$ (sh-remember-variable str) \n | |
f964dfcb | 3663 | "done" > )) |
ac59aed8 RS |
3664 | |
3665 | ||
5d73ac66 RS |
3666 | (defun sh-shell-initialize-variables () |
3667 | "Scan the buffer for variable assignments. | |
3668 | Add these variables to `sh-shell-variables'." | |
3669 | (message "Scanning buffer `%s' for variable assignments..." (buffer-name)) | |
3670 | (save-excursion | |
3671 | (goto-char (point-min)) | |
3672 | (setq sh-shell-variables-initialized t) | |
3673 | (while (search-forward "=" nil t) | |
3674 | (sh-assignment 0))) | |
3675 | (message "Scanning buffer `%s' for variable assignments...done" | |
3676 | (buffer-name))) | |
3677 | ||
3678 | (defvar sh-add-buffer) | |
3679 | ||
3680 | (defun sh-add-completer (string predicate code) | |
3681 | "Do completion using `sh-shell-variables', but initialize it first. | |
3682 | This function is designed for use as the \"completion table\", | |
3683 | so it takes three arguments: | |
3684 | STRING, the current buffer contents; | |
3685 | PREDICATE, the predicate for filtering possible matches; | |
3686 | CODE, which says what kind of things to do. | |
3687 | CODE can be nil, t or `lambda'. | |
3688 | nil means to return the best completion of STRING, or nil if there is none. | |
3689 | t means to return a list of all possible completions of STRING. | |
3690 | `lambda' means to return t if STRING is a valid completion as it stands." | |
3691 | (let ((sh-shell-variables | |
3692 | (save-excursion | |
3693 | (set-buffer sh-add-buffer) | |
3694 | (or sh-shell-variables-initialized | |
3695 | (sh-shell-initialize-variables)) | |
3696 | (nconc (mapcar (lambda (var) | |
3697 | (let ((name | |
3698 | (substring var 0 (string-match "=" var)))) | |
3699 | (cons name name))) | |
3700 | process-environment) | |
3701 | sh-shell-variables)))) | |
3702 | (cond ((null code) | |
3703 | (try-completion string sh-shell-variables predicate)) | |
3704 | ((eq code t) | |
3705 | (all-completions string sh-shell-variables predicate)) | |
3706 | ((eq code 'lambda) | |
3707 | (assoc string sh-shell-variables))))) | |
3708 | ||
ac59aed8 | 3709 | (defun sh-add (var delta) |
133693bc | 3710 | "Insert an addition of VAR and prefix DELTA for Bourne (type) shell." |
ac59aed8 | 3711 | (interactive |
5d73ac66 RS |
3712 | (let ((sh-add-buffer (current-buffer))) |
3713 | (list (completing-read "Variable: " 'sh-add-completer) | |
3714 | (prefix-numeric-value current-prefix-arg)))) | |
133693bc KH |
3715 | (insert (sh-feature '((bash . "$[ ") |
3716 | (ksh88 . "$(( ") | |
3717 | (posix . "$(( ") | |
3718 | (rc . "`{expr $") | |
3719 | (sh . "`expr $") | |
3720 | (zsh . "$[ "))) | |
3721 | (sh-remember-variable var) | |
3722 | (if (< delta 0) " - " " + ") | |
3723 | (number-to-string (abs delta)) | |
3724 | (sh-feature '((bash . " ]") | |
3725 | (ksh88 . " ))") | |
3726 | (posix . " ))") | |
3727 | (rc . "}") | |
3728 | (sh . "`") | |
3729 | (zsh . " ]"))))) | |
3730 | ||
3731 | ||
3732 | ||
3733 | (define-skeleton sh-function | |
3734 | "Insert a function definition. See `sh-feature'." | |
3735 | (bash eval sh-modify ksh88 | |
3736 | 3 "() {") | |
3737 | (ksh88 "name: " | |
3738 | "function " str " {" \n | |
3739 | > _ \n | |
3740 | < "}") | |
3741 | (rc eval sh-modify ksh88 | |
3742 | 1 "fn ") | |
ac59aed8 RS |
3743 | (sh () |
3744 | "() {" \n | |
3745 | > _ \n | |
133693bc | 3746 | < "}")) |
ac59aed8 RS |
3747 | |
3748 | ||
3749 | ||
133693bc KH |
3750 | (define-skeleton sh-if |
3751 | "Insert an if statement. See `sh-feature'." | |
ac59aed8 RS |
3752 | (csh "condition: " |
3753 | "if( " str " ) then" \n | |
3754 | > _ \n | |
3755 | ( "other condition, %s: " | |
133693bc KH |
3756 | < "else if( " str " ) then" \n |
3757 | > _ \n) | |
ac59aed8 | 3758 | < "else" \n |
133693bc | 3759 | > _ \n |
ac59aed8 RS |
3760 | resume: |
3761 | < "endif") | |
133693bc | 3762 | (es "condition: " |
f964dfcb | 3763 | > "if { " str " } {" \n |
133693bc KH |
3764 | > _ \n |
3765 | ( "other condition, %s: " | |
f964dfcb | 3766 | "} { " str " } {" > \n |
133693bc | 3767 | > _ \n) |
f964dfcb | 3768 | "} {" > \n |
133693bc KH |
3769 | > _ \n |
3770 | resume: | |
f964dfcb GM |
3771 | ?} > ) |
3772 | (rc "condition: " | |
3773 | > "if( " str " ) {" \n | |
3774 | > _ \n | |
3775 | ( "other condition, %s: " | |
3776 | "} else if( " str " ) {" > \n | |
133693bc | 3777 | > _ \n) |
f964dfcb GM |
3778 | "} else {" > \n |
3779 | > _ \n | |
3780 | resume: | |
3781 | ?} > | |
3782 | ) | |
133693bc | 3783 | (sh "condition: " |
225f6185 | 3784 | '(setq input (sh-feature sh-test)) |
f964dfcb | 3785 | > "if " str "; then" \n |
133693bc KH |
3786 | > _ \n |
3787 | ( "other condition, %s: " | |
f964dfcb GM |
3788 | > "elif " str "; then" > \n |
3789 | > \n) | |
3790 | "else" > \n | |
3791 | > \n | |
133693bc | 3792 | resume: |
f964dfcb | 3793 | "fi" > )) |
ac59aed8 RS |
3794 | |
3795 | ||
3796 | ||
133693bc KH |
3797 | (define-skeleton sh-repeat |
3798 | "Insert a repeat loop definition. See `sh-feature'." | |
3799 | (es nil | |
f964dfcb | 3800 | > "forever {" \n |
133693bc | 3801 | > _ \n |
f964dfcb | 3802 | ?} > ) |
133693bc | 3803 | (zsh "factor: " |
f964dfcb GM |
3804 | > "repeat " str "; do" > \n |
3805 | > \n | |
3806 | "done" > )) | |
3807 | ||
ea39159e | 3808 | ;;;(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat)) |
133693bc KH |
3809 | |
3810 | ||
3811 | ||
3812 | (define-skeleton sh-select | |
3813 | "Insert a select statement. See `sh-feature'." | |
3814 | (ksh88 "Index variable: " | |
f964dfcb | 3815 | > "select " str " in " _ "; do" \n |
133693bc | 3816 | > ?$ str \n |
f964dfcb GM |
3817 | "done" > ) |
3818 | (bash eval sh-append ksh88) | |
3819 | ) | |
ea39159e | 3820 | ;;;(put 'sh-select 'menu-enable '(sh-feature sh-select)) |
133693bc KH |
3821 | |
3822 | ||
3823 | ||
3824 | (define-skeleton sh-tmp-file | |
3825 | "Insert code to setup temporary file handling. See `sh-feature'." | |
3826 | (bash eval identity ksh88) | |
3827 | (csh (file-name-nondirectory (buffer-file-name)) | |
3828 | "set tmp = /tmp/" str ".$$" \n | |
3829 | "onintr exit" \n _ | |
3830 | (and (goto-char (point-max)) | |
3831 | (not (bolp)) | |
3832 | ?\n) | |
3833 | "exit:\n" | |
3834 | "rm $tmp* >&/dev/null" >) | |
3835 | (es (file-name-nondirectory (buffer-file-name)) | |
f964dfcb GM |
3836 | > "local( signals = $signals sighup sigint; tmp = /tmp/" str |
3837 | ".$pid ) {" \n | |
133693bc KH |
3838 | > "catch @ e {" \n |
3839 | > "rm $tmp^* >[2]/dev/null" \n | |
3840 | "throw $e" \n | |
f964dfcb GM |
3841 | "} {" > \n |
3842 | _ \n | |
3843 | ?} > \n | |
3844 | ?} > ) | |
133693bc | 3845 | (ksh88 eval sh-modify sh |
f964dfcb | 3846 | 7 "EXIT") |
133693bc | 3847 | (rc (file-name-nondirectory (buffer-file-name)) |
f964dfcb | 3848 | > "tmp = /tmp/" str ".$pid" \n |
133693bc KH |
3849 | "fn sigexit { rm $tmp^* >[2]/dev/null }") |
3850 | (sh (file-name-nondirectory (buffer-file-name)) | |
f964dfcb | 3851 | > "TMP=${TMPDIR:-/tmp}/" str ".$$" \n |
133693bc | 3852 | "trap \"rm $TMP* 2>/dev/null\" " ?0)) |
ac59aed8 RS |
3853 | |
3854 | ||
3855 | ||
133693bc KH |
3856 | (define-skeleton sh-until |
3857 | "Insert an until loop. See `sh-feature'." | |
ac59aed8 | 3858 | (sh "condition: " |
225f6185 | 3859 | '(setq input (sh-feature sh-test)) |
f964dfcb | 3860 | > "until " str "; do" \n |
ac59aed8 | 3861 | > _ \n |
f964dfcb | 3862 | "done" > )) |
ea39159e | 3863 | ;;;(put 'sh-until 'menu-enable '(sh-feature sh-until)) |
133693bc KH |
3864 | |
3865 | ||
3866 | ||
3867 | (define-skeleton sh-while | |
3868 | "Insert a while loop. See `sh-feature'." | |
3869 | (csh eval sh-modify sh | |
f964dfcb GM |
3870 | 2 "" |
3871 | 3 "while( " | |
3872 | 5 " )" | |
3873 | 10 '< | |
3874 | 11 "end" ) | |
3875 | (es eval sh-modify sh | |
3876 | 3 "while { " | |
3877 | 5 " } {" | |
3878 | 10 ?} ) | |
3879 | (rc eval sh-modify sh | |
3880 | 3 "while( " | |
3881 | 5 " ) {" | |
3882 | 10 ?} ) | |
ac59aed8 | 3883 | (sh "condition: " |
225f6185 | 3884 | '(setq input (sh-feature sh-test)) |
f964dfcb | 3885 | > "while " str "; do" \n |
ac59aed8 | 3886 | > _ \n |
f964dfcb | 3887 | "done" > )) |
133693bc KH |
3888 | |
3889 | ||
3890 | ||
3891 | (define-skeleton sh-while-getopts | |
3892 | "Insert a while getopts loop. See `sh-feature'. | |
3893 | Prompts for an options string which consists of letters for each recognized | |
3894 | option followed by a colon `:' if the option accepts an argument." | |
3895 | (bash eval sh-modify sh | |
3896 | 18 "${0##*/}") | |
225f6185 KH |
3897 | (csh nil |
3898 | "while( 1 )" \n | |
3899 | > "switch( \"$1\" )" \n | |
3900 | '(setq input '("- x" . 2)) | |
3901 | > > | |
3902 | ( "option, %s: " | |
3903 | < "case " '(eval str) | |
3904 | '(if (string-match " +" str) | |
3905 | (setq v1 (substring str (match-end 0)) | |
3906 | str (substring str 0 (match-beginning 0))) | |
3907 | (setq v1 nil)) | |
3908 | str ?: \n | |
3909 | > "set " v1 & " = $2" | -4 & _ \n | |
3910 | (if v1 "shift") & \n | |
3911 | "breaksw" \n) | |
3912 | < "case --:" \n | |
3913 | > "shift" \n | |
3914 | < "default:" \n | |
3915 | > "break" \n | |
3916 | resume: | |
3917 | < < "endsw" \n | |
3918 | "shift" \n | |
3919 | < "end") | |
133693bc KH |
3920 | (ksh88 eval sh-modify sh |
3921 | 16 "print" | |
3922 | 18 "${0##*/}" | |
3923 | 36 "OPTIND-1") | |
3924 | (posix eval sh-modify sh | |
3925 | 18 "$(basename $0)") | |
3926 | (sh "optstring: " | |
f964dfcb | 3927 | > "while getopts :" str " OPT; do" \n |
133693bc | 3928 | > "case $OPT in" \n |
133693bc KH |
3929 | '(setq v1 (append (vconcat str) nil)) |
3930 | ( (prog1 (if v1 (char-to-string (car v1))) | |
3931 | (if (eq (nth 1 v1) ?:) | |
3932 | (setq v1 (nthcdr 2 v1) | |
3933 | v2 "\"$OPTARG\"") | |
3934 | (setq v1 (cdr v1) | |
3935 | v2 nil))) | |
f964dfcb | 3936 | > str "|+" str '(sh-electric-rparen) \n |
133693bc | 3937 | > _ v2 \n |
f964dfcb GM |
3938 | > ";;" \n) |
3939 | > "*" '(sh-electric-rparen) \n | |
133693bc | 3940 | > "echo" " \"usage: " "`basename $0`" |
c898fb28 | 3941 | " [+-" '(setq v1 (point)) str |
133693bc KH |
3942 | '(save-excursion |
3943 | (while (search-backward ":" v1 t) | |
c898fb28 | 3944 | (replace-match " ARG] [+-" t t))) |
133693bc | 3945 | (if (eq (preceding-char) ?-) -5) |
c898fb28 | 3946 | "] [--] ARGS...\"" \n |
f964dfcb GM |
3947 | "exit 2" > \n |
3948 | "esac" > | |
3949 | \n "done" | |
3950 | > \n | |
133693bc | 3951 | "shift " (sh-add "OPTIND" -1))) |
ac59aed8 RS |
3952 | |
3953 | ||
3954 | ||
3955 | (defun sh-assignment (arg) | |
133693bc | 3956 | "Remember preceding identifier for future completion and do self-insert." |
ac59aed8 | 3957 | (interactive "p") |
133693bc KH |
3958 | (self-insert-command arg) |
3959 | (if (<= arg 1) | |
ac59aed8 RS |
3960 | (sh-remember-variable |
3961 | (save-excursion | |
133693bc KH |
3962 | (if (re-search-forward (sh-feature sh-assignment-regexp) |
3963 | (prog1 (point) | |
3964 | (beginning-of-line 1)) | |
3965 | t) | |
84bfbb44 | 3966 | (match-string 1)))))) |
ac59aed8 RS |
3967 | |
3968 | ||
3969 | ||
3970 | (defun sh-maybe-here-document (arg) | |
3971 | "Inserts self. Without prefix, following unquoted `<' inserts here document. | |
3972 | The document is bounded by `sh-here-document-word'." | |
3973 | (interactive "*P") | |
3974 | (self-insert-command (prefix-numeric-value arg)) | |
3975 | (or arg | |
3976 | (not (eq (char-after (- (point) 2)) last-command-char)) | |
3977 | (save-excursion | |
133693bc | 3978 | (backward-char 2) |
ac59aed8 RS |
3979 | (sh-quoted-p)) |
3980 | (progn | |
3981 | (insert sh-here-document-word) | |
133693bc | 3982 | (or (eolp) (looking-at "[ \t]") (insert ? )) |
ac59aed8 | 3983 | (end-of-line 1) |
133693bc KH |
3984 | (while |
3985 | (sh-quoted-p) | |
3986 | (end-of-line 2)) | |
ac59aed8 RS |
3987 | (newline) |
3988 | (save-excursion (insert ?\n sh-here-document-word))))) | |
3989 | ||
3990 | \f | |
3991 | ;; various other commands | |
3992 | ||
133693bc KH |
3993 | (autoload 'comint-dynamic-complete "comint" |
3994 | "Dynamically perform completion at point." t) | |
3995 | ||
3996 | (autoload 'shell-dynamic-complete-command "shell" | |
3997 | "Dynamically complete the command at point." t) | |
3998 | ||
ac59aed8 RS |
3999 | (autoload 'comint-dynamic-complete-filename "comint" |
4000 | "Dynamically complete the filename at point." t) | |
4001 | ||
133693bc KH |
4002 | (autoload 'shell-dynamic-complete-environment-variable "shell" |
4003 | "Dynamically complete the environment variable at point." t) | |
4004 | ||
ac59aed8 RS |
4005 | |
4006 | ||
cd76025c KH |
4007 | (defun sh-newline-and-indent () |
4008 | "Strip unquoted whitespace, insert newline, and indent like current line." | |
4009 | (interactive "*") | |
4010 | (indent-to (prog1 (current-indentation) | |
4011 | (delete-region (point) | |
4012 | (progn | |
4013 | (or (zerop (skip-chars-backward " \t")) | |
4014 | (if (sh-quoted-p) | |
4015 | (forward-char))) | |
4016 | (point))) | |
4017 | (newline)))) | |
ac59aed8 | 4018 | |
ac59aed8 RS |
4019 | (defun sh-beginning-of-command () |
4020 | "Move point to successive beginnings of commands." | |
4021 | (interactive) | |
4022 | (if (re-search-backward sh-beginning-of-command nil t) | |
4023 | (goto-char (match-beginning 2)))) | |
4024 | ||
ac59aed8 RS |
4025 | (defun sh-end-of-command () |
4026 | "Move point to successive ends of commands." | |
4027 | (interactive) | |
4028 | (if (re-search-forward sh-end-of-command nil t) | |
4029 | (goto-char (match-end 1)))) | |
4030 | ||
f7c7053e | 4031 | (provide 'sh-script) |
43c89a96 | 4032 | |
f964dfcb | 4033 | ;;; sh-script.el ends here |