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