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