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