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