Remove some function declarations, no longer needed or correct
[bpt/emacs.git] / lisp / progmodes / sh-script.el
CommitLineData
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
247This is the basis for `sh-feature'. See also `sh-alias-alist'.
248By default we have the following three hierarchies:
249
250csh 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
254rc Plan 9 Shell
255 es Extensible Shell
256sh 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 284Use this where the name of the executable doesn't correspond to the type of
cd482e05
RS
285shell 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
348See `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.
360For 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.
382These are used for completion in addition to all the variables named
383in `process-environment'. Each element looks like (VAR . VAR), where
384the 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.
475This 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 607First grouping matches the variable name. This is upto and including the `='
cd482e05
RS
608sign. 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 629That 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
635The 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
643The 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
651If the first character of this string is \"-\", this is taken as
652part of the redirection operator, rather than part of the
653word (that is, \"<<-\" instead of \"<<\"). This is a feature
654used by some shells (for example Bash) to indicate that leading
655tabs inside the here document should be ignored. In this case,
656Emacs indents the initial body and end of the here document with
657tabs, to the same level as the start (note that apart from this
658there is no support for indentation of here documents). This
659will only work correctly if `sh-basic-offset' is a multiple of
660`tab-width'.
661
662Any quote characters or leading whitespace in the word are
663removed 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 742Note that on some systems not all builtins are available or some are
cd482e05
RS
743implemented 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
764Given some confusion between keywords and builtins depending on shell and
765system, the distinction here has been based on whether they influence the
cd482e05
RS
766flow 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
803See `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.
881See `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.
994START is the position of <<.
df321f09 995STRING is the actual word used as delimiter (e.g. \"EOF\").
3e2dd647 996INDENTED is non-nil if the here document's content (and the EOF mark) can
9d37a5c0
SM
997be indented (i.e. a <<- was used rather than just <<).
998Point 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.
1037Find all the unescaped \" characters within said subshell, remembering that
1038subshells 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
1187Note: customizing these variables will not affect existing buffers if
449d63fd 1188`sh-make-vars-local' is non-nil. See the documentation for
f964dfcb
GM
1189variable `sh-make-vars-local', command `sh-make-vars-local'
1190and 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
1207nil mean: never.
1208t means: only if there seems to be an obvious value.
1209Anything 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 1218If t it is always shown. If nil, it is shown only when there
f964dfcb
GM
1219are 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
1227The position on the line is not necessarily meaningful.
1228In some cases the line will be the matching keyword, but this is not
1229always 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 1235Usually 0 meaning first column.
8db2b9fb 1236Can 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 1246This 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 1253nil means leave it as it is;
8db2b9fb 1254t means indent it as a normal line, aligning it to previous non-blank
f964dfcb 1255 non-comment line;
7d86c380 1256a 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
1324This includes lines after `else' and `elif' statements, too, but
1325does 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
1336This 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 1343This is used when the `do' is the first word of the line.
32518913
RS
1344This 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
1352This variable is used when the keyword `do' is on the same line as the
1353loop statement (e.g., `until', `while' or `for').
1354If 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 1361Normally this is 0, which aligns the `done' to the matching
f964dfcb 1362looping construct line.
dd77d6f4 1363Setting it non-zero allows you to have the `do' statement on a line
f964dfcb
GM
1364by 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 1370This 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 1376This 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 1388For 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
1401This 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
1407This 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 1425This specifically excludes an occurrence of WORD followed by
f964dfcb 1426punctuation 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.
1468This list is used when switching between buffer-local and global
8db2b9fb 1469values 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
1473If non-nil, indentation variables are made local initially.
1474If nil, you can later make the variables local by invoking
f964dfcb
GM
1475command `sh-make-vars-local'.
1476The default is t because I assume that in one Emacs session one is
1477frequently 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.
1489If 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.
1523When 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.
1542This mode works for many shells, since they all have roughly the same syntax,
1543as far as commands, arguments, variables, pipes, comments etc. are concerned.
1544Unless the file's magic number indicates the shell, your usual shell is
1545assumed. Since filenames rarely give a clue, they are not further analyzed.
1546
133693bc
KH
1547This mode adapts to the variations between shells (see `sh-set-shell') by
1548means of an inheritance based feature lookup (see `sh-feature'). This
1549mechanism applies to all variables (including skeletons) that pertain to
1550shell-specific features.
ac59aed8 1551
133693bc
KH
1552The default style of this mode is that of Rosenblatt's Korn shell book.
1553The syntax of the statements varies with the shell being used. The
1554following 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
1567For 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
1571would indent to the way it currently is.
1572\\[sh-learn-buffer-indent] Set the indentation variables so the
8db2b9fb 1573buffer 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
1584unquoted < insert a here document.
133693bc
KH
1585
1586If you generally program a shell different from your login shell you can
aafd074a 1587set `sh-shell-file' accordingly. If your shell's file name doesn't correctly
133693bc
KH
1588indicate what shell it is use `sh-alias-alist' to translate.
1589
1590If your shell gives error messages with line numbers, you can use \\[executable-interpret]
1591with 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'.
1657This 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.
1739A keyword position is one where if we're looking at something that looks
1740like 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.
1748Here we assume that a newline should be treated as a semi-colon unless it
1749comes right after a special keyword.
1750This function does not pay attention to line-continuations.
1751If TOK is nil, point should be before the newline; otherwise, TOK is the token
1752before 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.
1807Does 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.
1967May 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.
2092Returns 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.
2103Point 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
2276When used interactively, insert the proper starting #!-line,
2277and make the visited file executable via `executable-set-magic',
2278perhaps querying depending on the value of `executable-query'.
2279
2280When this function is called noninteractively, INSERT-FLAG (the third
2281argument) controls whether to insert a #!-line and think about making
2282the visited file executable, and NO-QUERY-FLAG (the second argument)
2283controls whether to query about making the visited file executable.
2284
133693bc 2285Calls 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.
2363If ALIST isn't a list where every element is a cons, it is returned as is.
2364Else 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
2378If FUNCTION is non-nil, it is called with one argument,
2379the 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).
2464Indent as far as preceding non-empty line, then by steps of `sh-indentation'.
133693bc 2465Lines 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.
2496The working directory is that of the buffer, and only environment variables
2497are already set which is why you can mark a header within the script.
2498
2499With a positive prefix ARG, instead of sending region, define header from
2500beginning of buffer to point. With a negative prefix ARG, instead of sending
2501region, 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 2536Also, 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.
2542Normally they already are local. This command is provided in case
2543variable `sh-make-vars-local' has been set to nil.
2544
8db2b9fb 2545To revert all these variables to the global values, use
f964dfcb
GM
2546command `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.
2553Then, 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
2571You can enter a number (positive to increase indentation,
2572negative to decrease indentation, zero for no change to indentation).
f964dfcb 2573
8db2b9fb 2574Or, you can enter one of the following symbols which are relative to
f964dfcb
GM
2575the value of variable `sh-basic-offset'
2576which 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.
2610This 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 2710Return 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.
2777This is a list. nil means the line is to be left as is.
2778Otherwise 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'.
2790STRING 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.
2945If there is not [just] one such variable, return a string
2946indicating the problem.
2947If 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 3005Go to beginning of logical line unless END is non-nil, in which case
f964dfcb 3006we 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.
3163If 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.
3175This takes into account that there may be nested open..close pairings.
3176OPEN and CLOSE are regexps denoting the tokens to be matched.
3177Optional 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.
3212It can also return t or nil.
eac9c0ef 3213If an invalid value is found, throw an error unless Optional argument
f964dfcb
GM
3214IGNORE-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 3242Unless optional argument NO-SYMBOL is non-nil, then if VALUE is
f964dfcb
GM
3243can 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.
3266If 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
3355This tells you which variable, if any, controls the indentation of
3356this line.
3357If optional arg ARG is non-null (called interactively with a prefix),
3358a pop up window describes this variable.
3359If variable `sh-blink' is non-nil then momentarily go to the line
3360we 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.
3395If the current line is controlled by an indentation variable, prompt
3396for 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
3434If there is an indentation variable which controls this line's indentation,
3435then set it to a value which would indent the line the way it
3436presently is.
3437
3438If the value can be represented by one of the symbols then do so
3439unless 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.
3505Buffer BUFFER is in `occur-mode'.
3506If ADD-LINENUM is non-nil the message is preceded by the line number.
8db2b9fb 3507If OCCUR-POINT is non-nil then the line is marked as a new occurrence
f964dfcb
GM
3508so 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
3568Output in buffer \"*indent*\" shows any lines which have conflicting
8db2b9fb 3569values of a variable, and the final value of all variables learned.
946c009b
CY
3570When called interactively, pop to this buffer automatically if
3571there are any discrepancies.
f964dfcb 3572
8db2b9fb
SM
3573If no prefix ARG is given, then variables are set to numbers.
3574If a prefix arg is given, then variables are set to symbols when
f964dfcb
GM
3575applicable -- e.g. to symbol `+' if the value is that of the
3576basic indent.
3577If a positive numerical prefix is given, then `sh-basic-offset'
3578is set to the prefix's numerical value.
8db2b9fb 3579Otherwise, sh-basic-offset may or may not be changed, according
f964dfcb
GM
3580to the value of variable `sh-learn-basic-offset'.
3581
3582Abnormal hook `sh-learned-buffer-hook' if non-nil is called when the
3583function completes. The function is abnormal because it is called
8db2b9fb 3584with an alist of variables learned. This feature may be changed or
f964dfcb
GM
3585removed in the future.
3586
3587This 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
3791This is experimental, heuristic and arbitrary!
3792Argument VEC is a vector of information collected by
3793`sh-learn-buffer-indent'.
3794Return 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 3862If this name exists, the command will prompt whether it should be
f964dfcb 3863overwritten 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.
3897This 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.
4020Add 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.
4034This function is designed for use as the \"completion table\",
4035so 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.
4039CODE can be nil, t or `lambda'.
4040nil means to return the best completion of STRING, or nil if there is none.
4041t 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'.
4235Prompts for an options string which consists of letters for each recognized
4236option 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 4315The 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.
4371With no argument, inserts backslashes and aligns existing backslashes.
4372With an argument, deletes the backslashes.
4373
4374This function does not modify the last line of the region if the region ends
4375right at the start of the following line; it does not modify blank lines
546e2f6f 4376at the start of the region. So you can put the region around an entire
bdd5fa99
RS
4377shell 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