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