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