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