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