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