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