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