(perform-replace): Move START and END parameters
[bpt/emacs.git] / lisp / progmodes / sh-script.el
dissimilarity index 73%
index c04a316..9392c3d 100644 (file)
-;;; sh-script.el --- shell-script editing commands for Emacs
-;; Copyright (C) 1993 Free Software Foundation, Inc.
-
-;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
-;; Maintainer: FSF
-;; Keywords: shell programming
-
-;; This file is part of GNU Emacs.
-
-;; GNU Emacs is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
-
-;; GNU Emacs is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-
-;;; Commentary:
-
-;; Major mode for editing shell scripts.  Currently sh, ksh, bash and csh,
-;; tcsh are supported.  Structured statements can be inserted with one
-;; command.
-
-;; Autoloading of these functions is currently turned off
-;; because it's not clear whether this mode is really desirable to use.
-;; -- rms
-
-;;; Code:
-
-;; page 1:     variables and settings
-;; page 2:     mode-command and utility functions
-;; page 3:     statement syntax-commands for various shells
-;; page 4:     various other commands
-
-
-;;;###dont-autoload
-(setq auto-mode-alist
-      ;; matches files
-      ;;       - whose path contains /bin/, but not directories
-      (cons '("/bin/" . sh-or-other-mode)
-           ;;  - that have a suffix .sh or .shar (shell archive)
-           ;;  - that contain resources for the various shells
-           ;;  - startup files for X11
-           (cons '("\\.sh\\'\\|\\.shar\\'\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)\\'" . sh-mode)
-                 auto-mode-alist)))
-
-
-(defvar sh-mode-syntax-table
-  (let ((table (copy-syntax-table)))
-    (modify-syntax-entry ?\# "<" table)
-    (modify-syntax-entry ?\^l ">#" table)
-    (modify-syntax-entry ?\n ">#" table)
-    (modify-syntax-entry ?\" "\"\"" table)
-    (modify-syntax-entry ?\' "\"'" table)
-    (modify-syntax-entry ?\` "$`" table)
-    (modify-syntax-entry ?$ "_" table)
-    (modify-syntax-entry ?! "_" table)
-    (modify-syntax-entry ?% "_" table)
-    (modify-syntax-entry ?: "_" table)
-    (modify-syntax-entry ?. "_" table)
-    (modify-syntax-entry ?^ "_" table)
-    (modify-syntax-entry ?~ "_" table)
-    table)
-  "Syntax table in use in Shell-Script mode.")
-
-
-
-(defvar sh-use-prefix nil
-  "If non-nil when loading, `$' and `<' will be  C-c $  and  C-c < .")
-
-(defvar sh-mode-map
-  (let ((map (make-sparse-keymap)))
-    (define-key map "\C-c(" 'sh-function)
-    (define-key map "\C-c\C-w" 'sh-while)
-    (define-key map "\C-c\C-u" 'sh-until)
-    (define-key map "\C-c\C-s" 'sh-select)
-    (define-key map "\C-c\C-l" 'sh-indexed-loop)
-    (define-key map "\C-c\C-i" 'sh-if)
-    (define-key map "\C-c\C-f" 'sh-for)
-    (define-key map "\C-c\C-c" 'sh-case)
-    
-    (define-key map (if sh-use-prefix "\C-c$" "$")
-      'sh-query-for-variable)
-    (define-key map "=" 'sh-assignment)
-    (define-key map "\C-c+" 'sh-add)
-    (define-key map (if sh-use-prefix "\C-c<" "<")
-      'sh-maybe-here-document)
-    (define-key map "(" 'pair-insert-maybe)
-    (define-key map "{" 'pair-insert-maybe)
-    (define-key map "[" 'pair-insert-maybe)
-    (define-key map "'" 'pair-insert-maybe)
-    (define-key map "`" 'pair-insert-maybe)
-    (define-key map "\"" 'pair-insert-maybe)
-
-    (define-key map "\t" 'sh-indent-line)
-    (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename
-                              map (current-global-map))
-    (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
-                              map (current-global-map))
-    ;; Now that tabs work properly, this might be unwanted.
-    (substitute-key-definition 'delete-backward-char
-                              'backward-delete-char-untabify
-                              map (current-global-map))
-    (define-key map "\C-c:" 'sh-set-shell)
-    (substitute-key-definition 'beginning-of-defun
-                              'sh-beginning-of-compound-command
-                              map (current-global-map))
-    (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
-                              map (current-global-map))
-    (substitute-key-definition 'forward-sentence 'sh-end-of-command
-                              map (current-global-map))
-    (substitute-key-definition 'manual-entry 'sh-manual-entry
-                              map (current-global-map))
-    (define-key map [menu-bar insert] 
-      (cons "Insert" (make-sparse-keymap "Insert")))
-    (define-key map [menu-bar insert sh-while] 
-      '("While loop" . sh-while))
-    (define-key map [menu-bar insert sh-until] 
-      '("Until loop" . sh-until))
-    (define-key map [menu-bar insert sh-select] 
-      '("Select statement" . sh-select))
-    (define-key map [menu-bar insert sh-indexed-loop] 
-      '("Indexed loop" . sh-indexed-loop))
-    (define-key map [menu-bar insert sh-if] 
-      '("If statement" . sh-if))
-    (define-key map [menu-bar insert sh-for] 
-      '("For loop" . sh-for))
-    (define-key map [menu-bar insert sh-case] 
-      '("Case statement" . sh-case))
-    map)
-  "Keymap used in Shell-Script mode.")
-
-
-
-(defvar sh-find-file-modifies t
-  "*What to do when newly found file has no magic number:
-       nil     do nothing
-       t       insert magic number
-       other   insert magic number, but mark as unmodified.")
-
-
-(defvar sh-query-for-magic t
-  "*If non-nil, ask user before changing or inserting magic number.")
-
-
-(defvar sh-magicless-file-regexp "/\\.[^/]+$"
-  "*On files with this kind of name no magic is inserted or changed.")
-
-
-;; someone who understands /etc/magic better than me should beef this up
-;; this currently covers only SCO Unix and Sinix executables
-;; the elegant way would be to read /etc/magic
-(defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode)
-                            ("#!.*perl" . perl-mode))
-  "A regexp to match the magic number of a found file.
-Currently this is only used by function `sh-or-other-mode'.")
-
-
-(defvar sh-executable ".* is \\([^ \t]*\\)\n"
-  "*Regexp to match the output of sh builtin `type' command on your machine.
-The regexp must match the whole output, and must contain a \\(something\\)
-construct which matches the actual executable.")
-
-
-
-(defvar sh-chmod-argument "+x"
-  "*After saving, if the file is not executable, set this mode.
-The mode can be absolute, such as \"777\", or relative, such as \"+x\".
-Do nothing if this is nil.")
-
-
-(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
-  "*The executable of the shell being programmed.")
-
-(defvar sh-shell-argument nil
-  "*A single argument for the magic number, or nil.")
-
-(defvar sh-shell nil
-  "The shell being programmed.  This is set by \\[sh-set-shell].")
-
-(defvar sh-shell-is-csh nil
-  "The shell being programmed.  This is set by \\[sh-set-shell].")
-
-(defvar sh-tab-width 4
-  "The default value for `tab-width' in Shell-Script mode.
-This is the width of tab stops after the indentation of the preceeding line.")
-
-(defvar sh-remember-variable-min 3
-  "*Don't remember variables less than this length for completing reads.")
-
-
-(defvar sh-beginning-of-command
-  "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)"
-  "*Regexp to determine the beginning of a shell command.
-The actual command starts at the beginning of the second \\(grouping\\).")
-
-(defvar sh-end-of-command
-  "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
-  "*Regexp to determine the end of a shell command.
-The actual command ends at the end of the first \\(grouping\\).")
-
-
-
-(defvar sh-assignment-space '(csh tcsh)
-  "List of shells that allow spaces around the assignment =.")
-
-(defvar sh-here-document-word "+"
-  "Word to delimit here documents.")
-
-
-;process-environment
-(defvar sh-variables
-  '(("addsuffix" tcsh)                 ("allow_null_glob_expansion" bash)
-    ("ampm" tcsh)                      ("argv" csh tcsh) 
-    ("autocorrect" tcsh)               ("autoexpand" tcsh)     
-    ("autolist" tcsh)                  ("autologout" tcsh)
-    ("auto_resume" bash)               ("BASH" bash)
-    ("BASH_VERSION" bash)              ("cdable_vars" bash)
-    ("cdpath" csh tcsh)                        ("CDPATH" sh ksh bash) 
-    ("chase_symlinks" tcsh)            ("child" csh tcsh) 
-    ("COLUMNS" ksh tcsh)               ("correct" tcsh) 
-    ("dextract" tcsh)                  ("echo" csh tcsh) 
-    ("edit" tcsh)                      ("EDITOR") 
-    ("el" tcsh)                                ("ENV" ksh bash)        
-    ("ERRNO" ksh)                      ("EUID" bash)
-    ("FCEDIT" ksh bash)                        ("FIGNORE" bash)
-    ("fignore" tcsh)                   ("FPATH" ksh) 
-    ("gid" tcsh)                       ("glob_dot_filenames" bash)
-    ("histchars" bash csh tcsh)                ("HISTFILE" ksh bash)
-    ("HISTFILESIZE" bash)              ("histlit" tcsh) 
-    ("history" csh tcsh)               ("history_control" bash)
-    ("HISTSIZE" bash)                  ("home" csh tcsh) 
-    ("HOME")                           ("HOST" tcsh) 
-    ("hostname_completion_file" bash)  ("HOSTTYPE" bash tcsh)
-    ("HPATH" tcsh)                     ("HUSHLOGIN")
-    ("IFS" sh ksh bash)                        ("ignoreeof" bash csh tcsh)
-    ("IGNOREEOF" bash)                 ("ignore_symlinks" tcsh) 
-    ("LANG")                           ("LC_COLLATE") 
-    ("LC_CTYPE")                       ("LC_MESSAGES") 
-    ("LC_MONETARY")                    ("LC_NUMERIC") 
-    ("LC_TIME")                                ("LINENO" ksh bash)
-    ("LINES" ksh tcsh)                 ("listjobs" tcsh) 
-    ("listlinks" tcsh)                 ("listmax" tcsh) 
-    ("LOGNAME")                                ("mail" csh tcsh) 
-    ("MAIL")                           ("MAILCHECK") 
-    ("MAILPATH")                       ("MAIL_WARNING" bash)
-    ("matchbeep" tcsh)                 ("nobeep" tcsh)
-    ("noclobber" bash csh tcsh)                ("noglob" csh tcsh)
-    ("nolinks" bash)                   ("nonomatch" csh tcsh) 
-    ("NOREBIND" tcsh)                  ("notify" bash)
-    ("no_exit_on_failed_exec" bash)    ("NO_PROMPT_VARS" bash)
-    ("oid" tcsh)                       ("OLDPWD" ksh bash)
-    ("OPTARG" sh ksh bash)             ("OPTERR" bash)
-    ("OPTIND" sh ksh bash)             ("PAGER") 
-    ("path" csh tcsh)                  ("PATH") 
-    ("PPID" ksh bash)                  ("printexitvalue" tcsh) 
-    ("prompt" csh tcsh)                        ("prompt2" tcsh) 
-    ("prompt3" tcsh)                   ("PROMPT_COMMAND" bash)
-    ("PS1" sh ksh bash)                        ("PS2" sh ksh bash)
-    ("PS3" ksh)                                ("PS4" ksh bash)
-    ("pushdsilent" tcsh)               ("pushdtohome" tcsh)
-    ("pushd_silent" bash)              ("PWD" ksh bash)
-    ("RANDOM" ksh bash)                        ("recexact" tcsh) 
-    ("recognize_only_executables" tcsh)        ("REPLY" ksh bash)
-    ("rmstar" tcsh)                    ("savehist" tcsh) 
-    ("SECONDS" ksh bash)               ("shell" csh tcsh) 
-    ("SHELL")                          ("SHLVL" bash tcsh) 
-    ("showdots" tcsh)                  ("sl" tcsh)     
-    ("status" csh tcsh)                        ("SYSTYPE" tcsh) 
-    ("tcsh" tcsh)                      ("term" tcsh) 
-    ("TERM")                           ("TERMCAP")     
-    ("time" csh tcsh)                  ("TMOUT" ksh bash) 
-    ("tperiod" tcsh)                   ("tty" tcsh) 
-    ("UID" bash)                       ("uid" tcsh)
-    ("verbose" csh tcsh)               ("version" tcsh)
-    ("visiblebell" tcsh)               ("VISUAL")
-    ("watch" tcsh)                     ("who" tcsh)
-    ("wordchars" tcsh))
-  "Alist of all environment and shell variables used for completing read.
-Variables only understood by some shells are associated to a list of those.")
-
-
-
-(defvar sh-font-lock-keywords nil
-  ;; This is done syntactically:
-  ;'(("[ \t]\\(#.*\\)" 1 font-lock-comment-face)
-  ;  ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face))
-  "*Rules for highlighting shell scripts.
-This variable is included into the various variables
-`sh-SHELL-font-lock-keywords'.  If no such variable exists for some shell,
-this one is used.")
-
-
-(defvar sh-sh-font-lock-keywords
-  (append sh-font-lock-keywords
-         '(("\\(^\\|[^-._a-z0-9]\\)\\(case\\|do\\|done\\|elif\\|else\\|esac\\|fi\\|for\\|if\\|in\\|then\\|until\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
-  "*Rules for highlighting Bourne shell scripts.")
-
-(defvar sh-ksh-font-lock-keywords
-  (append sh-sh-font-lock-keywords
-         '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
-  "*Rules for highlighting Korn shell scripts.")
-
-(defvar sh-bash-font-lock-keywords
-  (append sh-sh-font-lock-keywords
-         '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
-  "*Rules for highlighting Bourne again shell scripts.")
-
-
-(defvar sh-csh-font-lock-keywords
-  (append sh-font-lock-keywords
-         '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
-  "*Rules for highlighting C shell scripts.")
-
-(defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
-  "*Rules for highlighting Toronto C shell scripts.")
-
-
-\f
-;; mode-command and utility functions
-
-;;;###dont-autoload
-(defun sh-or-other-mode ()
-  "Decide whether this is a compiled executable or a script.
-Usually the file-names of scripts and binaries cannot be automatically
-distinguished, so the presence of an executable's magic number is used."
-  (funcall (or (let ((l magic-number-alist))
-                (while (and l
-                            (not (looking-at (car (car l)))))
-                  (setq l (cdr l)))
-                (cdr (car l)))
-              'sh-mode)))
-
-
-;;;###dont-autoload
-(defun sh-mode ()
-  "Major mode for editing shell scripts.
-This mode works for many shells, since they all have roughly the same syntax,
-as far as commands, arguments, variables, pipes, comments etc. are concerned.
-Unless the file's magic number indicates the shell, your usual shell is
-assumed.  Since filenames rarely give a clue, they are not further analyzed.
-
-The syntax of the statements varies with the shell being used.  The syntax of
-statements can be modified by putting a property on the command or new ones
-defined with `define-sh-skeleton'.  For example
-
-    (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
-or
-    (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
-
-where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
-
-The following commands are available, based on the current shell's syntax:
-
-\\[sh-case]     case statement
-\\[sh-for]      for loop
-\\[sh-function]         function definition
-\\[sh-if]       if statement
-\\[sh-indexed-loop]     indexed loop from 1 to n
-\\[sh-select]   select statement
-\\[sh-until]    until loop
-\\[sh-while]    while loop
-
-\\[backward-delete-char-untabify]       Delete backward one position, even if it was a tab.
-\\[sh-newline-and-indent]       Delete unquoted space and indent new line same as this one.
-\\[sh-end-of-command]   Go to end of successive commands.
-\\[sh-beginning-of-command]     Go to beginning of successive commands.
-\\[sh-set-shell]        Set this buffer's shell, and maybe its magic number.
-\\[sh-manual-entry]     Display the Unix manual entry for the current command or shell.
-
-\\[sh-query-for-variable]       Unless quoted with \\, query for a variable with completions offered.
-\\[sh-maybe-here-document]      Without prefix, following an unquoted < inserts here document.
-{, (, [, ', \", `
-       Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
-  (interactive)
-  (kill-all-local-variables)
-  (set-syntax-table sh-mode-syntax-table)
-  (use-local-map sh-mode-map)
-  (make-local-variable 'indent-line-function)
-  (make-local-variable 'comment-start)
-  (make-local-variable 'comment-start-skip)
-  (make-local-variable 'after-save-hook)
-  (make-local-variable 'require-final-newline)
-  (make-local-variable 'sh-shell-path)
-  (make-local-variable 'sh-shell)
-  (make-local-variable 'sh-shell-is-csh)
-  (make-local-variable 'pair-alist)
-  (make-local-variable 'pair-filter)
-  (make-local-variable 'font-lock-defaults)
-  (make-local-variable 'sh-variables)
-  (setq major-mode 'sh-mode
-       mode-name "Shell-script"
-       ;; Why can't Emacs have one standard function with some parameters?
-       ;; Only few modes actually analyse the previous line's contents
-       indent-line-function 'sh-indent-line
-       comment-start "# "
-       after-save-hook 'sh-chmod
-       tab-width sh-tab-width
-       ;; C shells do
-       require-final-newline t
-       pair-alist '((?` _ ?`))
-       pair-filter 'sh-quoted-p)
-  ;; parse or insert magic number for exec
-  (save-excursion
-    (goto-char (point-min))
-    (sh-set-shell
-     (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
-        (buffer-substring (match-beginning 1) (match-end 1))
-       sh-shell-path)))
-  ;; find-file is set by `normal-mode' when called by `after-find-file'
-  (and (boundp 'find-file) find-file
-       (or (eq sh-find-file-modifies t)
-          (set-buffer-modified-p nil)))
-  (run-hooks 'sh-mode-hook))
-;;;###dont-autoload
-(defalias 'shell-script-mode 'sh-mode)
-
-
-
-(defmacro define-sh-skeleton (command documentation &rest definitions)
-  "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ...
-Prior definitions (e.g. from ~/.emacs) are maintained.
-Each definition is built up as (SHELL PROMPT ELEMENT ...).  Alternately
-a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
-
-For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
-Each DEFINITION is actually stored as
-       (put COMMAND SHELL (PROMPT ELEMENT ...)),
-which you can also do yourself."
-  (or (stringp documentation)
-      (setq definitions (cons documentation definitions)
-           documentation ""))
-  ;; The compiled version doesn't.
-  (require 'backquote)
-  (`(progn
-      (let ((definitions '(, definitions)))
-       (while definitions
-         ;; skeleton need not be loaded to define these
-         (or (and (not (if (boundp 'skeleton-debug) skeleton-debug))
-                  (get '(, command) (car (car definitions))))
-             (put '(, command) (car (car definitions))
-                  (if (symbolp (cdr (car definitions)))
-                      (get '(, command) (cdr (car definitions)))
-                    (cdr (car definitions)))))
-         (setq definitions (cdr definitions))))
-      (put '(, command) 'menu-enable '(get '(, command) sh-shell))
-      (defun (, command) ()
-       (, documentation)
-       (interactive)
-       (skeleton-insert
-        (or (get '(, command) sh-shell)
-            (error "%s statement syntax not defined for shell %s."
-                   '(, command) sh-shell)))))))
-
-
-
-(defun sh-indent-line ()
-  "Indent as far as preceding line, then by steps of `tab-width'.
-If previous line starts with a comment, it's considered empty."
-  (interactive)
-  (let ((previous (save-excursion
-                   (line-move -1)
-                   (back-to-indentation)
-                   (if (looking-at comment-start-skip)
-                       0
-                     (current-column)))))
-    (save-excursion
-      (indent-to (if (eq this-command 'newline-and-indent)
-                    previous
-                  (if (< (current-column)
-                         (progn (back-to-indentation)
-                                (current-column)))
-                      (if (eolp) previous 0)
-                    (if (eolp)
-                        (max previous (* (1+ (/ (current-column) tab-width))
-                                         tab-width))
-                      (* (1+ (/ (current-column) tab-width)) tab-width))))))
-      (if (< (current-column) (current-indentation))
-         (skip-chars-forward " \t"))))
-
-
-(defun sh-remember-variable (var)
-  "Make VARIABLE available for future completing reads in this buffer."
-  (or (< (length var) sh-remember-variable-min)
-      (assoc var sh-variables)
-      (setq sh-variables (cons (list var) sh-variables)))
-  var)
-
-
-;; Augment the standard variables by those found in the environment.
-(if (boundp 'process-environment)(let ((l process-environment))
-  (while l
-    (sh-remember-variable (substring (car l)
-                                    0 (string-match "=" (car l))))
-    (setq l (cdr l)))))
-
-
-
-(defun sh-quoted-p ()
-  "Is point preceded by an odd number of backslashes?"
-  (eq 1 (% (- (point) (save-excursion
-                       (skip-chars-backward "\\\\")
-                       (point)))
-          2)))
-
-
-
-(defun sh-executable (command)
-  "If COMMAND is an executable in $PATH its full name is returned.  Else nil."
-  (let ((point (point))
-       (buffer-modified-p (buffer-modified-p))
-       buffer-read-only after-change-function)
-    (call-process "sh" nil t nil "-c" (concat "type " command))
-    (setq point (prog1 (point)
-                 (goto-char point)))
-    (prog1
-       (and (looking-at sh-executable)
-            (eq point (match-end 0))
-            (buffer-substring (match-beginning 1) (match-end 1)))
-      (delete-region (point) point)
-      (set-buffer-modified-p buffer-modified-p))))
-
-
-
-(defun sh-chmod ()
-  "This gets called after saving a file to assure that it be executable.
-You can set the absolute or relative mode with `sh-chmod-argument'."
-  (if sh-chmod-argument
-       (or (file-executable-p buffer-file-name)
-          (shell-command (concat "chmod " sh-chmod-argument
-                                 " " buffer-file-name)))))
-\f
-;; statement syntax-commands for various shells
-
-;; You are welcome to add the syntax or even completely new statements as
-;; appropriate for your favorite shell.
-
-(define-sh-skeleton sh-case
-  "Insert a case/switch statement in the current shell's syntax."
-  (sh "expression: "
-      "case " str " in" \n
-      > (read-string "pattern: ") ?\) \n
-      > _ \n
-      ";;" \n
-      ( "other pattern, %s: "
-       < str ?\) \n
-       > \n
-       ";;" \n)
-      < "*)" \n
-      > \n
-      resume:
-      < < "esac")
-  (ksh . sh)
-  (bash . sh)
-  (csh "expression: "
-       "switch( " str " )" \n
-       > "case " (read-string "pattern: ") ?: \n
-       > _ \n
-       "breaksw" \n
-       ( "other pattern, %s: "
-        < "case " str ?: \n
-        > \n
-        "breaksw" \n)
-       < "default:" \n
-       > \n
-       resume:
-       < < "endsw")
-  (tcsh . csh))
-
-
-
-(define-sh-skeleton sh-for
-  "Insert a for loop in the current shell's syntax."
-  (sh "Index variable: "
-      "for " str " in " _ "; do" \n
-      > ?$ (sh-remember-variable str) \n
-      < "done")
-  (ksh . sh)
-  (bash . sh)
-  (csh "Index variable: "
-       "foreach " str " ( " _ " )" \n
-       > ?$ (sh-remember-variable str) \n
-       < "end")
-  (tcsh . csh))
-
-
-
-(define-sh-skeleton sh-indexed-loop
-  "Insert an indexed loop from 1 to n in the current shell's syntax."
-  (sh "Index variable: "
-      str "=1" \n
-      "while [ $" str " -le "
-      (read-string "upper limit: ")
-      " ]; do" \n
-      > _ ?$ str \n
-      str ?= (sh-add (sh-remember-variable str) 1) \n
-      < "done")
-  (ksh . sh)
-  (bash . sh)
-  (csh "Index variable: "
-       "@ " str " = 1" \n
-       "while( $" str " <= "
-       (read-string "upper limit: ")
-       " )" \n
-       > _ ?$ (sh-remember-variable str) \n
-       "@ " str "++" \n
-       < "end")
-  (tcsh . csh))
-
-
-
-(defun sh-add (var delta)
-  "Insert an addition of VAR and prefix DELTA for Bourne type shells."
-  (interactive
-   (list (sh-remember-variable
-         (completing-read "Variable: " sh-variables
-                          (lambda (element)
-                            (or (not (cdr element))
-                                (memq sh-shell (cdr element))))))
-        (prefix-numeric-value current-prefix-arg)))
-  (setq delta (concat (if (< delta 0) " - " " + ")
-                     (abs delta)))
-  (skeleton-insert
-   (assq sh-shell
-        '((sh "`expr $" var delta "`")
-          (ksh "$(( $" var delta " ))")
-          (bash "$[ $" var delta " ]")))
-   t))
-
-
-
-(define-sh-skeleton sh-function
-  "Insert a function definition in the current shell's syntax."
-  (sh ()
-      "() {" \n
-      > _ \n
-      < "}")
-  (ksh "name: "
-       "function " str " {" \n
-       > _ \n
-       < "}")
-  (bash "name: "
-       "function " str "() {" \n
-       > _ \n
-       < "}"))
-
-
-
-(define-sh-skeleton sh-if
-  "Insert an if statement in the current shell's syntax."
-  (sh "condition: "
-      "if [ " str " ]; then" \n
-      > _ \n
-      ( "other condition, %s: "
-       < "elif [ " str " ]; then" \n
-       > \n)
-      < "else" \n
-      > \n
-      resume:
-      < "fi")
-  (ksh . sh)
-  (bash . sh)
-  (csh "condition: "
-       "if( " str " ) then" \n
-       > _ \n
-       ( "other condition, %s: "
-        < "else if ( " str " ) then" \n
-        > \n)
-       < "else" \n
-       > \n
-       resume:
-       < "endif")
-  (tcsh . csh))
-
-
-
-(define-sh-skeleton sh-select
-  "Insert a select statement in the current shell's syntax."
-  (ksh "Index variable: "
-       "select " str " in " _ "; do" \n
-       > ?$ str \n
-       < "done"))
-(put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
-
-
-
-(define-sh-skeleton sh-until
-  "Insert an until loop in the current shell's syntax."
-  (sh "condition: "
-      "until [ " str " ]; do" \n
-      > _ \n
-      < "done")
-  (ksh . sh)
-  (bash . sh))
-(put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
-
-
-(define-sh-skeleton sh-while
-  "Insert a while loop in the current shell's syntax."
-  (sh "condition: "
-      "while [ " str " ]; do" \n
-      > _ \n
-      < "done")
-  (ksh . sh)
-  (bash . sh)
-  (csh "condition: "
-       "while( " str " )" \n
-       > _ \n
-       < "end")
-  (tcsh . csh))
-
-
-
-(defun sh-query-for-variable (arg)
-  "Unless quoted with `\\', query for variable-name with completions.
-Prefix arg 0 means don't insert `$' before the variable.
-Prefix arg 2 or more means only do self-insert that many times.
-  If { is pressed as the first character, it will surround the variable name."
-  (interactive "*p")
-  (or (prog1 (or (> arg 1)
-                (sh-quoted-p))
-       (self-insert-command arg))
-    (let (completion-ignore-case
-         (minibuffer-local-completion-map
-          (or (get 'sh-query-for-variable 'keymap)
-              (put 'sh-query-for-variable 'keymap
-                   (copy-keymap minibuffer-local-completion-map))))
-         (buffer (current-buffer)))
-      ;; local function that depends on `arg' and `buffer'
-      (define-key minibuffer-local-completion-map "{"
-       (lambda () (interactive)
-         (if (or arg (> (point) 1))
-             (beep)
-           (save-window-excursion
-             (setq arg t)
-             (switch-to-buffer-other-window buffer)
-             (insert "{}")))))
-      (insert
-       (prog1
-          (sh-remember-variable
-           (completing-read "Variable: " sh-variables
-                            (lambda (element)
-                              (or (not (cdr element))
-                                  (memq sh-shell (cdr element))))))
-        (if (eq t arg) (forward-char 1))))
-      (if (eq t arg) (forward-char 1)))))
-
-
-
-(defun sh-assignment (arg)
-  "Insert self.  Remember previous identifier for future completing read."
-  (interactive "p")
-  (if (eq arg 1)
-      (sh-remember-variable
-       (save-excursion
-        (buffer-substring
-         (progn
-           (if (memq sh-shell sh-assignment-space)
-               (skip-chars-backward " \t"))
-           (point))
-         (progn
-           (skip-chars-backward "a-zA-Z0-9_")
-           (point))))))
-  (self-insert-command arg))
-
-
-
-(defun sh-maybe-here-document (arg)
-  "Inserts self.  Without prefix, following unquoted `<' inserts here document.
-The document is bounded by `sh-here-document-word'."
-  (interactive "*P")
-  (self-insert-command (prefix-numeric-value arg))
-  (or arg
-      (not (eq (char-after (- (point) 2)) last-command-char))
-      (save-excursion
-       (goto-char (- (point) 2))
-       (sh-quoted-p))
-      (progn
-       (insert sh-here-document-word)
-       (or (looking-at "[ \t\n]") (insert ? ))
-       (end-of-line 1)
-       (newline)
-       (save-excursion (insert ?\n sh-here-document-word)))))
-
-\f
-;; various other commands
-
-(autoload 'comint-dynamic-complete-filename "comint"
-  "Dynamically complete the filename at point." t)
-
-
-
-(defun sh-newline-and-indent (&optional arg)
-  "Strip unquoted whitespace, insert newline, and indent like current line.
-Unquoted whitespace is stripped from the current line's end, unless a
-prefix ARG is given."
-  (interactive "*P")
-  (let ((previous (current-indentation))
-       (end-of-line (point)))
-    (if arg ()
-      (skip-chars-backward " \t") 
-      (and (< (point) end-of-line)
-          (sh-quoted-p)
-          (forward-char 1))
-      (delete-region (point) end-of-line))
-    (newline)
-    (indent-to previous)))
-
-
-
-(defun sh-set-shell (shell)
-  "Set this buffer's shell to SHELL (a string).
-Calls the value of `sh-set-shell-hook' if set."
-  (interactive "sName or path of shell: ")
-  (save-excursion
-    (goto-char (point-min))
-    (setq sh-shell-path (if (file-name-absolute-p shell)
-                                     shell
-                                   (or (sh-executable shell)
-                                       (error "Cannot find %s." shell)))
-         sh-shell (intern (file-name-nondirectory sh-shell-path))
-         sh-shell-is-csh (memq sh-shell '(csh tcsh))
-         font-lock-defaults
-         (let ((keywords (intern-soft (format "sh-%s-font-lock-keywords"
-                                              sh-shell))))
-           (list (if (and keywords (boundp keywords))
-                     keywords
-                   'sh-font-lock-keywords)))
-         comment-start-skip (if sh-shell-is-csh
-                                "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*"
-                              "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*")
-         mode-line-process (format ": %s" sh-shell)
-         shell (concat sh-shell-path
-                       (and sh-shell-argument " ")
-                       sh-shell-argument))
-    (and (not buffer-read-only)
-        (not (if buffer-file-name
-                 (string-match sh-magicless-file-regexp buffer-file-name)))
-        ;; find-file is set by `normal-mode' when called by `after-find-file'
-        (if (and (boundp 'find-file) find-file) sh-find-file-modifies t)
-        (if (looking-at "#!")
-            (and (skip-chars-forward "#! \t")
-                 (not (string= shell
-                               (buffer-substring (point)
-                                                 (save-excursion (end-of-line)
-                                                                 (point)))))
-                 (if sh-query-for-magic
-                     (y-or-n-p (concat "Replace magic number by ``#! "
-                                       shell "''? "))
-                   (message "Magic number ``%s'' replaced."
-                            (buffer-substring (point-min) (point))))
-                 (not (delete-region (point) (progn (end-of-line) (point))))
-                 (insert shell))
-          (if (if sh-query-for-magic
-                  (y-or-n-p (concat "Add ``#! " shell "''? "))
-                t)
-              (insert "#! " shell ?\n)))))
-  (run-hooks 'sh-set-shell-hook))
-
-
-
-(defun sh-beginning-of-command ()
-  "Move point to successive beginnings of commands."
-  (interactive)
-  (if (re-search-backward sh-beginning-of-command nil t)
-      (goto-char (match-beginning 2))))
-
-
-
-(defun sh-end-of-command ()
-  "Move point to successive ends of commands."
-  (interactive)
-  (if (re-search-forward sh-end-of-command nil t)
-      (goto-char (match-end 1))))
-
-
-
-(defun sh-manual-entry (arg)
-  "Display the Unix manual entry for the current command or shell.
-Universal argument ARG, is passed to `Man-getpage-in-background'."
-  (interactive "P")
-  (let ((command (save-excursion
-                  (sh-beginning-of-command)
-                  (sh-executable
-                   (buffer-substring (point)
-                                     (progn (forward-sexp) (point)))))))
-    (setq command (read-input (concat "Manual entry (default "
-                                     (symbol-name sh-shell)
-                                     "): ")
-                             (if command
-                                 (file-name-nondirectory command))))
-    (manual-entry (if (string= command "")
-                     (symbol-name sh-shell)
-                   command)
-                 arg)))
-
-;; sh-script.el ends here
+;;; sh-script.el --- shell-script editing commands for Emacs
+
+;; Copyright (C) 1993, 94, 95, 96, 97, 1999, 2001
+;;  Free Software Foundation, Inc.
+
+;; Author: Daniel Pfeiffer <occitan@esperanto.org>
+;; Version: 2.0f
+;; Maintainer: FSF
+;; Keywords: languages, unix
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Major mode for editing shell scripts.  Bourne, C and rc shells as well
+;; as various derivatives are supported and easily derived from.  Structured
+;; statements can be inserted with one command or abbrev.  Completion is
+;; available for filenames, variables known from the script, the shell and
+;; the environment as well as commands.
+
+;;; Known Bugs:
+
+;; - In Bourne the keyword `in' is not anchored to case, for, select ...
+;; - Variables in `"' strings aren't fontified because there's no way of
+;;   syntactically distinguishing those from `'' strings.
+
+;;             Indentation
+;;             ===========
+;; Indentation for rc and es modes is very limited, but for Bourne shells
+;; and its derivatives it is quite customizable.
+;; 
+;; The following description applies to sh and derived shells (bash,
+;; zsh, ...).
+;; 
+;; There are various customization variables which allow tailoring to
+;; a wide variety of styles.  Most of these variables are named
+;; sh-indent-for-XXX and sh-indent-after-XXX.  For example.
+;; sh-indent-after-if controls the indenting of a line following
+;; an if statement, and sh-indent-for-fi controls the indentation
+;; of the line containing the fi.
+;; 
+;; You can set each to a numeric value, but it is often more convenient
+;; to a symbol such as `+' which uses the value of variable `sh-basic-offset'.
+;; By changing this one variable you can increase or decrease how much
+;; indentation there is.  Valid symbols:
+;; 
+;;     +   Indent right by sh-basic-offset
+;;     -   Indent left  by sh-basic-offset
+;;     ++  Indent right twice sh-basic-offset
+;;     --  Indent left  twice sh-basic-offset
+;;     *   Indent right half sh-basic-offset
+;;     /   Indent left  half sh-basic-offset.
+;; 
+;; There are 4 commands to help set the indentation variables:
+;; 
+;; `sh-show-indent'
+;;    This shows what variable controls the indentation of the current
+;;    line and its value.
+;; 
+;; `sh-set-indent'
+;;    This allows you to set the value of the variable controlling the
+;;    current line's indentation.  You can enter a number or one of a
+;;    number of special symbols to denote the value of sh-basic-offset,
+;;    or its negative, or half it, or twice it, etc.  If you've used
+;;    cc-mode this should be familiar.  If you forget which symbols are
+;;    valid simply press C-h at the prompt.
+;; 
+;; `sh-learn-line-indent'
+;;    Simply make the line look the way you want it, then invoke this
+;;    command.  It will set the variable to the value that makes the line
+;;    indent like that.  If called with a prefix argument then it will set
+;;    the value to one of the symbols if applicable.
+;;    
+;; `sh-learn-buffer-indent'
+;;    This is the deluxe function!  It "learns" the whole buffer (use
+;;    narrowing if you want it to process only part).  It outputs to a
+;;    buffer *indent* any conflicts it finds, and all the variables it has
+;;    learned.  This buffer is a sort of Occur mode buffer, allowing you to
+;;    easily find where something was set.  It is popped to automatically
+;;    if there are any conflicts found or if `sh-popup-occur-buffer' is
+;;    non-nil.
+;;    `sh-indent-comment' will be set if all comments follow  the same
+;;    pattern;  if they don't it will be set to nil.
+;;    Whether `sh-basic-offset' is set is determined by variable
+;;    `sh-learn-basic-offset'.
+;; 
+;;    Unfortunately, `sh-learn-buffer-indent' can take a long time to run
+;;    (e.g. if there are large case statements).  Perhaps it does not make
+;;    sense to run it on large buffers: if lots of lines have different
+;;    indentation styles it will produce a lot of diagnostics in the
+;;    *indent* buffer; if there is a consistent style then running
+;;    `sh-learn-buffer-indent' on a small region of the buffer should
+;;    suffice.
+;;   
+;;     Saving indentation values
+;;     -------------------------
+;; After you've learned the values in a buffer, how to you remember
+;; them?   Originally I had hoped that `sh-learn-buffer-indent'
+;; would make this unnecessary;  simply learn the values when you visit
+;; the buffer.
+;; You can do this automatically like this:
+;;   (add-hook 'sh-set-shell-hook 'sh-learn-buffer-indent)
+;; 
+;; However...   `sh-learn-buffer-indent' is extremely slow,
+;; especially on large-ish buffer.  Also, if there are conflicts the
+;; "last one wins" which may not produce the desired setting.
+;; 
+;; So...There is a minimal way of being able to save indentation values and
+;; to reload them in another buffer or at another point in time.
+;; 
+;; Use `sh-name-style' to give a name to the indentation settings of
+;;     the current buffer.
+;; Use `sh-load-style' to load indentation settings for the current
+;;     buffer from a specific style.
+;; Use `sh-save-styles-to-buffer' to write all the styles to a buffer
+;;     in lisp code.  You can then store it in a file and later use
+;;     `load-file' to load it.
+;; 
+;;     Indentation variables - buffer local or global?
+;;     ----------------------------------------------
+;; I think that often having them buffer-local makes sense,
+;; especially if one is using `sh-learn-buffer-indent'.  However, if
+;; a user sets values using customization, these changes won't appear
+;; to work if the variables are already local!
+;; 
+;; To get round this, there is a variable `sh-make-vars-local' and 2
+;; functions: `sh-make-vars-local' and `sh-reset-indent-vars-to-global-values'.
+;; 
+;; If `sh-make-vars-local' is non-nil, then these variables become
+;; buffer local when the mode is established.
+;; If this is nil, then the variables are global.  At any time you
+;; can make them local with the command `sh-make-vars-local'.
+;; Conversely, to update with the global values you can use the
+;; command `sh-reset-indent-vars-to-global-values'.
+;; 
+;; This may be awkward, but the intent is to cover all cases.
+;; 
+;;     Awkward things, pitfalls
+;;     ------------------------
+;; Indentation for a sh script is complicated for a number of reasons:
+;; 
+;; 1. You can't format by simply looking at symbols, you need to look
+;;    at keywords.  [This is not the case for rc and es shells.]
+;; 2. The character ")" is used both as a matched pair "(" ... ")" and
+;;    as a stand-alone symbol (in a case alternative).  This makes
+;;    things quite tricky!
+;; 3. Here-documents in a script should be treated "as is", and when
+;;    they terminate we want to revert to the indentation of the line
+;;    containing the "<<" symbol.
+;; 4. A line may be continued using the "\".
+;; 5. The character "#" (outside a string) normally starts a comment,
+;;    but it doesn't in the sequence "$#"!
+;; 
+;; To try and address points 2 3 and 5 I used a feature that cperl mode
+;; uses, that of a text's syntax property.  This, however, has 2
+;; disadvantages:
+;; 1. We need to scan the buffer to find which ")" symbols belong to a
+;;    case alternative, to find any here documents, and handle "$#".
+;; 2. Setting the text property makes the buffer modified.  If the
+;;    buffer is read-only buffer we have to cheat and bypass the read-only
+;;    status.  This is for cases where the buffer started read-only buffer
+;;    but the user issued `toggle-read-only'.
+;; 
+;;     Bugs
+;;     ----
+;; - Indenting many lines is slow.  It currently does each line
+;;   independently, rather than saving state information.
+;; 
+;; - `sh-learn-buffer-indent' is extremely slow.
+;; 
+;; Richard Sharman <rsharman@pobox.com>  June 1999.
+
+;;; Code:
+
+;; page 1:     variables and settings
+;; page 2:     indentation stuff
+;; page 3:     mode-command and utility functions
+;; page 4:     statement syntax-commands for various shells
+;; page 5:     various other commands
+
+(eval-when-compile
+  (require 'skeleton)
+  (require 'comint))
+(require 'executable)
+
+
+
+(defgroup sh nil
+  "Shell programming utilities"
+  :group 'unix
+  :group 'languages)
+
+(defgroup sh-script nil
+  "Shell script mode"
+  :group 'sh
+  :prefix "sh-")
+
+
+(defcustom sh-ancestor-alist
+  '((ash . sh)
+    (bash . jsh)
+    (bash2 . jsh)
+    (dtksh . ksh)
+    (es . rc)
+    (itcsh . tcsh)
+    (jcsh . csh)
+    (jsh . sh)
+    (ksh . ksh88)
+    (ksh88 . jsh)
+    (oash . sh)
+    (pdksh . ksh88)
+    (posix . sh)
+    (tcsh . csh)
+    (wksh . ksh88)
+    (wsh . sh)
+    (zsh . ksh88)
+    (rpm . sh))
+  "*Alist showing the direct ancestor of various shells.
+This is the basis for `sh-feature'.  See also `sh-alias-alist'.
+By default we have the following three hierarchies:
+
+csh            C Shell
+  jcsh         C Shell with Job Control
+  tcsh         Turbo C Shell
+    itcsh      ? Turbo C Shell
+rc             Plan 9 Shell
+  es           Extensible Shell
+sh             Bourne Shell
+  ash          ? Shell
+  jsh          Bourne Shell with Job Control
+    bash       GNU Bourne Again Shell
+    ksh88      Korn Shell '88
+      ksh      Korn Shell '93
+       dtksh   CDE Desktop Korn Shell
+      pdksh    Public Domain Korn Shell
+      wksh     Window Korn Shell
+      zsh      Z Shell
+  oash         SCO OA (curses) Shell
+  posix                IEEE 1003.2 Shell Standard
+  wsh          ? Shell"
+  :type '(repeat (cons symbol symbol))
+  :group 'sh-script)
+
+
+(defcustom sh-alias-alist
+  (nconc (if (eq system-type 'gnu/linux)
+            '((csh . tcsh)
+              (ksh . pdksh)))
+        ;; for the time being
+        '((ksh . ksh88)
+           (bash2 . bash)
+          (sh5 . sh)))
+  "*Alist for transforming shell names to what they really are.
+Use this where the name of the executable doesn't correspond to the type of
+shell it really is."
+  :type '(repeat (cons symbol symbol))
+  :group 'sh-script)
+
+
+(defcustom sh-shell-file
+  (or
+   ;; On MSDOS and Windows, collapse $SHELL to lower-case and remove
+   ;; the executable extension, so comparisons with the list of
+   ;; known shells work.
+   (and (memq system-type '(ms-dos windows-nt))
+       (let* ((shell (getenv "SHELL"))
+              (shell-base
+               (and shell (file-name-nondirectory shell))))
+         ;; shell-script mode doesn't support DOS/Windows shells,
+         ;; so use the default instead.
+         (if (or (null shell)
+                 (member (downcase shell-base)
+                         '("command.com" "cmd.exe" "4dos.com" "ndos.com"
+                           "cmdproxy.exe")))
+             "/bin/sh"
+           (file-name-sans-extension (downcase shell)))))
+   (getenv "SHELL")
+   "/bin/sh")
+  "*The executable file name for the shell being programmed."
+  :type 'string
+  :group 'sh-script)
+
+
+(defcustom sh-shell-arg
+  ;; bash does not need any options when run in a shell script,
+  '((bash)
+    (csh . "-f")
+    (pdksh)
+    ;; Bill_Mann@praxisint.com says -p with ksh can do harm.
+    (ksh88)
+    ;; -p means don't initialize functions from the environment.
+    (rc . "-p")
+    ;; Someone proposed -motif, but we don't want to encourage
+    ;; use of a non-free widget set.
+    (wksh)
+    ;; -f means don't run .zshrc.
+    (zsh . "-f"))
+  "*Single argument string for the magic number.  See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice (const :tag "No Arguments" nil)
+                              (string :tag "Arguments")
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+(defcustom sh-imenu-generic-expression
+  `((sh
+     . ((nil "^\\s-*\\(function\\s-+\\)?\\([A-Za-z_][A-Za-z_0-9]+\\)\\s-*()" 2))))
+  "*Alist of regular expressions for recognizing shell function definitions.
+See `sh-feature' and `imenu-generic-expression'."
+  :type '(alist :key-type (symbol :tag "Shell")
+               :value-type (alist :key-type (choice :tag "Title"
+                                                    string
+                                                    (const :tag "None" nil))
+                                  :value-type
+                                  (repeat :tag "Regexp, index..." sexp)))
+  :group 'sh-script
+  :version "20.4")
+
+(defvar sh-shell-variables nil
+  "Alist of shell variable names that should be included in completion.
+These are used for completion in addition to all the variables named
+in `process-environment'.  Each element looks like (VAR . VAR), where
+the car and cdr are the same symbol.")
+
+(defvar sh-shell-variables-initialized nil
+  "Non-nil if `sh-shell-variables' is initialized.")
+
+(defun sh-canonicalize-shell (shell)
+  "Convert a shell name SHELL to the one we should handle it as."
+  (if (string-match "\\.exe\\'" shell)
+      (setq shell (substring shell 0 (match-beginning 0))))
+  (or (symbolp shell)
+      (setq shell (intern shell)))
+  (or (cdr (assq shell sh-alias-alist))
+      shell))
+
+(defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file))
+  "The shell being programmed.  This is set by \\[sh-set-shell].")
+
+;; I turned off this feature because it doesn't permit typing commands
+;; in the usual way without help.
+;;(defvar sh-abbrevs
+;;  '((csh eval sh-abbrevs shell
+;;      "switch" 'sh-case
+;;      "getopts" 'sh-while-getopts)
+
+;;    (es eval sh-abbrevs shell
+;;     "function" 'sh-function)
+
+;;    (ksh88 eval sh-abbrevs sh
+;;        "select" 'sh-select)
+
+;;    (rc eval sh-abbrevs shell
+;;     "case" 'sh-case
+;;     "function" 'sh-function)
+
+;;    (sh eval sh-abbrevs shell
+;;     "case" 'sh-case
+;;     "function" 'sh-function
+;;     "until" 'sh-until
+;;     "getopts" 'sh-while-getopts)
+
+;;    ;; The next entry is only used for defining the others
+;;    (shell "for" sh-for
+;;        "loop" sh-indexed-loop
+;;        "if" sh-if
+;;        "tmpfile" sh-tmp-file
+;;        "while" sh-while)
+
+;;    (zsh eval sh-abbrevs ksh88
+;;      "repeat" 'sh-repeat))
+;;  "Abbrev-table used in Shell-Script mode.  See `sh-feature'.
+;;;Due to the internal workings of abbrev tables, the shell name symbol is
+;;;actually defined as the table for the like of \\[edit-abbrevs].")
+
+
+
+(easy-mmode-defsyntax sh-mode-syntax-table
+  '((?\# . "<")
+   (?\^l . ">#")
+   (?\n . ">#")
+   (?\" . "\"\"")
+   (?\' . "\"'")
+   (?\` . "\"`")
+   (?! . "_")
+   (?% . "_")
+   (?: . "_")
+   (?. . "_")
+   (?^ . "_")
+   (?~ . "_")
+   (?< . ".")
+   (?> . "."))
+  "Syntax-table used in Shell-Script mode.")
+
+
+(defvar sh-mode-map
+  (let ((map (make-sparse-keymap))
+       (menu-map (make-sparse-keymap "Insert")))
+    (define-key map "\C-c(" 'sh-function)
+    (define-key map "\C-c\C-w" 'sh-while)
+    (define-key map "\C-c\C-u" 'sh-until)
+    (define-key map "\C-c\C-t" 'sh-tmp-file)
+    (define-key map "\C-c\C-s" 'sh-select)
+    (define-key map "\C-c\C-r" 'sh-repeat)
+    (define-key map "\C-c\C-o" 'sh-while-getopts)
+    (define-key map "\C-c\C-l" 'sh-indexed-loop)
+    (define-key map "\C-c\C-i" 'sh-if)
+    (define-key map "\C-c\C-f" 'sh-for)
+    (define-key map "\C-c\C-c" 'sh-case)
+    (define-key map "\C-c?" 'sh-show-indent)
+    (define-key map "\C-c=" 'sh-set-indent)
+    (define-key map "\C-c<" 'sh-learn-line-indent)
+    (define-key map "\C-c>" 'sh-learn-buffer-indent)
+
+    (define-key map "=" 'sh-assignment)
+    (define-key map "\C-c+" 'sh-add)
+    (define-key map "\C-\M-x" 'sh-execute-region)
+    (define-key map "\C-c\C-x" 'executable-interpret)
+    (define-key map "<" 'sh-maybe-here-document)
+    (define-key map "(" 'skeleton-pair-insert-maybe)
+    (define-key map "{" 'skeleton-pair-insert-maybe)
+    (define-key map "[" 'skeleton-pair-insert-maybe)
+    (define-key map "'" 'skeleton-pair-insert-maybe)
+    (define-key map "`" 'skeleton-pair-insert-maybe)
+    (define-key map "\"" 'skeleton-pair-insert-maybe)
+
+    (substitute-key-definition 'complete-tag 'comint-dynamic-complete
+                              map (current-global-map))
+    (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
+                              map (current-global-map))
+    (substitute-key-definition 'delete-backward-char
+                              'backward-delete-char-untabify
+                              map (current-global-map))
+    (define-key map "\C-c:" 'sh-set-shell)
+    (substitute-key-definition 'beginning-of-defun
+                              'sh-beginning-of-compound-command
+                              map (current-global-map))
+    (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
+                              map (current-global-map))
+    (substitute-key-definition 'forward-sentence 'sh-end-of-command
+                              map (current-global-map))
+    (define-key map [menu-bar insert] (cons "Insert" menu-map))
+    (define-key menu-map [sh-while]    '("While Loop" . sh-while))
+    (define-key menu-map [sh-until]    '("Until Loop" . sh-until))
+    (define-key menu-map [sh-tmp-file] '("Temporary File" . sh-tmp-file))
+    (define-key menu-map [sh-select]   '("Select Statement" . sh-select))
+    (define-key menu-map [sh-repeat]   '("Repeat Loop" . sh-repeat))
+    (define-key menu-map [sh-getopts]  '("Options Loop" . sh-while-getopts))
+    (define-key menu-map [sh-indexed-loop] '("Indexed Loop" . sh-indexed-loop))
+    (define-key menu-map [sh-if]       '("If Statement" . sh-if))
+    (define-key menu-map [sh-for]      '("For Loop" . sh-for))
+    (define-key menu-map [sh-case]     '("Case Statement" . sh-case))
+    map)
+  "Keymap used in Shell-Script mode.")
+
+
+
+(defcustom sh-dynamic-complete-functions
+  '(shell-dynamic-complete-environment-variable
+    shell-dynamic-complete-command
+    comint-dynamic-complete-filename)
+  "*Functions for doing TAB dynamic completion."
+  :type '(repeat function)
+  :group 'sh-script)
+
+
+(defcustom sh-require-final-newline
+  '((csh . t)
+    (pdksh . t)
+    (rc eval . require-final-newline)
+    (sh eval . require-final-newline))
+  "*Value of `require-final-newline' in Shell-Script mode buffers.
+See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice (const :tag "require" t)
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+
+(defcustom sh-assignment-regexp
+  '((csh . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
+    ;; actually spaces are only supported in let/(( ... ))
+    (ksh88 . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=")
+    (rc . "\\<\\([a-zA-Z0-9_*]+\\)[ \t]*=")
+    (sh . "\\<\\([a-zA-Z0-9_]+\\)="))
+  "*Regexp for the variable name and what may follow in an assignment.
+First grouping matches the variable name.  This is upto and including the `='
+sign.  See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice regexp
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+
+(defcustom sh-indentation 4
+  "The width for further indentation in Shell-Script mode."
+  :type 'integer
+  :group 'sh-script)
+
+
+(defcustom sh-remember-variable-min 3
+  "*Don't remember variables less than this length for completing reads."
+  :type 'integer
+  :group 'sh-script)
+
+
+(defvar sh-header-marker nil
+  "When non-nil is the end of header for prepending by \\[sh-execute-region].
+That command is also used for setting this variable.")
+
+
+(defcustom sh-beginning-of-command
+  "\\([;({`|&]\\|\\`\\|[^\\]\n\\)[ \t]*\\([/~a-zA-Z0-9:]\\)"
+  "*Regexp to determine the beginning of a shell command.
+The actual command starts at the beginning of the second \\(grouping\\)."
+  :type 'regexp
+  :group 'sh-script)
+
+
+(defcustom sh-end-of-command
+  "\\([/~a-zA-Z0-9:]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
+  "*Regexp to determine the end of a shell command.
+The actual command ends at the end of the first \\(grouping\\)."
+  :type 'regexp
+  :group 'sh-script)
+
+
+
+(defvar sh-here-document-word "EOF"
+  "Word to delimit here documents.")
+
+(defvar sh-test
+  '((sh "[  ]" . 3)
+    (ksh88 "[[  ]]" . 4))
+  "Initial input in Bourne if, while and until skeletons.  See `sh-feature'.")
+
+
+;; customized this out of sheer bravado.  not for the faint of heart.
+;; but it *did* have an asterisk in the docstring!
+(defcustom sh-builtins
+  '((bash eval sh-append posix
+         "alias" "bg" "bind" "builtin" "declare" "dirs" "enable" "fc" "fg"
+         "help" "history" "jobs" "kill" "let" "local" "popd" "pushd" "source"
+         "suspend" "typeset" "unalias")
+
+    ;; The next entry is only used for defining the others
+    (bourne eval sh-append shell
+           "eval" "export" "getopts" "newgrp" "pwd" "read" "readonly"
+           "times" "ulimit")
+
+    (csh eval sh-append shell
+        "alias" "chdir" "glob" "history" "limit" "nice" "nohup" "rehash"
+        "setenv" "source" "time" "unalias" "unhash")
+
+    (dtksh eval identity wksh)
+
+    (es "access" "apids" "cd" "echo" "eval" "false" "let" "limit" "local"
+       "newpgrp" "result" "time" "umask" "var" "vars" "wait" "whatis")
+
+    (jsh eval sh-append sh
+        "bg" "fg" "jobs" "kill" "stop" "suspend")
+
+    (jcsh eval sh-append csh
+         "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
+
+    (ksh88 eval sh-append bourne
+          "alias" "bg" "false" "fc" "fg" "jobs" "kill" "let" "print" "time"
+          "typeset" "unalias" "whence")
+
+    (oash eval sh-append sh
+         "checkwin" "dateline" "error" "form" "menu" "newwin" "oadeinit"
+         "oaed" "oahelp" "oainit" "pp" "ppfile" "scan" "scrollok" "wattr"
+         "wclear" "werase" "win" "wmclose" "wmmessage" "wmopen" "wmove"
+         "wmtitle" "wrefresh")
+
+    (pdksh eval sh-append ksh88
+          "bind")
+
+    (posix eval sh-append sh
+          "command")
+
+    (rc "builtin" "cd" "echo" "eval" "limit" "newpgrp" "shift" "umask" "wait"
+       "whatis")
+
+    (sh eval sh-append bourne
+       "hash" "test" "type")
+
+    ;; The next entry is only used for defining the others
+    (shell "cd" "echo" "eval" "set" "shift" "umask" "unset" "wait")
+
+    (wksh eval sh-append ksh88
+         "Xt[A-Z][A-Za-z]*")
+
+    (zsh eval sh-append ksh88
+        "autoload" "bindkey" "builtin" "chdir" "compctl" "declare" "dirs"
+        "disable" "disown" "echotc" "enable" "functions" "getln" "hash"
+        "history" "integer" "limit" "local" "log" "popd" "pushd" "r"
+        "readonly" "rehash" "sched" "setopt" "source" "suspend" "true"
+        "ttyctl" "type" "unfunction" "unhash" "unlimit" "unsetopt" "vared"
+        "which"))
+  "*List of all shell builtins for completing read and fontification.
+Note that on some systems not all builtins are available or some are
+implemented as aliases.  See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice (repeat string)
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+
+
+(defcustom sh-leading-keywords
+  '((csh "else")
+
+    (es "true" "unwind-protect" "whatis")
+
+    (rc "else")
+
+    (sh "do" "elif" "else" "if" "then" "trap" "type" "until" "while"))
+  "*List of keywords that may be immediately followed by a builtin or keyword.
+Given some confusion between keywords and builtins depending on shell and
+system, the distinction here has been based on whether they influence the
+flow of control or syntax.  See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice (repeat string)
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+
+(defcustom sh-other-keywords
+  '((bash eval sh-append bourne
+         "bye" "logout")
+
+    ;; The next entry is only used for defining the others
+    (bourne eval sh-append sh
+           "function")
+
+    (csh eval sh-append shell
+        "breaksw" "default" "end" "endif" "endsw" "foreach" "goto"
+        "if" "logout" "onintr" "repeat" "switch" "then" "while")
+
+    (es "break" "catch" "exec" "exit" "fn" "for" "forever" "fork" "if"
+       "return" "throw" "while")
+
+    (ksh88 eval sh-append bourne
+          "select")
+
+    (rc "break" "case" "exec" "exit" "fn" "for" "if" "in" "return" "switch"
+       "while")
+
+    (sh eval sh-append shell
+       "done" "esac" "fi" "for" "in" "return")
+
+    ;; The next entry is only used for defining the others
+    (shell "break" "case" "continue" "exec" "exit")
+
+    (zsh eval sh-append bash
+        "select"))
+  "*List of keywords not in `sh-leading-keywords'.
+See `sh-feature'."
+  :type '(repeat (cons (symbol :tag "Shell")
+                      (choice (repeat string)
+                              (cons :format "Evaluate: %v"
+                                    (const :format "" eval)
+                                    sexp))))
+  :group 'sh-script)
+
+
+
+(defvar sh-variables
+  '((bash eval sh-append sh
+         "allow_null_glob_expansion" "auto_resume" "BASH" "BASH_VERSION"
+         "cdable_vars" "ENV" "EUID" "FCEDIT" "FIGNORE" "glob_dot_filenames"
+         "histchars" "HISTFILE" "HISTFILESIZE" "history_control" "HISTSIZE"
+         "hostname_completion_file" "HOSTTYPE" "IGNOREEOF" "ignoreeof"
+         "LINENO" "MAIL_WARNING" "noclobber" "nolinks" "notify"
+         "no_exit_on_failed_exec" "NO_PROMPT_VARS" "OLDPWD" "OPTERR" "PPID"
+         "PROMPT_COMMAND" "PS4" "pushd_silent" "PWD" "RANDOM" "REPLY"
+         "SECONDS" "SHLVL" "TMOUT" "UID")
+
+    (csh eval sh-append shell
+        "argv" "cdpath" "child" "echo" "histchars" "history" "home"
+        "ignoreeof" "mail" "noclobber" "noglob" "nonomatch" "path" "prompt"
+        "shell" "status" "time" "verbose")
+
+    (es eval sh-append shell
+       "apid" "cdpath" "CDPATH" "history" "home" "ifs" "noexport" "path"
+       "pid" "prompt" "signals")
+
+    (jcsh eval sh-append csh
+         "notify")
+
+    (ksh88 eval sh-append sh
+          "ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO"
+          "OLDPWD" "PPID" "PS3" "PS4" "PWD" "RANDOM" "REPLY" "SECONDS"
+          "TMOUT")
+
+    (oash eval sh-append sh
+         "FIELD" "FIELD_MAX" "LAST_KEY" "OALIB" "PP_ITEM" "PP_NUM")
+
+    (rc eval sh-append shell
+       "apid" "apids" "cdpath" "CDPATH" "history" "home" "ifs" "path" "pid"
+       "prompt" "status")
+
+    (sh eval sh-append shell
+       "CDPATH" "IFS" "OPTARG" "OPTIND" "PS1" "PS2")
+
+    ;; The next entry is only used for defining the others
+    (shell "COLUMNS" "EDITOR" "HOME" "HUSHLOGIN" "LANG" "LC_COLLATE"
+          "LC_CTYPE" "LC_MESSAGES" "LC_MONETARY" "LC_NUMERIC" "LC_TIME"
+          "LINES" "LOGNAME" "MAIL" "MAILCHECK" "MAILPATH" "PAGER" "PATH"
+          "SHELL" "TERM" "TERMCAP" "TERMINFO" "VISUAL")
+
+    (tcsh eval sh-append csh
+         "addsuffix" "ampm" "autocorrect" "autoexpand" "autolist"
+         "autologout" "chase_symlinks" "correct" "dextract" "edit" "el"
+         "fignore" "gid" "histlit" "HOST" "HOSTTYPE" "HPATH"
+         "ignore_symlinks" "listjobs" "listlinks" "listmax" "matchbeep"
+         "nobeep" "NOREBIND" "oid" "printexitvalue" "prompt2" "prompt3"
+         "pushdsilent" "pushdtohome" "recexact" "recognize_only_executables"
+         "rmstar" "savehist" "SHLVL" "showdots" "sl" "SYSTYPE" "tcsh" "term"
+         "tperiod" "tty" "uid" "version" "visiblebell" "watch" "who"
+         "wordchars")
+
+    (zsh eval sh-append ksh88
+        "BAUD" "bindcmds" "cdpath" "DIRSTACKSIZE" "fignore" "FIGNORE" "fpath"
+        "HISTCHARS" "hostcmds" "hosts" "HOSTS" "LISTMAX" "LITHISTSIZE"
+        "LOGCHECK" "mailpath" "manpath" "NULLCMD" "optcmds" "path" "POSTEDIT"
+        "prompt" "PROMPT" "PROMPT2" "PROMPT3" "PROMPT4" "psvar" "PSVAR"
+        "READNULLCMD" "REPORTTIME" "RPROMPT" "RPS1" "SAVEHIST" "SPROMPT"
+        "STTY" "TIMEFMT" "TMOUT" "TMPPREFIX" "varcmds" "watch" "WATCH"
+        "WATCHFMT" "WORDCHARS" "ZDOTDIR"))
+  "List of all shell variables available for completing read.
+See `sh-feature'.")
+
+\f
+;; Font-Lock support
+
+(defface sh-heredoc-face
+  '((((class color)
+      (background dark))
+     (:foreground "yellow" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "tan" ))
+    (t
+     (:bold t)))
+  "Face to show a here-document"
+  :group 'sh-indentation)
+(defvar sh-heredoc-face 'sh-heredoc-face)
+
+
+(defvar sh-font-lock-keywords
+  '((csh eval sh-append shell
+        '("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1
+          font-lock-variable-name-face))
+
+    (es eval sh-append executable-font-lock-keywords
+       '("\\$#?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\)" 1
+         font-lock-variable-name-face))
+
+    (rc eval identity es)
+
+    (sh eval sh-append shell
+       ;; Variable names.
+       '("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
+         font-lock-variable-name-face)
+       ;; Function names.
+       '("^\\(\\sw+\\)[ \t]*(" 1 font-lock-function-name-face)
+       '("\\<\\(function\\)\\>[ \t]*\\(\\sw+\\)?"
+         (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t)))
+
+    ;; The next entry is only used for defining the others
+    (shell eval sh-append executable-font-lock-keywords
+          '("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
+          '("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1
+            font-lock-variable-name-face))
+    (rpm eval sh-append rpm2
+        '("%{?\\(\\sw+\\)"  1 font-lock-keyword-face))
+    (rpm2 eval sh-append shell
+         '("^\\(\\sw+\\):"  1 font-lock-variable-name-face)))
+  "Default expressions to highlight in Shell Script modes.  See `sh-feature'.")
+
+(defvar sh-font-lock-keywords-1
+  '((sh "[ \t]in\\>"))
+  "Subdued level highlighting for Shell Script modes.")
+
+(defvar sh-font-lock-keywords-2 ()
+  "Gaudy level highlighting for Shell Script modes.")
+
+;; These are used for the syntax table stuff (derived from cperl-mode).
+;; Note: parse-sexp-lookup-properties must be set to t for it to work.
+(defconst sh-st-punc (string-to-syntax "."))
+(defconst sh-st-symbol (string-to-syntax "_"))
+(defconst sh-here-doc-syntax (string-to-syntax "|")) ;; generic string
+
+(defconst sh-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\|\\s_\\)+\\).*\\(\n\\)")
+
+(defvar sh-here-doc-markers nil)
+(make-variable-buffer-local 'sh-here-doc-markers)
+(defvar sh-here-doc-re sh-here-doc-open-re)
+(make-variable-buffer-local 'sh-here-doc-re)
+
+(defun sh-font-lock-close-heredoc (bol eof indented)
+  "Determine the syntax of the \\n after an EOF.
+If non-nil INDENTED indicates that the EOF was indented."
+  (let* (;; A rough regexp that should find the opening <<EOF back.
+        (sre (concat "<<\\(-?\\)\\s-*['\"\\]?"
+                     ;; Use \s| to cheaply check it's an open-heredoc.
+                     (regexp-quote eof) "['\"]?\\([ \t|;&)<>].*\\)?\\s|"))
+        ;; A regexp that will find other EOFs.
+        (ere (concat "^" (if indented "[ \t]*") (regexp-quote eof) "\n"))
+        (start (save-excursion
+                 (goto-char bol)
+                 (re-search-backward (concat sre "\\|" ere) nil t))))
+    ;; If subgroup 1 matched, we found an open-heredoc, otherwise we first
+    ;; found a close-heredoc which makes the current close-heredoc inoperant.
+    (cond
+     ((when (and start (match-end 1)
+                (not (and indented (= (match-beginning 1) (match-end 1))))
+                (not (sh-in-comment-or-string (match-beginning 0))))
+       ;; Make sure our `<<' is not the EOF1 of a `cat <<EOF1 <<EOF2'.
+       (save-excursion
+         (goto-char start)
+         (setq start (line-beginning-position 2))
+         (while
+             (progn
+               (re-search-forward "<<") ; Skip ourselves.
+               (and (re-search-forward sh-here-doc-open-re start 'move)
+                    (goto-char (match-beginning 0))
+                    (sh-in-comment-or-string (point)))))
+         ;; No <<EOF2 found after our <<.
+         (= (point) start)))
+      sh-here-doc-syntax)
+     ((not (or start (save-excursion (re-search-forward sre nil t))))
+      ;; There's no <<EOF either before or after us,
+      ;; so we should remove ourselves from font-lock's keywords.
+      (setq sh-here-doc-markers (delete eof sh-here-doc-markers))
+      (setq sh-here-doc-re
+           (concat sh-here-doc-open-re "\\|^\\([ \t]*\\)"
+                   (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))
+      nil))))
+
+(defun sh-font-lock-open-heredoc (start string)
+  "Determine the syntax of the \\n after a <<EOF.
+START is the position of <<.
+STRING is the actual word used as delimiter (f.ex. \"EOF\").
+INDENTED is non-nil if the here document's content (and the EOF mark) can
+be indented (i.e. a <<- was used rather than just <<)."
+  (unless (or (memq (char-before start) '(?< ?>))
+             (sh-in-comment-or-string start))
+    ;; We're looking at <<STRING, so we add "^STRING$" to the syntactic
+    ;; font-lock keywords to detect the end of this here document.
+    (let ((str (replace-regexp-in-string "['\"]" "" string)))
+      (unless (member str sh-here-doc-markers)
+       (push str sh-here-doc-markers)
+       (setq sh-here-doc-re
+             (concat sh-here-doc-open-re "\\|^\\([ \t]*\\)"
+                     (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))))
+    sh-here-doc-syntax))
+
+(defun sh-font-lock-here-doc (limit)
+  "Search for a heredoc marker."
+  ;; This looks silly, but it's because `sh-here-doc-re' keeps changing.
+  (re-search-forward sh-here-doc-re limit t))
+
+(defun sh-font-lock-paren (start)
+  (save-excursion
+    (goto-char start)
+    ;; Skip through all patterns
+    (while
+       (progn
+         (forward-comment (- (point-max)))
+         ;; Skip through one pattern
+         (while
+             (or (/= 0 (skip-syntax-backward "w_"))
+                 (/= 0 (skip-chars-backward "?*/"))
+                 (when (memq (char-before) '(?\" ?\'))
+                   (condition-case nil (progn (backward-sexp 1) t)
+                     (error nil)))))
+         (forward-comment (- (point-max)))
+         (when (eq (char-before) ?|)
+           (backward-char 1) t)))
+    (when (save-excursion (backward-char 2) (looking-at ";;\\|in"))
+      sh-st-punc)))
+
+(defconst sh-font-lock-syntactic-keywords
+  ;; A `#' begins a comment when it is unquoted and at the beginning of a
+  ;; word.  In the shell, words are separated by metacharacters.
+  ;; The list of special chars is taken from the single-unix spec
+  ;; of the shell command language (under `quoting') but with `$' removed.
+  `(("[^|&;<>()`\\\"' \t\n]\\(#+\\)" 1 ,sh-st-symbol)
+    ;; Find HEREDOC starters and add a corresponding rule for the ender.
+    (sh-font-lock-here-doc
+     (2 (sh-font-lock-open-heredoc
+        (match-beginning 0) (match-string 1)) nil t)
+     (5 (sh-font-lock-close-heredoc
+        (match-beginning 0) (match-string 4)
+        (/= (match-beginning 3) (match-end 3))) nil t))
+    ;; Distinguish the special close-paren in `case'.
+    (")" 0 (sh-font-lock-paren (match-beginning 0)))))
+
+(defun sh-font-lock-syntactic-face-function (state)
+  (if (nth 3 state)
+      (if (char-valid-p (nth 3 state))
+         font-lock-string-face
+       sh-heredoc-face)
+    font-lock-comment-face))
+
+(defgroup sh-indentation nil
+  "Variables controlling indentation in shell scripts.
+
+Note: customizing these variables will not affect existing buffers if
+`sh-make-vars-local' is no-nil.  See the documentation for
+variable `sh-make-vars-local', command `sh-make-vars-local'
+and command `sh-reset-indent-vars-to-global-values'."
+  :group 'sh-script)
+
+
+(defcustom sh-set-shell-hook nil
+  "*Hook run by `sh-set-shell'."
+  :type 'hook
+  :group 'sh-script)
+
+(defcustom sh-mode-hook nil
+  "*Hook run by `sh-mode'."
+  :type 'hook
+  :group 'sh-script)
+
+(defcustom sh-learn-basic-offset nil
+  "*When `sh-guess-basic-offset' should learn `sh-basic-offset'.
+
+nil mean:              never.
+t means:               only if there seems to be an obvious value.
+Anything else means:   whenever we have a \"good guess\" as to the value."
+  :type '(choice
+         (const :tag "Never" nil)
+         (const :tag "Only if sure"  t)
+         (const :tag "If have a good guess" usually))
+  :group 'sh-indentation)
+
+(defcustom sh-popup-occur-buffer nil
+  "*Controls when  `sh-learn-buffer-indent' pops the *indent* buffer.
+If t it is always shown.  If nil, it is shown only when there
+are conflicts."
+  :type '(choice
+         (const :tag "Only when there are conflicts." nil)
+         (const :tag "Always"  t))
+  :group 'sh-indentation)
+
+(defcustom sh-blink t
+  "*If non-nil, `sh-show-indent' shows the line indentation is relative to.
+The position on the line is not necessarily meaningful.
+In some cases the line will be the matching keyword, but this is not
+always the case."
+  :type 'boolean
+  :group 'sh-indentation)
+
+(defcustom sh-first-lines-indent 0
+  "*The indentation of the first non-blank non-comment line.
+Usually 0 meaning first column.
+Can be set to a number, or to nil which means leave it as is."
+  :type '(choice
+         (const :tag "Leave as is"     nil)
+         (integer :tag "Column number"
+                  :menu-tag "Indent to this col (0 means first col)" ))
+  :group 'sh-indentation)
+
+
+(defcustom sh-basic-offset 4
+  "*The default indentation increment.
+This value is used for the + and - symbols in an indentation variable."
+  :type 'integer
+  :group 'sh-indentation)
+
+(defcustom sh-indent-comment nil
+  "*How a comment line is to be indented.
+nil means leave it as it is;
+t  means indent it as a normal line, aligning it to previous non-blank
+   non-comment line;
+a number means align to that column, e.g. 0 means fist column."
+  :type '(choice
+         (const :tag "Leave as is." nil)
+         (const :tag "Indent as a normal line."  t)
+         (integer :menu-tag "Indent to this col (0 means first col)."
+                  :tag "Indent to column number.") )
+  :group 'sh-indentation)
+
+
+(defvar sh-debug nil
+  "Enable lots of debug messages - if function `sh-debug' is enabled.")
+
+
+;; Uncomment this defun and comment the defmacro for debugging.
+;; (defun sh-debug (&rest args)
+;;   "For debugging:  display message ARGS if variable SH-DEBUG is non-nil."
+;;   (if sh-debug
+;;       (apply 'message args)))
+(defmacro sh-debug (&rest args))
+
+(defconst sh-symbol-list
+  '((const :tag "+ "  :value +
+          :menu-tag "+   Indent right by sh-basic-offset")
+    (const :tag "- "  :value -
+          :menu-tag "-   Indent left  by sh-basic-offset")
+    (const :tag "++"  :value  ++
+          :menu-tag "++  Indent right twice sh-basic-offset")
+    (const :tag "--"  :value --
+          :menu-tag "--  Indent left  twice sh-basic-offset")
+    (const :tag "* " :value *
+          :menu-tag "*   Indent right half sh-basic-offset")
+    (const :tag "/ " :value /
+          :menu-tag "/   Indent left  half sh-basic-offset")))
+
+(defcustom sh-indent-for-else 0
+  "*How much to indent an else relative to an if.  Usually 0."
+  :type `(choice
+         (integer :menu-tag "A number (positive=>indent right)"
+                  :tag "A number")
+         (const :tag "--") ;; separator!
+         ,@ sh-symbol-list
+         )
+  :group 'sh-indentation)
+
+(defconst sh-number-or-symbol-list
+  (append '((integer :menu-tag "A number (positive=>indent right)"
+                    :tag "A number")
+           (const :tag "--"))          ; separator
+         sh-symbol-list))
+
+(defcustom sh-indent-for-fi 0
+  "*How much to indent a fi relative to an if.   Usually 0."
+  :type `(choice ,@ sh-number-or-symbol-list )
+  :group 'sh-indentation)
+
+(defcustom sh-indent-for-done '0
+  "*How much to indent a done relative to its matching stmt.   Usually 0."
+  :type `(choice ,@ sh-number-or-symbol-list )
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-else '+
+  "*How much to indent a statement after an else statement."
+  :type `(choice ,@ sh-number-or-symbol-list )
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-if '+
+  "*How much to indent a statement after an if statement.
+This includes lines after else and elif statements, too, but
+does not affect then else elif or fi statements themselves."
+  :type `(choice ,@ sh-number-or-symbol-list )
+  :group 'sh-indentation)
+
+(defcustom sh-indent-for-then '+
+  "*How much to indent an then relative to an if."
+  :type `(choice ,@ sh-number-or-symbol-list )
+  :group 'sh-indentation)
+
+(defcustom sh-indent-for-do '*
+  "*How much to indent a do statement.
+This is relative to the statement before the do, i.e. the
+while until or for statement."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-do '*
+  "*How much to indent a line after a do statement.
+This is used when the do is the first word of the line.
+This is relative to the statement before the do, e.g. a
+while for repeat or select statement."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-loop-construct '+
+  "*How much to indent a statement after a loop construct.
+
+This variable is used when the keyword \"do\" is on the same line as the
+loop statement (e.g.  \"until\", \"while\" or \"for\").
+If the do is on a line by itself, then `sh-indent-after-do' is used instead."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+
+(defcustom sh-indent-after-done 0
+  "*How much to indent a statement after a \"done\" keyword.
+Normally this is 0, which aligns the \"done\" to the matching
+looping construct line.
+Setting it non-zero allows you to have the \"do\" statement on a line
+by itself and align the done under to do."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-for-case-label '+
+  "*How much to indent a case label statement.
+This is relative to the line containing the case statement."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-for-case-alt '++
+  "*How much to indent statements after the case label.
+This is relative to the line containing the case statement."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+
+(defcustom sh-indent-for-continuation '+
+  "*How much to indent for a continuation statement."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-open '+
+  "*How much to indent after a line with an opening parenthesis or brace.
+For an open paren after a function `sh-indent-after-function' is used."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-function '+
+  "*How much to indent after a function line."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+;; These 2 are for the rc shell:
+
+(defcustom sh-indent-after-switch '+
+  "*How much to indent a case statement relative to the switch statement.
+This is for the rc shell."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+(defcustom sh-indent-after-case '+
+  "*How much to indent a statement relative to the case statement.
+This is for the rc shell."
+  :type `(choice ,@ sh-number-or-symbol-list)
+  :group 'sh-indentation)
+
+;; Internal use - not designed to be changed by the user:
+
+(defun sh-mkword-regexpr (word)
+  "Make a regexp which matches WORD as a word.
+This specifically excludes an occurrence of WORD followed by
+punctuation characters like '-'."
+  (concat word "\\([^-a-z0-9_]\\|$\\)"))
+
+(defconst sh-re-done (sh-mkword-regexpr "done"))
+
+
+(defconst sh-kws-for-done
+  '((sh .  ( "while" "until" "for" ) )
+    (bash . ( "while" "until" "for" "select"  ) )
+    (ksh88 . ( "while" "until" "for" "select"  ) )
+    (zsh .  ( "while" "until" "for" "repeat" "select" ) ) )
+  "Which keywords can match the word `done' in this shell.")
+
+
+(defconst sh-indent-supported
+  '((sh . t)
+    (csh . nil)
+    (rc . t))
+  "Shell types that shell indenting can do something with.")
+
+(defvar sh-indent-supported-here nil
+  "Non-nil if we support indentation for the current buffer's shell type.")
+
+(defconst sh-electric-rparen-needed
+  '((sh . t))
+  "Non-nil if the shell type needs an electric handling of case alternatives.")
+
+(defconst sh-var-list
+  '(
+    sh-basic-offset sh-first-lines-indent sh-indent-after-case
+    sh-indent-after-do sh-indent-after-done
+    sh-indent-after-else
+    sh-indent-after-if
+    sh-indent-after-loop-construct
+    sh-indent-after-open
+    sh-indent-comment
+    sh-indent-for-case-alt
+    sh-indent-for-case-label
+    sh-indent-for-continuation
+    sh-indent-for-do
+    sh-indent-for-done
+    sh-indent-for-else
+    sh-indent-for-fi
+    sh-indent-for-then
+    )
+  "A list of variables used by script mode to control indentation.
+This list is used when switching between buffer-local and global
+values of variables, and for the commands using indentation styles.")
+
+(defvar sh-make-vars-local t
+  "*Controls whether indentation variables are local to the buffer.
+If non-nil, indentation variables are made local initially.
+If nil, you can later make the variables local by invoking
+command `sh-make-vars-local'.
+The default is t because I assume that in one Emacs session one is
+frequently editing existing scripts with different styles.")
+
+\f
+;; mode-command and utility functions
+
+;;;###autoload
+(put 'sh-mode 'mode-class 'special)
+
+;;;###autoload
+(define-derived-mode sh-mode nil "Shell-script"
+  "Major mode for editing shell scripts.
+This mode works for many shells, since they all have roughly the same syntax,
+as far as commands, arguments, variables, pipes, comments etc. are concerned.
+Unless the file's magic number indicates the shell, your usual shell is
+assumed.  Since filenames rarely give a clue, they are not further analyzed.
+
+This mode adapts to the variations between shells (see `sh-set-shell') by
+means of an inheritance based feature lookup (see `sh-feature').  This
+mechanism applies to all variables (including skeletons) that pertain to
+shell-specific features.
+
+The default style of this mode is that of Rosenblatt's Korn shell book.
+The syntax of the statements varies with the shell being used.  The
+following commands are available, based on the current shell's syntax:
+
+\\[sh-case]     case statement
+\\[sh-for]      for loop
+\\[sh-function]         function definition
+\\[sh-if]       if statement
+\\[sh-indexed-loop]     indexed loop from 1 to n
+\\[sh-while-getopts]    while getopts loop
+\\[sh-repeat]   repeat loop
+\\[sh-select]   select loop
+\\[sh-until]    until loop
+\\[sh-while]    while loop
+
+For sh and rc shells indentation commands are:
+\\[sh-show-indent]     Show the variable controlling this line's indentation.
+\\[sh-set-indent]      Set then variable controlling this line's indentation.
+\\[sh-learn-line-indent]       Change the indentation variable so this line
+would indent to the way it currently is.
+\\[sh-learn-buffer-indent]  Set the indentation variables so the
+buffer indents as it currently is indented.
+
+
+\\[backward-delete-char-untabify]       Delete backward one position, even if it was a tab.
+\\[sh-newline-and-indent]       Delete unquoted space and indent new line same as this one.
+\\[sh-end-of-command]   Go to end of successive commands.
+\\[sh-beginning-of-command]     Go to beginning of successive commands.
+\\[sh-set-shell]        Set this buffer's shell, and maybe its magic number.
+\\[sh-execute-region]   Have optional header and region be executed in a subshell.
+
+\\[sh-maybe-here-document]      Without prefix, following an unquoted < inserts here document.
+{, (, [, ', \", `
+       Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
+
+If you generally program a shell different from your login shell you can
+set `sh-shell-file' accordingly.  If your shell's file name doesn't correctly
+indicate what shell it is use `sh-alias-alist' to translate.
+
+If your shell gives error messages with line numbers, you can use \\[executable-interpret]
+with your script for an edit-interpret-debug cycle."
+  (make-local-variable 'skeleton-end-hook)
+  (make-local-variable 'paragraph-start)
+  (make-local-variable 'paragraph-separate)
+  (make-local-variable 'comment-start)
+  (make-local-variable 'comment-start-skip)
+  (make-local-variable 'require-final-newline)
+  (make-local-variable 'sh-header-marker)
+  (make-local-variable 'sh-shell-file)
+  (make-local-variable 'sh-shell)
+  (make-local-variable 'skeleton-pair-alist)
+  (make-local-variable 'skeleton-pair-filter)
+  (make-local-variable 'comint-dynamic-complete-functions)
+  (make-local-variable 'comint-prompt-regexp)
+  (make-local-variable 'font-lock-defaults)
+  (make-local-variable 'skeleton-filter)
+  (make-local-variable 'skeleton-newline-indent-rigidly)
+  (make-local-variable 'sh-shell-variables)
+  (make-local-variable 'sh-shell-variables-initialized)
+  (make-local-variable 'imenu-generic-expression)
+  (make-local-variable 'sh-indent-supported-here)
+  (setq skeleton-end-hook (lambda ()
+                           (or (eolp) (newline) (indent-relative)))
+       paragraph-start (concat page-delimiter "\\|$")
+       paragraph-separate paragraph-start
+       comment-start "# "
+       comint-dynamic-complete-functions sh-dynamic-complete-functions
+       ;; we can't look if previous line ended with `\'
+       comint-prompt-regexp "^[ \t]*"
+       font-lock-defaults
+       `((sh-font-lock-keywords
+          sh-font-lock-keywords-1 sh-font-lock-keywords-2)
+         nil nil
+         ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil
+         (font-lock-syntactic-keywords . sh-font-lock-syntactic-keywords)
+         (font-lock-syntactic-face-function
+          . sh-font-lock-syntactic-face-function))
+       skeleton-pair-alist '((?` _ ?`))
+       skeleton-pair-filter 'sh-quoted-p
+       skeleton-further-elements '((< '(- (min sh-indentation
+                                               (current-column)))))
+       skeleton-filter 'sh-feature
+       skeleton-newline-indent-rigidly t
+       sh-indent-supported-here nil)
+  (set (make-local-variable 'parse-sexp-ignore-comments) t)
+  ;; Parse or insert magic number for exec, and set all variables depending
+  ;; on the shell thus determined.
+  (let ((interpreter
+        (save-excursion
+          (goto-char (point-min))
+          (cond ((looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")
+                 (match-string 2))
+                ((and buffer-file-name
+                      (string-match "\\.m?spec$" buffer-file-name))
+                 "rpm")))))
+    (sh-set-shell (or interpreter sh-shell-file) nil nil)))
+
+;;;###autoload
+(defalias 'shell-script-mode 'sh-mode)
+
+
+(defun sh-font-lock-keywords (&optional keywords)
+  "Function to get simple fontification based on `sh-font-lock-keywords'.
+This adds rules for comments and assignments."
+  (sh-feature sh-font-lock-keywords
+             (when (stringp (sh-feature sh-assignment-regexp))
+               (lambda (list)
+                 `((,(sh-feature sh-assignment-regexp)
+                    1 font-lock-variable-name-face)
+                   ,@keywords
+                   ,@list)))))
+
+(defun sh-font-lock-keywords-1 (&optional builtins)
+  "Function to get better fontification including keywords."
+  (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\("
+                         (regexp-opt (sh-feature sh-leading-keywords) t)
+                         "[ \t]+\\)?"
+                         (regexp-opt (append (sh-feature sh-leading-keywords)
+                                             (sh-feature sh-other-keywords))
+                                     t))))
+    (sh-font-lock-keywords
+     `(,@(if builtins
+            `((,(concat keywords "[ \t]+\\)?"
+                        (regexp-opt (sh-feature sh-builtins) t)
+                        "\\>")
+               (2 font-lock-keyword-face nil t)
+               (6 font-lock-builtin-face))
+              ,@(sh-feature sh-font-lock-keywords-2)))
+        (,(concat keywords "\\)\\>")
+         2 font-lock-keyword-face)
+        ,@(sh-feature sh-font-lock-keywords-1)))))
+
+(defun sh-font-lock-keywords-2 ()
+  "Function to get better fontification including keywords and builtins."
+  (sh-font-lock-keywords-1 t))
+
+
+(defvar sh-regexp-for-done nil
+  "A buffer-local regexp to match opening keyword for done.")
+
+(defvar sh-kw-alist nil
+  "A buffer-local, since it is shell-type dependent, list of keywords.")
+
+;; ( key-word  first-on-this  on-prev-line )
+;; This is used to set `sh-kw-alist' which is a list of sublists each
+;; having 3 elements:
+;;   a keyword
+;;   a rule to check when the keyword appears on "this" line
+;;   a rule to check when the keyword appears on "the previous" line
+;; The keyword is usually a string and is the first word on a line.
+;; If this keyword appears on the line whose indentation is to be
+;; calculated, the rule in element 2 is called.  If this returns
+;; non-zero, the resulting point (which may be changed by the rule)
+;; is used as the default indentation.
+;; If it returned false or the keyword was not found in the table,
+;; then the keyword from the previous line is looked up and the rule
+;; in element 3 is called.  In this case, however,
+;; `sh-get-indent-info' does not stop but may keep going and test
+;; other keywords against rules in element 3.  This is because the
+;; preceding line could have, for example, an opening "if" and an
+;; opening "while" keyword and we need to add the indentation offsets
+;; for both.
+;;
+(defconst sh-kw
+  '((sh
+     ("if" nil sh-handle-prev-if)
+     ("elif" sh-handle-this-else sh-handle-prev-else)
+     ("else" sh-handle-this-else sh-handle-prev-else)
+     ("fi" sh-handle-this-fi sh-handle-prev-fi)
+     ("then" sh-handle-this-then sh-handle-prev-then)
+     ("(" nil sh-handle-prev-open)
+     ("{" nil sh-handle-prev-open)
+     ("[" nil sh-handle-prev-open)
+     ("}" sh-handle-this-close nil)
+     (")" sh-handle-this-close nil)
+     ("]" sh-handle-this-close nil)
+     ("case" nil sh-handle-prev-case)
+     ("esac" sh-handle-this-esac sh-handle-prev-esac)
+     (case-label nil sh-handle-after-case-label) ;; ???
+     (";;" nil sh-handle-prev-case-alt-end) ;; ???
+     ("done" sh-handle-this-done sh-handle-prev-done)
+     ("do" sh-handle-this-do sh-handle-prev-do))
+
+    ;; Note: we don't need specific stuff for bash and zsh shells;
+    ;; the regexp `sh-regexp-for-done' handles the extra keywords
+    ;; these shells use.
+    (rc
+     ("{" nil sh-handle-prev-open)
+     ("}" sh-handle-this-close nil)
+     ("case" sh-handle-this-rc-case sh-handle-prev-rc-case))))
+
+
+(defun sh-set-shell (shell &optional no-query-flag insert-flag)
+  "Set this buffer's shell to SHELL (a string).
+Makes this script executable via `executable-set-magic', and sets up the
+proper starting #!-line, if INSERT-FLAG is non-nil.
+Calls the value of `sh-set-shell-hook' if set."
+  (interactive (list (completing-read "Name or path of shell: "
+                                     interpreter-mode-alist
+                                     (lambda (x) (eq (cdr x) 'sh-mode)))
+                    (eq executable-query 'function)
+                    t))
+  (if (string-match "\\.exe\\'" shell)
+      (setq shell (substring shell 0 (match-beginning 0))))
+  (setq sh-shell (intern (file-name-nondirectory shell))
+       sh-shell (or (cdr (assq sh-shell sh-alias-alist))
+                    sh-shell))
+  (if insert-flag
+      (setq sh-shell-file
+           (executable-set-magic shell (sh-feature sh-shell-arg)
+                                 no-query-flag insert-flag)))
+  (setq require-final-newline (sh-feature sh-require-final-newline)
+;;;    local-abbrev-table (sh-feature sh-abbrevs)
+       comment-start-skip "#+[\t ]*"
+       mode-line-process (format "[%s]" sh-shell)
+       sh-shell-variables nil
+       sh-shell-variables-initialized nil
+       imenu-generic-expression (sh-feature sh-imenu-generic-expression)
+       imenu-case-fold-search nil)
+  (dolist (var (sh-feature sh-variables))
+    (sh-remember-variable var))
+  (make-local-variable 'indent-line-function)
+  (if (setq sh-indent-supported-here (sh-feature sh-indent-supported))
+      (progn
+       (message "Setting up indent for shell type %s" sh-shell)
+       (set (make-local-variable 'parse-sexp-lookup-properties) t)
+       (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
+       (let ((regexp (sh-feature sh-kws-for-done)))
+         (if regexp
+             (set (make-local-variable 'sh-regexp-for-done)
+                  (sh-mkword-regexpr (regexp-opt regexp t)))))
+       (message "setting up indent stuff")
+       ;; sh-mode has already made indent-line-function local
+       ;; but do it in case this is called before that.
+       (setq indent-line-function 'sh-indent-line)
+       (if sh-make-vars-local
+           (sh-make-vars-local))
+       (message "Indentation setup for shell type %s" sh-shell))
+    (message "No indentation for this shell type.")
+    (setq indent-line-function 'sh-basic-indent-line))
+  (run-hooks 'sh-set-shell-hook))
+
+
+
+(defun sh-feature (list &optional function)
+  "Index ALIST by the current shell.
+If ALIST isn't a list where every element is a cons, it is returned as is.
+Else indexing follows an inheritance logic which works in two ways:
+
+  - Fall back on successive ancestors (see `sh-ancestor-alist') as long as
+    the alist contains no value for the current shell.
+    The ultimate default is always `sh'.
+
+  - If the value thus looked up is a list starting with `eval' its `cdr' is
+    first evaluated.  If that is also a list and the first argument is a
+    symbol in ALIST it is not evaluated, but rather recursively looked up in
+    ALIST to allow the function called to define the value for one shell to be
+    derived from another shell.  While calling the function, is the car of the
+    alist element is the current shell.
+    The value thus determined is physically replaced into the alist.
+
+Optional FUNCTION is applied to the determined value and the result is cached
+in ALIST."
+  (or (if (consp list)
+         (let ((l list))
+           (while (and l (consp (car l)))
+             (setq l (cdr l)))
+           (if l list)))
+      (if function
+         (cdr (assoc (setq function (cons sh-shell function)) list)))
+      (let ((sh-shell sh-shell)
+           elt val)
+       (while (and sh-shell
+                   (not (setq elt (assq sh-shell list))))
+         (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist))))
+       ;; If the shell is not known, treat it as sh.
+       (unless elt
+         (setq elt (assq 'sh list)))
+       (if (and (consp (setq val (cdr elt)))
+                (eq (car val) 'eval))
+           (setcdr elt
+                   (setq val
+                         (eval (if (consp (setq val (cdr val)))
+                                   (let ((sh-shell (car (cdr val)))
+                                         function)
+                                     (if (assq sh-shell list)
+                                         (setcar (cdr val)
+                                                 (list 'quote
+                                                       (sh-feature list))))
+                                     val)
+                                 val)))))
+       (if function
+           (nconc list
+                  (list (cons function
+                              (setq sh-shell (car function)
+                                    val (funcall (cdr function) val))))))
+       val)))
+
+
+
+;; I commented this out because nobody calls it -- rms.
+;;(defun sh-abbrevs (ancestor &rest list)
+;;  "Iff it isn't, define the current shell as abbrev table and fill that.
+;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
+;;table or a list of (NAME1 EXPANSION1 ...).  In addition it will define abbrevs
+;;according to the remaining arguments NAMEi EXPANSIONi ...
+;;EXPANSION may be either a string or a skeleton command."
+;;  (or (if (boundp sh-shell)
+;;       (symbol-value sh-shell))
+;;      (progn
+;;     (if (listp ancestor)
+;;         (nconc list ancestor))
+;;     (define-abbrev-table sh-shell ())
+;;     (if (vectorp ancestor)
+;;         (mapatoms (lambda (atom)
+;;                     (or (eq atom 0)
+;;                         (define-abbrev (symbol-value sh-shell)
+;;                           (symbol-name atom)
+;;                           (symbol-value atom)
+;;                           (symbol-function atom))))
+;;                   ancestor))
+;;     (while list
+;;       (define-abbrev (symbol-value sh-shell)
+;;         (car list)
+;;         (if (stringp (car (cdr list)))
+;;             (car (cdr list))
+;;           "")
+;;         (if (symbolp (car (cdr list)))
+;;             (car (cdr list))))
+;;       (setq list (cdr (cdr list)))))
+;;      (symbol-value sh-shell)))
+
+
+(defun sh-append (ancestor &rest list)
+  "Return list composed of first argument (a list) physically appended to rest."
+  (nconc list ancestor))
+
+
+(defun sh-modify (skeleton &rest list)
+  "Modify a copy of SKELETON by replacing I1 with REPL1, I2 with REPL2 ..."
+  (setq skeleton (copy-sequence skeleton))
+  (while list
+    (setcar (or (nthcdr (car list) skeleton)
+               (error "Index %d out of bounds" (car list)))
+           (car (cdr list)))
+    (setq list (nthcdr 2 list)))
+  skeleton)
+
+
+(defun sh-basic-indent-line ()
+  "Indent a line for Sh mode (shell script mode).
+Indent as far as preceding non-empty line, then by steps of `sh-indentation'.
+Lines containing only comments are considered empty."
+  (interactive)
+  (let ((previous (save-excursion
+                   (while (and (progn (beginning-of-line)
+                                      (not (bobp)))
+                               (progn
+                                 (forward-line -1)
+                                 (back-to-indentation)
+                                 (or (eolp)
+                                     (eq (following-char) ?#)))))
+                   (current-column)))
+       current)
+    (save-excursion
+      (indent-to (if (eq this-command 'newline-and-indent)
+                    previous
+                  (if (< (current-column)
+                         (setq current (progn (back-to-indentation)
+                                              (current-column))))
+                      (if (eolp) previous 0)
+                    (delete-region (point)
+                                   (progn (beginning-of-line) (point)))
+                    (if (eolp)
+                        (max previous (* (1+ (/ current sh-indentation))
+                                         sh-indentation))
+                      (* (1+ (/ current sh-indentation)) sh-indentation))))))
+    (if (< (current-column) (current-indentation))
+       (skip-chars-forward " \t"))))
+
+
+(defun sh-execute-region (start end &optional flag)
+  "Pass optional header and region to a subshell for noninteractive execution.
+The working directory is that of the buffer, and only environment variables
+are already set which is why you can mark a header within the script.
+
+With a positive prefix ARG, instead of sending region, define header from
+beginning of buffer to point.  With a negative prefix ARG, instead of sending
+region, clear header."
+  (interactive "r\nP")
+  (if flag
+      (setq sh-header-marker (if (> (prefix-numeric-value flag) 0)
+                                (point-marker)))
+    (if sh-header-marker
+       (save-excursion
+         (let (buffer-undo-list)
+           (goto-char sh-header-marker)
+           (append-to-buffer (current-buffer) start end)
+           (shell-command-on-region (point-min)
+                                    (setq end (+ sh-header-marker
+                                                 (- end start)))
+                                    sh-shell-file)
+           (delete-region sh-header-marker end)))
+      (shell-command-on-region start end (concat sh-shell-file " -")))))
+
+
+(defun sh-remember-variable (var)
+  "Make VARIABLE available for future completing reads in this buffer."
+  (or (< (length var) sh-remember-variable-min)
+      (getenv var)
+      (assoc var sh-shell-variables)
+      (push (cons var var) sh-shell-variables))
+  var)
+
+
+
+(defun sh-quoted-p ()
+  "Is point preceded by an odd number of backslashes?"
+  (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2)))
+\f
+;; Indentation stuff.
+(defun sh-must-be-shell-mode ()
+  "Signal an error if not in Shell-script mode."
+  (unless (eq major-mode 'sh-mode)
+    (error "This buffer is not in Shell-script mode")))
+
+(defun sh-must-support-indent ()
+  "*Signal an error if the shell type for this buffer is not supported.
+Also, the buffer must be in Shell-script mode."
+  (sh-must-be-shell-mode)
+  (unless sh-indent-supported-here
+    (error "This buffer's shell type is not supported for this command")))
+
+(defun sh-make-vars-local ()
+  "Make the indentation variables local to this buffer.
+Normally they already are local.  This command is provided in case
+variable `sh-make-vars-local' has been set to nil.
+
+To revert all these variables to the global values, use
+command `sh-reset-indent-vars-to-global-values'."
+  (interactive)
+  (sh-must-be-shell-mode)
+  (mapcar 'make-local-variable sh-var-list)
+  (message "Indentation variable are now local."))
+
+(defun sh-reset-indent-vars-to-global-values ()
+  "Reset local indentation variables to the global values.
+Then, if variable `sh-make-vars-local' is non-nil, make them local."
+  (interactive)
+  (sh-must-be-shell-mode)
+  (mapcar 'kill-local-variable sh-var-list)
+  (if sh-make-vars-local
+      (mapcar 'make-local-variable sh-var-list)))
+
+
+;; Theoretically these are only needed in shell and derived modes.
+;; However, the routines which use them are only called in those modes.
+(defconst sh-special-keywords "then\\|do")
+
+(defun sh-help-string-for-variable (var)
+  "Construct a string for `sh-read-variable' when changing variable VAR ."
+  (let ((msg (documentation-property var 'variable-documentation))
+       (msg2 ""))
+    (unless (memq var '(sh-first-lines-indent sh-indent-comment))
+      (setq msg2
+           (format "\n
+You can enter a number (positive to increase indentation,
+negative to decrease indentation, zero for no change to indentation).
+
+Or, you can enter one of the following symbols which are relative to
+the value of variable `sh-basic-offset'
+which in this buffer is currently %s.
+
+\t%s."
+                   sh-basic-offset
+                   (mapconcat (lambda (x)
+                                (nth (1- (length x)) x))
+                              sh-symbol-list  "\n\t"))))
+    (concat
+     ;; The following shows the global not the local value!
+     ;; (format "Current value of %s is %s\n\n" var (symbol-value var))
+     msg msg2)))
+
+(defun sh-read-variable (var)
+  "Read a new value for indentation variable VAR."
+  (interactive "*variable? ") ;; to test
+  (let ((minibuffer-help-form `(sh-help-string-for-variable
+                               (quote ,var)))
+       val)
+    (setq val (read-from-minibuffer
+              (format "New value for %s (press %s for help): "
+                      var  (single-key-description help-char))
+              (format "%s" (symbol-value var))
+              nil t))
+    val))
+
+
+
+(defun sh-in-comment-or-string (start)
+  "Return non-nil if START is in a comment or string."
+  (save-excursion
+    (let ((state (syntax-ppss start)))
+      (or (nth 3 state) (nth 4 state)))))
+
+(defun sh-goto-matching-if ()
+  "Go to the matching if for a fi.
+This handles nested if..fi pairs."
+  (let ((found (sh-find-prev-matching "\\bif\\b" "\\bfi\\b" 1)))
+    (if found
+       (goto-char found))))
+
+
+;; Functions named sh-handle-this-XXX are called when the keyword on the
+;; line whose indentation is being handled contain XXX;
+;; those named sh-handle-prev-XXX are when XXX appears on the previous line.
+
+(defun sh-handle-prev-if ()
+  (list '(+ sh-indent-after-if)))
+
+(defun sh-handle-this-else ()
+  (if (sh-goto-matching-if)
+      ;; (list "aligned to if")
+      (list "aligned to if" '(+ sh-indent-for-else))
+    nil
+    ))
+
+(defun sh-handle-prev-else ()
+  (if (sh-goto-matching-if)
+      (list  '(+ sh-indent-after-if))
+    ))
+
+(defun sh-handle-this-fi ()
+  (if (sh-goto-matching-if)
+      (list "aligned to if" '(+ sh-indent-for-fi))
+    nil
+    ))
+
+(defun sh-handle-prev-fi ()
+  ;; Why do we have this rule?  Because we must go back to the if
+  ;; to get its indent.  We may continue back from there.
+  ;; We return nil because we don't have anything to add to result,
+  ;; the side affect of setting align-point is all that matters.
+  ;; we could return a comment (a string) but I can't think of a good one...
+  (sh-goto-matching-if)
+  nil)
+
+(defun sh-handle-this-then ()
+  (let ((p (sh-goto-matching-if)))
+    (if p
+       (list '(+ sh-indent-for-then))
+      )))
+
+(defun sh-handle-prev-then ()
+  (let ((p (sh-goto-matching-if)))
+    (if p
+       (list '(+ sh-indent-after-if))
+      )))
+
+(defun sh-handle-prev-open ()
+  (save-excursion
+    (let ((x (sh-prev-stmt)))
+      (if (and x
+              (progn
+                (goto-char x)
+                (or
+                 (looking-at "function\\b")
+                 (looking-at "\\s-*\\S-+\\s-*()")
+                 )))
+         (list '(+ sh-indent-after-function))
+       (list '(+ sh-indent-after-open)))
+      )))
+
+(defun sh-handle-this-close ()
+  (forward-char 1) ;; move over ")"
+  (if (sh-safe-forward-sexp -1)
+      (list "aligned to opening paren")))
+
+(defun sh-goto-matching-case ()
+  (let ((found (sh-find-prev-matching "\\bcase\\b" "\\besac\\b" 1)))
+    (if found (goto-char found))))
+
+(defun sh-handle-prev-case ()
+  ;; This is typically called when point is on same line as a case
+  ;; we shouldn't -- and can't find prev-case
+  (if (looking-at ".*\\<case\\>")
+      (list '(+ sh-indent-for-case-label))
+    (error "We don't seem to be on a line with a case"))) ;; debug
+
+(defun sh-handle-this-esac ()
+  (if (sh-goto-matching-case)
+      (list "aligned to matching case")))
+
+(defun sh-handle-prev-esac ()
+  (if (sh-goto-matching-case)
+      (list "matching case")))
+
+(defun sh-handle-after-case-label ()
+  (if (sh-goto-matching-case)
+      (list '(+ sh-indent-for-case-alt))))
+
+(defun sh-handle-prev-case-alt-end ()
+  (if (sh-goto-matching-case)
+      (list '(+ sh-indent-for-case-label))))
+
+(defun sh-safe-forward-sexp (&optional arg)
+  "Try and do a `forward-sexp', but do not error.
+Return new point if successful, nil if an error occurred."
+  (condition-case nil
+      (progn
+       (forward-sexp (or arg 1))
+       (point))        ;; return point if successful
+    (error
+     (sh-debug "oops!(1) %d" (point))
+     nil))) ;; return nil if fail
+
+(defun sh-goto-match-for-done ()
+  (let ((found (sh-find-prev-matching sh-regexp-for-done sh-re-done 1)))
+    (if found
+       (goto-char found))))
+
+(defun sh-handle-this-done ()
+  (if (sh-goto-match-for-done)
+      (list  "aligned to do stmt"  '(+ sh-indent-for-done))))
+
+(defun sh-handle-prev-done ()
+  (if (sh-goto-match-for-done)
+      (list "previous done")))
+
+(defun sh-handle-this-do ()
+  (if (sh-goto-match-for-done)
+      (list '(+ sh-indent-for-do))))
+
+(defun sh-handle-prev-do ()
+  (cond
+   ((save-restriction
+      (narrow-to-region
+       (point)
+       (save-excursion
+        (beginning-of-line)
+        (point)))
+      (sh-goto-match-for-done))
+    (sh-debug "match for done found on THIS line")
+    (list '(+ sh-indent-after-loop-construct)))
+   ((sh-goto-match-for-done)
+    (sh-debug "match for done found on PREV line")
+    (list '(+ sh-indent-after-do)))
+   (t
+    (message "match for done NOT found")
+    nil)))
+
+;; for rc:
+(defun sh-find-prev-switch ()
+  "Find the line for the switch keyword matching this line's case keyword."
+  (re-search-backward "\\<switch\\>" nil t))
+
+(defun sh-handle-this-rc-case ()
+  (if (sh-find-prev-switch)
+      (list  '(+ sh-indent-after-switch))
+    ;; (list  '(+ sh-indent-for-case-label))
+    nil))
+
+(defun sh-handle-prev-rc-case ()
+  (list '(+ sh-indent-after-case)))
+
+(defun sh-check-rule (n thing)
+  (let ((rule (nth n (assoc thing sh-kw-alist)))
+       (val nil))
+    (if rule
+       (progn
+         (setq val (funcall rule))
+         (sh-debug "rule (%d) for %s at %d is %s\n-> returned %s"
+                   n thing (point) rule val)))
+    val))
+
+
+(defun sh-get-indent-info ()
+  "Return indent-info for this line.
+This is a list.  nil means the line is to be left as is.
+Otherwise it contains one or more of the following sublists:
+\(t NUMBER\)   NUMBER is the base location in the buffer that indentation is
+            relative to.  If present, this is always the first of the
+            sublists.  The indentation of the line in question is
+            derived from the indentation of this point, possibly
+            modified by subsequent sublists.
+\(+ VAR\)
+\(- VAR\)      Get the value of variable VAR and add to or subtract from
+            the indentation calculated so far.
+\(= VAR\)           Get the value of variable VAR and *replace* the
+            indentation with its value.  This only occurs for
+            special variables such as `sh-indent-comment'.
+STRING      This is ignored for the purposes of calculating
+            indentation, it is printed in certain cases to help show
+            what the indentation is based on."
+  ;; See comments before `sh-kw'.
+  (save-excursion
+    (let ((prev-kw nil)
+         (prev-stmt nil)
+         (have-result nil)
+         depth-bol depth-eol
+         this-kw
+         (state nil)
+         state-bol
+         (depth-prev-bol nil)
+         start
+         func val
+         (result nil)
+         prev-lines-indent
+         (prev-list nil)
+         (this-list nil)
+         (align-point nil)
+         prev-line-end x)
+      (beginning-of-line)
+      ;; Note: setting result to t means we are done and will return nil.
+      ;;(This function never returns just t.)
+      (cond
+       ((or (and (boundp 'font-lock-string-face)
+                (eq (get-text-property (point) 'face) font-lock-string-face))
+           (eq (get-text-property (point) 'face) sh-heredoc-face))
+       (setq result t)
+       (setq have-result t))
+       ((looking-at "\\s-*#")          ; was (equal this-kw "#")
+       (if (bobp)
+           (setq result t) ;; return nil if 1st line!
+         (setq result (list '(= sh-indent-comment)))
+         ;; we still need to get previous line in case
+         ;; sh-indent-comment is t (indent as normal)
+         (setq align-point (sh-prev-line nil))
+         (setq have-result nil)
+         ))
+       ) ;; cond
+      
+      (unless have-result
+       ;; Continuation lines are handled specially
+       (if (sh-this-is-a-continuation)
+           (progn
+             ;; We assume the line being continued is already
+             ;; properly indented...
+             ;; (setq prev-line-end (sh-prev-line))
+             (setq align-point (sh-prev-line nil))
+             (setq result (list '(+ sh-indent-for-continuation)))
+             (setq have-result t))
+         (beginning-of-line)
+         (skip-chars-forward " \t")
+         (setq this-kw (sh-get-kw)))
+
+        ;; Handle "this" keyword:  first word on the line we're
+       ;; calculating indentation info for.
+       (if this-kw
+           (if (setq val (sh-check-rule 1 this-kw))
+               (progn
+                 (setq align-point (point))
+                 (sh-debug
+                  "this - setting align-point to %d" align-point)
+                 (setq result (append result val))
+                 (setq have-result t)
+                 ;; set prev-line to continue processing remainder
+                 ;; of this line as a previous line
+                 (setq prev-line-end (point))
+                 ))))
+
+      (unless have-result
+       (setq prev-line-end (sh-prev-line 'end)))
+
+      (if prev-line-end
+         (save-excursion
+           ;; We start off at beginning of this line.
+           ;; Scan previous statements while this is <=
+           ;; start of previous line.
+           (setq start (point)) ;; for debug only
+           (goto-char prev-line-end)
+           (setq x t)
+           (while (and x (setq x  (sh-prev-thing)))
+             (sh-debug "at %d x is: %s  result is: %s" (point) x result)
+             (cond
+              ((and (equal x ")")
+                    (equal (get-text-property (1- (point)) 'syntax-table)
+                           sh-st-punc))
+               (sh-debug "Case label) here")
+               (setq x 'case-label)
+               (if (setq val (sh-check-rule 2 x))
+                   (progn
+                     (setq result (append result val))
+                     (setq align-point (point))))
+               (forward-char -1)
+               (skip-chars-forward "[a-z0-9]*?")
+               )
+              ((string-match "[])}]" x)
+               (setq x (sh-safe-forward-sexp -1))
+               (if x
+                   (progn
+                     (setq align-point (point))
+                     (setq result (append result
+                                          (list "aligned to opening paren")))
+                     )))
+              ((string-match "[[({]" x)
+               (sh-debug "Checking special thing: %s" x)
+               (if (setq val (sh-check-rule 2 x))
+                   (setq result (append result val)))
+               (forward-char -1)
+               (setq align-point (point)))
+              ((string-match "[\"'`]" x)
+               (sh-debug "Skipping back for %s" x)
+               ;; this was oops-2
+               (setq x (sh-safe-forward-sexp -1)))
+              ((stringp x)
+               (sh-debug "Checking string %s at %s" x (point))
+               (if (setq val (sh-check-rule 2 x))
+                   ;; (or (eq t (car val))
+                   ;; (eq t (car (car val))))
+                   (setq result (append result val)))
+               ;; not sure about this test Wed Jan 27 23:48:35 1999
+               (setq align-point (point))
+               (unless (bolp)
+                 (forward-char -1)))
+              (t
+               (error "Don't know what to do with %s" x))
+              )
+             ) ;; while
+           (sh-debug "result is %s" result)
+           )
+       (sh-debug "No prev line!")
+       (sh-debug "result: %s  align-point: %s" result align-point)
+       )
+      
+      (if align-point
+         ;; was: (setq result (append result (list (list t align-point))))
+         (setq result (append  (list (list t align-point)) result))
+       )
+      (sh-debug "result is now: %s" result)
+       
+      (or result
+         (if prev-line-end
+             (setq result (list (list t prev-line-end)))
+           (setq result (list (list '= 'sh-first-lines-indent)))
+           ))
+       
+      (if (eq result t)
+         (setq result nil))
+      (sh-debug  "result is: %s" result)
+      result
+      )        ;; let
+    ))
+
+
+(defun sh-get-indent-var-for-line (&optional info)
+  "Return the variable controlling indentation for this line.
+If there is not [just] one such variable, return a string
+indicating the problem.
+If INFO is supplied it is used, else it is calculated."
+  (let ((var nil)
+       (result nil)
+       (reason nil)
+       sym elt)
+    (or info
+       (setq info (sh-get-indent-info)))
+    (if (null info)
+       (setq result "this line to be left as is")
+      (while (and info (null result))
+       (setq elt (car info))
+       (cond
+        ((stringp elt)
+         (setq reason elt)
+         )
+        ((not (listp elt))
+         (error "sh-get-indent-var-for-line invalid elt: %s" elt))
+        ;; so it is a list
+        ((eq t (car elt))
+         ) ;; nothing
+        ((symbolp  (setq sym (nth 1 elt)))
+         ;; A bit of a kludge - when we see the sh-indent-comment
+         ;; ignore other variables.  Otherwise it is tricky to
+         ;; "learn" the comment indentation.
+         (if (eq var 'sh-indent-comment)
+             (setq result var)
+           (if var
+               (setq result
+                     "this line is controlled by more than 1 variable.")
+             (setq var sym))))
+        (t
+         (error "sh-get-indent-var-for-line invalid list elt: %s" elt)))
+       (setq info (cdr info))
+       ))
+    (or result
+       (setq result var))
+    (or result
+       (setq result reason))
+    (if (null result)
+       ;; e.g. just had (t POS)
+       (setq result "line has default indentation"))
+    result))
+
+
+
+;; Finding the previous line isn't trivial.
+;; We must *always* go back one more and see if that is a continuation
+;; line -- it is the PREVIOUS line which is continued, not the one
+;; we are going to!
+;; Also, we want to treat a whole "here document" as one big line,
+;; because we may want to a align to the beginning of it.
+;;
+;; What we do:
+;; - go back to previous non-empty line
+;; - if this is in a here-document, go to the beginning of it
+;; - while previous line is continued, go back one line
+(defun sh-prev-line (&optional end)
+  "Back to end of previous non-comment non-empty line.
+Go to beginning of logical line unless END is non-nil, in which case
+we go to the end of the previous line and do not check for continuations."
+  (sh-must-be-shell-mode)
+  (save-excursion
+    (beginning-of-line)
+    (forward-comment (- (point-max)))
+    (unless end (beginning-of-line))
+    (when (and (not (bobp))
+              (equal (get-text-property (1- (point)) 'face)
+                     sh-heredoc-face))
+      (let ((p1 (previous-single-property-change (1- (point)) 'face)))
+       (when p1
+         (goto-char p1)
+         (if end
+             (end-of-line)
+           (beginning-of-line)))))
+    (unless end
+      ;; we must check previous lines to see if they are continuation lines
+      ;; if so, we must return position of first of them
+      (while (and (sh-this-is-a-continuation)
+                 (>= 0 (forward-line -1))))
+      (beginning-of-line)
+      (skip-chars-forward " \t"))
+    (point)))
+
+
+(defun sh-prev-stmt ()
+  "Return the address of the previous stmt or nil."
+  ;; This is used when we are trying to find a matching keyword.
+  ;; Searching backward for the keyword would certainly be quicker, but
+  ;; it is hard to remove "false matches" -- such as if the keyword
+  ;; appears in a string or quote.  This way is slower, but (I think) safer.
+  (interactive)
+  (save-excursion
+    (let ((going t)
+         (start (point))
+         (found nil)
+         (prev nil))
+      (skip-chars-backward " \t;|&({[")
+      (while (and (not found)
+                 (not (bobp))
+                 going)
+       ;; Do a backward-sexp if possible, else backup bit by bit...
+       (if (sh-safe-forward-sexp -1)
+           (progn
+             (if (looking-at sh-special-keywords)
+                 (progn
+                   (setq found prev))
+               (setq prev (point))
+               ))
+         ;; backward-sexp failed
+         (if (zerop (skip-chars-backward " \t()[\]{};`'"))
+             (forward-char -1))
+         (if (bolp)
+             (let ((back (sh-prev-line nil)))
+               (if back
+                   (goto-char back)
+                 (setq going nil)))))
+       (unless found
+         (skip-chars-backward " \t")
+         (if (or (and (bolp) (not (sh-this-is-a-continuation)))
+                 (eq (char-before) ?\;)
+                 (looking-at "\\s-*[|&]"))
+             (setq found (point)))))
+      (if found
+         (goto-char found))
+      (if found
+         (progn
+           (skip-chars-forward " \t|&({[")
+           (setq found (point))))
+      (if (>= (point) start)
+         (progn
+           (debug "We didn't move!")
+           (setq found nil))
+       (or found
+           (sh-debug "Did not find prev stmt.")))
+      found)))
+
+
+(defun sh-get-word ()
+  "Get a shell word skipping whitespace from point."
+  (interactive)
+  (skip-chars-forward "\t ")
+  (let ((start (point)))
+    (while
+       (if (looking-at "[\"'`]")
+           (sh-safe-forward-sexp)
+         ;; (> (skip-chars-forward "^ \t\n\"'`") 0)
+         (> (skip-chars-forward "-_a-zA-Z\$0-9") 0)
+         ))
+    (buffer-substring start (point))
+    ))
+
+(defun sh-prev-thing ()
+  "Return the previous thing this logical line."
+  ;; This is called when `sh-get-indent-info' is working backwards on
+  ;; the previous line(s) finding what keywords may be relevant for
+  ;; indenting.  It moves over sexps if possible, and will stop
+  ;; on a ; and at the beginning of a line if it is not a continuation
+  ;; line.
+  ;;
+  ;; Added a kludge for ";;"
+  ;; Possible return values:
+  ;;  nil  -  nothing
+  ;; a string - possibly a keyword
+  ;; 
+  (if (bolp)
+      nil
+    (let ((going t)
+         c n
+         min-point
+         (start (point))
+         (found nil))
+      (save-restriction
+       (narrow-to-region
+       (if (sh-this-is-a-continuation)
+           (setq min-point (sh-prev-line nil))
+         (save-excursion
+           (beginning-of-line)
+           (setq min-point (point))))
+       (point))
+       (skip-chars-backward " \t;")
+       (unless (looking-at "\\s-*;;")
+       (skip-chars-backward "^)}];\"'`({[")
+       (setq c (char-before))))
+      (sh-debug "stopping at %d c is %s  start=%d min-point=%d"
+               (point) c start min-point)
+      (if (< (point) min-point)
+         (error "point %d < min-point %d" (point) min-point))
+      (cond
+       ((looking-at "\\s-*;;")
+       ;; (message "Found ;; !")
+       ";;")
+       ((or (eq c ?\n)
+           (eq c nil)
+           (eq c ?\;))
+       (save-excursion
+        ;; skip forward over white space newline and \ at eol
+        (skip-chars-forward " \t\n\\\\")
+        (sh-debug "Now at %d   start=%d" (point) start)
+        (if (>= (point) start)
+            (progn
+              (sh-debug "point: %d >= start: %d" (point) start)
+              nil)
+          (sh-get-word))
+        ))
+       (t
+       ;; c    -- return a string
+       (char-to-string c)
+       ))
+      )))
+
+
+(defun sh-this-is-a-continuation ()
+  "Return non-nil if current line is a continuation of previous line."
+  (save-excursion
+    (and (zerop (forward-line -1))
+        (looking-at ".*\\\\$")
+        (not (nth 4 (parse-partial-sexp (match-beginning 0) (match-end 0)
+                                        nil nil nil t))))))
+
+(defun sh-get-kw (&optional where and-move)
+  "Return first word of line from WHERE.
+If AND-MOVE is non-nil then move to end of word."
+  (let ((start (point)))
+    (if where
+       (goto-char where))
+    (prog1
+       (buffer-substring (point)
+                         (progn (skip-chars-forward "^ \t\n;")(point)))
+      (unless and-move
+       (goto-char start)))))
+
+(defun sh-find-prev-matching (open close &optional depth)
+  "Find a matching token for a set of opening and closing keywords.
+This takes into account that there may be nested open..close pairings.
+OPEN and CLOSE are regexps denoting the tokens to be matched.
+Optional parameter DEPTH (usually 1) says how many to look for."
+  (let ((parse-sexp-ignore-comments t)
+       prev)
+    (setq depth (or depth 1))
+    (save-excursion
+      (condition-case nil
+         (while (and
+                 (/= 0  depth)
+                 (not (bobp))
+                 (setq prev (sh-prev-stmt)))
+           (goto-char prev)
+           (save-excursion
+             (if (looking-at "\\\\\n")
+                 (progn
+                   (forward-char 2)
+                   (skip-chars-forward " \t")))
+             (cond
+              ((looking-at open)
+               (setq depth (1- depth))
+               (sh-debug "found open at %d - depth = %d" (point) depth))
+              ((looking-at close)
+               (setq depth (1+ depth))
+               (sh-debug "found close - depth = %d" depth))
+              (t
+               ))))
+       (error nil))
+      (if (eq depth 0)
+         prev ;; (point)
+       nil)
+      )))
+
+
+(defun sh-var-value (var &optional ignore-error)
+  "Return the value of variable VAR, interpreting symbols.
+It can also return t or nil.
+If an illegal value is found, throw an error unless Optional argument
+IGNORE-ERROR is non-nil."
+  (let ((val (symbol-value var)))
+    (cond
+     ((numberp val)
+      val)
+     ((eq val t)
+      val)
+     ((null val)
+      val)
+     ((eq val '+)
+      sh-basic-offset)
+     ((eq val '-)
+      (- sh-basic-offset))
+     ((eq val '++)
+      (* 2 sh-basic-offset))
+     ((eq val '--)
+      (* 2 (- sh-basic-offset)))
+     ((eq val '*)
+      (/ sh-basic-offset 2))
+     ((eq val '/)
+      (/ (- sh-basic-offset) 2))
+     (t
+      (if ignore-error
+      (progn
+       (message "Don't know how to handle %s's value of %s" var val)
+       0)
+      (error "Don't know how to handle %s's value of %s" var val))
+      ))))
+
+(defun sh-set-var-value (var value &optional no-symbol)
+  "Set variable VAR to VALUE.
+Unless optional argument NO-SYMBOL is non-nil, then if VALUE is
+can be represented by a symbol then do so."
+  (cond
+   (no-symbol
+    (set var value))
+   ((= value sh-basic-offset)
+    (set var '+))
+   ((= value (- sh-basic-offset))
+    (set var '-))
+   ((eq value (* 2 sh-basic-offset))
+    (set var  '++))
+   ((eq value (* 2 (- sh-basic-offset)))
+    (set var  '--))
+   ((eq value (/ sh-basic-offset 2))
+    (set var  '*))
+   ((eq value (/ (- sh-basic-offset) 2))
+    (set var  '/))
+   (t
+    (set var value)))
+  )
+
+
+(defun sh-calculate-indent (&optional info)
+  "Return the indentation for the current line.
+If INFO is supplied it is used, else it is calculated from current line."
+  (let ((ofs 0)
+       (base-value 0)
+       elt a b var val)
+    (or info
+       (setq info (sh-get-indent-info)))
+    (when info
+      (while info
+       (sh-debug "info: %s  ofs=%s" info ofs)
+       (setq elt (car info))
+       (cond
+        ((stringp elt)) ;; do nothing?
+        ((listp elt)
+         (setq a (car (car info)))
+         (setq b (nth 1 (car info)))
+         (cond
+          ((eq a t)
+           (save-excursion
+             (goto-char b)
+             (setq val (current-indentation)))
+           (setq base-value val))
+          ((symbolp b)
+           (setq val (sh-var-value b))
+           (cond
+            ((eq a '=)
+             (cond
+              ((null val)
+               ;; no indentation
+               ;; set info to nil so  we stop immediately
+               (setq base-value nil  ofs nil  info nil))
+              ((eq val t) (setq ofs 0)) ;; indent as normal line
+              (t
+               ;; The following assume the (t POS) come first!
+               (setq ofs val  base-value 0)
+               (setq info nil))))      ;; ? stop now
+            ((eq a '+) (setq ofs (+ ofs val)))
+            ((eq a '-) (setq ofs (- ofs val)))
+            (t
+             (error "sh-calculate-indent invalid a a=%s b=%s" a b))))
+          (t
+           (error "sh-calculate-indent invalid elt: a=%s b=%s" a b))))
+        (t
+         (error "sh-calculate-indent invalid elt %s" elt)))
+       (sh-debug "a=%s b=%s val=%s base-value=%s ofs=%s"
+                 a b val base-value ofs)
+       (setq info (cdr info)))
+      ;; return value:
+      (sh-debug "at end:  base-value: %s    ofs: %s" base-value ofs)
+
+      (cond
+       ((or (null base-value)(null ofs))
+       nil)
+       ((and (numberp base-value)(numberp ofs))
+       (sh-debug "base (%d) + ofs (%d) = %d"
+                 base-value ofs (+ base-value ofs))
+       (+ base-value ofs)) ;; return value
+       (t
+       (error "sh-calculate-indent:  Help.  base-value=%s ofs=%s"
+              base-value ofs)
+       nil)))))
+
+
+(defun sh-indent-line ()
+  "Indent the current line."
+  (interactive)
+  (sh-must-be-shell-mode)
+  (let ((indent (sh-calculate-indent)) shift-amt beg end
+       (pos (- (point-max) (point))))
+    (when indent
+      (beginning-of-line)
+      (setq beg (point))
+      (skip-chars-forward " \t")
+      (setq shift-amt (- indent (current-column)))
+      (unless (zerop shift-amt)
+       (delete-region beg (point))
+       (indent-to indent))
+      ;; If initial point was within line's indentation,
+      ;; position after the indentation.  Else stay at same point in text.
+      (if (> (- (point-max) pos) (point))
+         (goto-char (- (point-max) pos))))))
+
+
+(defun sh-blink (blinkpos &optional msg)
+  "Move cursor momentarily to BLINKPOS and display MSG."
+  ;; We can get here without it being a number on first line
+  (if (numberp blinkpos)
+      (save-excursion
+       (goto-char blinkpos)
+       (message msg)
+       (sit-for blink-matching-delay))
+    (message msg)))
+
+(defun sh-show-indent (arg)
+  "Show the how the currently line would be indented.
+This tells you which variable, if any, controls the indentation of
+this line.
+If optional arg ARG is non-null (called interactively with a prefix),
+a pop up window describes this variable.
+If variable `sh-blink' is non-nil then momentarily go to the line
+we are indenting relative to, if applicable."
+  (interactive "P")
+  (sh-must-support-indent)
+  (let* ((info (sh-get-indent-info))
+        (var (sh-get-indent-var-for-line info))
+        (curr-indent (current-indentation))
+        val msg)
+    (if (stringp var)
+       (message (setq msg var))
+      (setq val (sh-calculate-indent info))
+
+      (if (eq curr-indent val)
+         (setq msg (format "%s is %s" var (symbol-value var)))
+       (setq msg
+             (if val
+                 (format "%s (%s) would change indent from %d to: %d"
+                         var (symbol-value var) curr-indent val)
+               (format "%s (%s) would leave line as is"
+                       var (symbol-value var)))
+             ))
+      (if (and arg var)
+         (describe-variable var)))
+    (if sh-blink
+       (let ((info (sh-get-indent-info)))
+         (if (and info (listp (car info))
+                  (eq (car (car info)) t))
+             (sh-blink (nth 1 (car info))  msg)
+           (message msg)))
+      (message msg))
+    ))
+
+(defun sh-set-indent ()
+  "Set the indentation for the current line.
+If the current line is controlled by an indentation variable, prompt
+for a new value for it."
+  (interactive)
+  (sh-must-support-indent)
+  (let* ((info (sh-get-indent-info))
+        (var (sh-get-indent-var-for-line info))
+        val val0 new-val old-val indent-val)
+    (if (stringp var)
+       (message (format "Cannot set indent - %s" var))
+      (setq old-val (symbol-value var))
+      (setq val (sh-read-variable var))
+      (condition-case nil
+         (progn
+           (set var val)
+           (setq indent-val (sh-calculate-indent info))
+           (if indent-val
+               (message "Variable: %s  Value: %s  would indent to: %d"
+                        var (symbol-value var) indent-val)
+             (message "Variable: %s  Value: %s  would leave line as is."
+                      var (symbol-value var)))
+           ;; I'm not sure about this, indenting it now?
+           ;; No.  Because it would give the impression that an undo would
+           ;; restore thing, but the value has been altered.
+           ;; (sh-indent-line)
+           )
+       (error
+        (set var old-val)
+        (message "Bad value for %s, restoring to previous value %s"
+                 var old-val)
+        (sit-for 1)
+        nil))
+      )))
+
+
+(defun sh-learn-line-indent (arg)
+  "Learn how to indent a line as it currently is indented.
+
+If there is an indentation variable which controls this line's indentation,
+then set it to a value which would indent the line the way it
+presently is.
+
+If the value can be represented by one of the symbols then do so
+unless optional argument ARG (the prefix when interactive) is non-nil."
+  (interactive "*P")
+  (sh-must-support-indent)
+  ;; I'm not sure if we show allow learning on an empty line.
+  ;; Though it might occasionally be useful I think it usually
+  ;; would just be confusing.
+  (if (save-excursion
+       (beginning-of-line)
+       (looking-at "\\s-*$"))
+      (message "sh-learn-line-indent ignores empty lines.")
+    (let* ((info (sh-get-indent-info))
+          (var (sh-get-indent-var-for-line info))
+          ival sval diff new-val
+          (no-symbol arg)
+          (curr-indent (current-indentation)))
+      (cond
+       ((stringp var)
+       (message (format "Cannot learn line - %s" var)))
+       ((eq var 'sh-indent-comment)
+       ;; This is arbitrary...
+       ;; - if curr-indent is 0, set to curr-indent
+       ;; - else if it has the indentation of a "normal" line,
+       ;;   then set to t
+       ;; - else set to curr-indent.
+       (setq sh-indent-comment
+             (if (= curr-indent 0)
+                 0
+               (let* ((sh-indent-comment t)
+                      (val2 (sh-calculate-indent info)))
+                 (if (= val2 curr-indent)
+                     t
+                   curr-indent))))
+       (message "%s set to %s" var (symbol-value var))
+       )
+       ((numberp (setq sval (sh-var-value var)))
+       (setq ival (sh-calculate-indent info))
+       (setq diff (- curr-indent ival))
+      
+       (sh-debug "curr-indent: %d   ival: %d  diff: %d  var:%s  sval %s"
+                 curr-indent ival diff  var sval)
+       (setq new-val (+ sval diff))
+;;;      I commented out this because someone might want to replace
+;;;      a value of `+' with the current value of sh-basic-offset
+;;;      or vice-versa.
+;;;      (if (= 0 diff)
+;;;          (message "No change needed!")
+       (sh-set-var-value var new-val no-symbol)
+       (message "%s set to %s" var (symbol-value var))
+       )
+       (t
+       (debug)
+       (message "Cannot change %s" var))))))
+
+
+
+(defun sh-mark-init (buffer)
+  "Initialize a BUFFER to be used by `sh-mark-line'."
+  (let ((main-buffer (current-buffer)))
+    (save-excursion
+      (set-buffer (get-buffer-create buffer))
+      (erase-buffer)
+      (occur-mode)
+      (setq occur-buffer main-buffer)
+      )))
+
+
+(defun sh-mark-line (message point buffer &optional add-linenum occur-point)
+  "Insert MESSAGE referring to location POINT in current buffer into BUFFER.
+Buffer BUFFER is in `occur-mode'.
+If ADD-LINENUM is non-nil the message is preceded by the line number.
+If OCCUR-POINT is non-nil then the line is marked as a new occurrence
+so that `occur-next' and `occur-prev' will work."
+  (let ((m1 (make-marker))
+       (main-buffer (current-buffer))
+       start
+       (line ""))
+    (when point
+      (set-marker m1 point (current-buffer))
+      (if add-linenum
+         (setq line (format "%d: " (1+ (count-lines 1 point))))))
+    (save-excursion
+      (if (get-buffer buffer)
+         (set-buffer (get-buffer buffer))
+       (set-buffer (get-buffer-create buffer))
+       (occur-mode)
+       (setq occur-buffer main-buffer)
+       )
+      (goto-char (point-max))
+      (setq start (point))
+      (insert line)
+      (if occur-point
+         (setq occur-point (point)))
+      (insert message)
+      (if point
+         (add-text-properties
+          start (point)
+          '(mouse-face highlight
+            help-echo "mouse-2: go to the line where I learned this")))
+      (insert "\n")
+      (if point
+         (progn
+           (put-text-property start (point) 'occur m1)
+           (if occur-point
+               (put-text-property occur-point (1+ occur-point)
+                                  'occur-point t))
+           ))
+      )))
+
+
+
+;; Is this really worth having?
+(defvar sh-learned-buffer-hook nil
+  "*An abnormal hook, called with an alist of learned variables.")
+;; Example of how to use sh-learned-buffer-hook
+;; 
+;; (defun what-i-learned (list)
+;;   (let ((p list))
+;;     (save-excursion
+;;       (set-buffer "*scratch*")
+;;       (goto-char (point-max))
+;;       (insert "(setq\n")
+;;       (while p
+;;     (insert (format "  %s %s \n"
+;;                     (nth 0 (car p)) (nth 1 (car p))))
+;;     (setq p (cdr p)))
+;;       (insert ")\n")
+;;       )))
+;; 
+;; (add-hook 'sh-learned-buffer-hook 'what-i-learned)
+
+
+;; Originally this was sh-learn-region-indent (beg end)
+;; However, in practice this was awkward so I changed it to
+;; use the whole buffer.  Use narrowing if needbe.
+(defun sh-learn-buffer-indent (&optional arg)
+  "Learn how to indent the buffer the way it currently is.
+
+Output in buffer \"*indent*\" shows any lines which have conflicting
+values of a variable, and the final value of all variables learned.
+This buffer is popped to automatically if there are any discrepancies.
+
+If no prefix ARG is given, then variables are set to numbers.
+If a prefix arg is given, then variables are set to symbols when
+applicable -- e.g. to symbol `+' if the value is that of the
+basic indent.
+If a positive numerical prefix is given, then  `sh-basic-offset'
+is set to the prefix's numerical value.
+Otherwise, sh-basic-offset may or may not be changed, according
+to the value of variable `sh-learn-basic-offset'.
+
+Abnormal hook `sh-learned-buffer-hook' if non-nil is called when the
+function completes.  The function is abnormal because it is called
+with an alist of variables learned.  This feature may be changed or
+removed in the future.
+
+This command can often take a long time to run."
+  (interactive "P")
+  (sh-must-support-indent)
+  (save-excursion
+    (goto-char (point-min))
+    (let ((learned-var-list nil)
+         (out-buffer "*indent*")
+         (num-diffs 0)
+         last-pos
+         previous-set-info
+         (max 17)
+         vec
+         msg
+         (comment-col nil) ;; number if all same, t if seen diff values
+         (comments-always-default t) ;; nil if we see one not default
+         initial-msg
+         (specified-basic-offset (and arg (numberp arg)
+                                      (> arg 0)))
+         (linenum 0)
+         suggested)
+      (setq vec (make-vector max 0))
+      (sh-mark-init out-buffer)
+
+      (if specified-basic-offset
+         (progn
+           (setq sh-basic-offset arg)
+           (setq initial-msg
+                 (format "Using specified sh-basic-offset of %d"
+                         sh-basic-offset)))
+       (setq initial-msg
+             (format "Initial value of sh-basic-offset: %s"
+                     sh-basic-offset)))
+
+      (while (< (point) (point-max))
+       (setq linenum (1+ linenum))
+       ;; (if (zerop (% linenum 10))
+       (message "line %d" linenum)
+       ;; )
+       (unless (looking-at "\\s-*$") ;; ignore empty lines!
+         (let* ((sh-indent-comment t) ;; info must return default indent
+                (info (sh-get-indent-info))
+                (var (sh-get-indent-var-for-line info))
+                sval ival diff new-val
+                (curr-indent (current-indentation)))
+           (cond
+            ((null var)
+             nil)
+            ((stringp var)
+             nil)
+            ((numberp (setq sval (sh-var-value var 'no-error)))
+             ;; the numberp excludes comments since sval will be t.
+             (setq ival (sh-calculate-indent))
+             (setq diff (- curr-indent ival))
+             (setq new-val (+ sval diff))
+             (sh-set-var-value var new-val 'no-symbol)
+             (unless (looking-at "\\s-*#") ;; don't learn from comments
+               (if (setq previous-set-info (assoc var learned-var-list))
+                   (progn
+                     ;; it was already there, is it same value ?
+                     (unless (eq (symbol-value var)
+                                 (nth 1 previous-set-info))
+                       (sh-mark-line
+                        (format "Variable %s was set to %s"
+                                var (symbol-value var))
+                        (point) out-buffer t t)
+                       (sh-mark-line
+                        (format "  but was previously set to %s"
+                                (nth 1 previous-set-info))
+                        (nth 2 previous-set-info) out-buffer t)
+                       (setq num-diffs (1+ num-diffs))
+                       ;; (delete previous-set-info  learned-var-list)
+                       (setcdr previous-set-info
+                               (list (symbol-value var) (point)))
+                       )
+                     )
+                 (setq learned-var-list
+                       (append (list (list var (symbol-value var)
+                                           (point)))
+                               learned-var-list)))
+               (if (numberp new-val)
+                   (progn
+                     (sh-debug
+                      "This line's indent value: %d"  new-val)
+                     (if (< new-val 0)
+                         (setq new-val (- new-val)))
+                     (if (< new-val max)
+                         (aset vec new-val (1+ (aref vec new-val))))))
+               ))
+            ((eq var 'sh-indent-comment)
+             (unless (= curr-indent (sh-calculate-indent info))
+               ;; this is not the default indentation
+               (setq comments-always-default nil)
+               (if comment-col ;; then we have see one before
+                   (or (eq comment-col curr-indent)
+                       (setq comment-col t)) ;; seen a different one
+                 (setq comment-col curr-indent))
+               ))
+            (t
+             (sh-debug "Cannot learn this line!!!")
+             ))
+           (sh-debug
+            "at %s learned-var-list is %s" (point) learned-var-list)
+           ))
+       (forward-line 1)
+       ) ;; while
+      (if sh-debug
+         (progn
+           (setq msg (format
+                      "comment-col = %s  comments-always-default = %s"
+                      comment-col comments-always-default))
+           ;; (message msg)
+           (sh-mark-line  msg nil out-buffer)))
+      (cond
+       ((eq comment-col 0)
+       (setq msg  "\nComments are all in 1st column.\n"))
+       (comments-always-default
+       (setq msg  "\nComments follow default indentation.\n")
+       (setq comment-col t))
+       ((numberp comment-col)
+       (setq msg  (format "\nComments are in col %d." comment-col)))
+       (t
+       (setq msg  "\nComments seem to be mixed, leaving them as is.\n")
+       (setq comment-col nil)
+       ))
+      (sh-debug msg)
+      (sh-mark-line  msg nil out-buffer)
+
+      (sh-mark-line initial-msg nil out-buffer t t)
+
+      (setq suggested (sh-guess-basic-offset vec))
+
+      (if (and suggested (not specified-basic-offset))
+         (let ((new-value
+                (cond
+                 ;; t => set it if we have a single value as a number
+                 ((and (eq sh-learn-basic-offset t) (numberp suggested))
+                  suggested)
+                 ;; other non-nil => set it if only one value was found
+                 (sh-learn-basic-offset
+                  (if (numberp suggested)
+                      suggested
+                    (if (= (length suggested) 1)
+                        (car suggested))))
+                 (t
+                  nil))))
+           (if new-value
+               (progn
+                 (setq learned-var-list
+                       (append (list (list 'sh-basic-offset
+                                           (setq sh-basic-offset new-value)
+                                           (point-max)))
+                               learned-var-list))
+                 ;; Not sure if we need to put this line in, since
+                 ;; it will appear in the "Learned variable settings".
+                 (sh-mark-line
+                  (format "Changed sh-basic-offset to: %d" sh-basic-offset)
+                  nil out-buffer))
+             (sh-mark-line
+              (if (listp suggested)
+                  (format "Possible value(s) for sh-basic-offset:  %s"
+                          (mapconcat 'int-to-string suggested " "))
+                (format "Suggested sh-basic-offset:  %d" suggested))
+              nil out-buffer))))
+
+      
+      (setq learned-var-list
+           (append (list (list 'sh-indent-comment comment-col (point-max)))
+                   learned-var-list))
+      (setq sh-indent-comment comment-col)
+      (let ((name (buffer-name))
+           (lines (if (and (eq (point-min) 1)
+                           (eq (point-max) (1+ (buffer-size))))
+                      ""
+                    (format "lines %d to %d of "
+                            (1+ (count-lines 1 (point-min)))
+                            (1+ (count-lines 1 (point-max))))))
+           )
+       (sh-mark-line  "\nLearned variable settings:" nil out-buffer)
+       (if arg
+           ;; Set learned variables to symbolic rather than numeric
+           ;; values where possible.
+           (dolist (learned-var (reverse learned-var-list))
+             (let ((var (car learned-var))
+                   (val (nth 1 learned-var)))
+               (when (and (not (eq var 'sh-basic-offset))
+                          (numberp val))
+                 (sh-set-var-value var val)))))
+       (dolist (learned-var (reverse learned-var-list))
+         (let ((var (car learned-var)))
+           (sh-mark-line (format "  %s %s" var (symbol-value var))
+                         (nth 2 learned-var) out-buffer)))
+       (save-excursion
+         (set-buffer out-buffer)
+         (goto-char (point-min))
+         (insert
+          (format "Indentation values for buffer %s.\n" name)
+          (format "%d indentation variable%s different values%s\n\n"
+                  num-diffs
+                  (if (= num-diffs 1)
+                      " has"   "s have")
+                  (if (zerop num-diffs)
+                      "." ":"))
+          )))
+      ;; Are abnormal hooks considered bad form?
+      (run-hook-with-args 'sh-learned-buffer-hook learned-var-list)
+      (if (or sh-popup-occur-buffer (> num-diffs 0))
+         (pop-to-buffer out-buffer))
+      )))
+
+(defun sh-guess-basic-offset (vec)
+  "See if we can determine a reasonable value for `sh-basic-offset'.
+This is experimental, heuristic and arbitrary!
+Argument VEC is a vector of information collected by
+`sh-learn-buffer-indent'.
+Return values:
+  number          - there appears to be a good single value
+  list of numbers - no obvious one, here is a list of one or more
+                   reasonable choices
+  nil            - we couldn't find a reasonable one."
+  (let* ((max (1- (length vec)))
+        (i 1)
+        (totals (make-vector max 0))
+        (return nil)
+        j)
+    (while (< i max)
+      (aset totals i (+ (aref totals i) (* 4 (aref vec i))))
+      (setq j (/ i 2))
+      (if (zerop (% i 2))
+         (aset totals i (+ (aref totals i) (aref vec (/ i 2)))))
+      (if (< (* i 2) max)
+         (aset totals i (+ (aref totals i) (aref vec (* i 2)))))
+      (setq i (1+ i)))
+
+    (let ((x nil)
+         (result nil)
+         tot sum p)
+      (setq i 1)
+      (while (< i max)
+       (if (/= (aref totals i) 0)
+           (setq x (append x (list (cons i (aref totals i))))))
+       (setq i (1+ i)))
+
+      (setq x (sort x (lambda (a b) (> (cdr a) (cdr b)))))
+      (setq tot (apply '+ (append totals nil)))
+      (sh-debug (format "vec: %s\ntotals: %s\ntot: %d"
+                       vec totals tot))
+      (cond
+       ((zerop (length x))
+       (message "no values!")) ;; we return nil
+       ((= (length x) 1)
+       (message "only value is %d" (car (car x)))
+       (setq result (car (car x)))) ;; return single value
+       ((> (cdr (car x)) (/ tot 2))
+       ;; 1st is > 50%
+       (message "basic-offset is probably %d" (car (car x)))
+       (setq result (car (car x)))) ;;   again, return a single value
+       ((>=  (cdr (car x)) (* 2 (cdr (car (cdr x)))))
+       ;; 1st is >= 2 * 2nd
+       (message "basic-offset could be %d" (car (car x)))
+       (setq result (car (car x))))
+       ((>= (+ (cdr (car x))(cdr (car (cdr x)))) (/ tot 2))
+       ;; 1st & 2nd together >= 50%  - return a list
+       (setq p x  sum 0 result nil)
+       (while  (and p
+                    (<= (setq sum (+ sum (cdr (car p)))) (/ tot 2)))
+         (setq result (append result (list (car (car p)))))
+         (setq p (cdr p)))
+       (message "Possible choices for sh-basic-offset: %s"
+                (mapconcat 'int-to-string result " ")))
+       (t
+       (message "No obvious value for sh-basic-offset.  Perhaps %d"
+                (car (car x)))
+       ;; result is nil here
+       ))
+      result)))
+
+;; ========================================================================
+
+;; Styles -- a quick and dirty way of saving the indentation settings.
+
+(defvar sh-styles-alist nil
+  "A list of all known shell indentation styles.")
+
+(defun sh-name-style (name &optional confirm-overwrite)
+  "Name the current indentation settings as a style called NAME.
+If this name exists, the command will prompt whether it should be
+overwritten if
+- - it was called interactively with a prefix argument, or
+- - called non-interactively with optional CONFIRM-OVERWRITE non-nil."
+  ;; (interactive "sName for this style: ")
+  (interactive
+   (list
+    (read-from-minibuffer "Name for this style? " )
+    (not current-prefix-arg)))
+  (let ((slist (cons name
+                    (mapcar (lambda (var) (cons var (symbol-value var)))
+                            sh-var-list)))
+       (style (assoc name sh-styles-alist)))
+    (if style
+       (if (and confirm-overwrite
+                (not (y-or-n-p "This style exists.  Overwrite it? ")))
+           (message "Not changing style %s" name)
+         (message "Updating style %s" name)
+         (setcdr style (cdr slist)))
+      (message "Creating new style %s" name)
+      (push slist sh-styles-alist))))
+
+(defun sh-load-style (name)
+  "Set shell indentation values for this buffer from those in style NAME."
+  (interactive (list (completing-read
+                     "Which style to use for this buffer? "
+                     sh-styles-alist nil t)))
+  (let ((sl (assoc name  sh-styles-alist)))
+    (if (null sl)
+       (error "sh-load-style - style %s not known" name)
+      (dolist (var (cdr sl))
+       (set (car var) (cdr var))))))
+
+(defun sh-save-styles-to-buffer (buff)
+  "Save all current styles in elisp to buffer BUFF.
+This is always added to the end of the buffer."
+  (interactive (list
+               (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
+  (with-current-buffer (get-buffer-create buff)
+    (goto-char (point-max))
+    (insert "\n")
+    (pp `(setq sh-styles-alist ',sh-styles-alist) (current-buffer))))
+
+
+\f
+;; statement syntax-commands for various shells
+
+;; You are welcome to add the syntax or even completely new statements as
+;; appropriate for your favorite shell.
+
+(define-skeleton sh-case
+  "Insert a case/switch statement.  See `sh-feature'."
+  (csh "expression: "
+       "switch( " str " )" \n
+       > "case " (read-string "pattern: ") ?: \n
+       > _ \n
+       "breaksw" \n
+       ( "other pattern, %s: "
+        < "case " str ?: \n
+        > _ \n
+        "breaksw" \n)
+       < "default:" \n
+       > _ \n
+       resume:
+       < < "endsw" \n)
+  (es)
+  (rc "expression: "
+      > "switch( " str " ) {" \n
+      > "case " (read-string "pattern: ") \n
+      > _ \n
+      ( "other pattern, %s: "
+       "case " str > \n
+       > _ \n)
+      "case *" > \n
+      > _ \n
+      resume:
+      ?} > \n)
+  (sh "expression: "
+      > "case " str " in" \n
+      > (read-string "pattern: ")
+      (propertize ")" 'syntax-table sh-st-punc)
+      \n
+      > _ \n
+      ";;" \n
+      ( "other pattern, %s: "
+       > str (propertize ")" 'syntax-table sh-st-punc) \n
+       > _ \n
+       ";;" \n)
+      > "*" (propertize ")" 'syntax-table sh-st-punc) \n
+      > _ \n
+      resume:
+      "esac" > \n))
+
+(define-skeleton sh-for
+  "Insert a for loop.  See `sh-feature'."
+  (csh eval sh-modify sh
+       1 ""
+       2 "foreach "
+       4 " ( "
+       6 " )"
+       15 '<
+       16 "end")
+  (es eval sh-modify rc
+      4 " = ")
+  (rc eval sh-modify sh
+      2 "for( "
+      6 " ) {"
+      15 ?} )
+  (sh "Index variable: "
+      > "for " str " in " _ "; do" \n
+      > _ | ?$ & (sh-remember-variable str) \n
+      "done" > \n))
+
+
+
+(define-skeleton sh-indexed-loop
+  "Insert an indexed loop from 1 to n.  See `sh-feature'."
+  (bash eval identity posix)
+  (csh "Index variable: "
+       "@ " str " = 1" \n
+       "while( $" str " <= " (read-string "upper limit: ") " )" \n
+       > _ ?$ str \n
+       "@ " str "++" \n
+       < "end" \n)
+  (es eval sh-modify rc
+      4 " =")
+  (ksh88 "Index variable: "
+        > "integer " str "=0" \n
+        > "while (( ( " str " += 1 ) <= "
+        (read-string "upper limit: ")
+        " )); do" \n
+        > _ ?$ (sh-remember-variable str) > \n
+        "done" > \n)
+  (posix "Index variable: "
+        > str "=1" \n
+        "while [ $" str " -le "
+        (read-string "upper limit: ")
+        " ]; do" \n
+        > _ ?$ str \n
+        str ?= (sh-add (sh-remember-variable str) 1) \n
+        "done" > \n)
+  (rc "Index variable: "
+      > "for( " str " in" " `{awk 'BEGIN { for( i=1; i<="
+      (read-string "upper limit: ")
+      "; i++ ) print i }'`}) {" \n
+      > _ ?$ (sh-remember-variable str) \n
+      ?} > \n)
+  (sh "Index variable: "
+      > "for " str " in `awk 'BEGIN { for( i=1; i<="
+      (read-string "upper limit: ")
+      "; i++ ) print i }'`; do" \n
+      > _ ?$ (sh-remember-variable str) \n
+      "done" > \n))
+
+
+(defun sh-shell-initialize-variables ()
+  "Scan the buffer for variable assignments.
+Add these variables to `sh-shell-variables'."
+  (message "Scanning buffer `%s' for variable assignments..." (buffer-name))
+  (save-excursion
+    (goto-char (point-min))
+    (setq sh-shell-variables-initialized t)
+    (while (search-forward "=" nil t)
+      (sh-assignment 0)))
+  (message "Scanning buffer `%s' for variable assignments...done"
+          (buffer-name)))
+
+(defvar sh-add-buffer)
+
+(defun sh-add-completer (string predicate code)
+  "Do completion using `sh-shell-variables', but initialize it first.
+This function is designed for use as the \"completion table\",
+so it takes three arguments:
+  STRING, the current buffer contents;
+  PREDICATE, the predicate for filtering possible matches;
+  CODE, which says what kind of things to do.
+CODE can be nil, t or `lambda'.
+nil means to return the best completion of STRING, or nil if there is none.
+t means to return a list of all possible completions of STRING.
+`lambda' means to return t if STRING is a valid completion as it stands."
+  (let ((sh-shell-variables
+        (save-excursion
+          (set-buffer sh-add-buffer)
+          (or sh-shell-variables-initialized
+              (sh-shell-initialize-variables))
+          (nconc (mapcar (lambda (var)
+                           (let ((name
+                                  (substring var 0 (string-match "=" var))))
+                             (cons name name)))
+                         process-environment)
+                 sh-shell-variables))))
+    (cond ((null code)
+          (try-completion string sh-shell-variables predicate))
+         ((eq code t)
+          (all-completions string sh-shell-variables predicate))
+         ((eq code 'lambda)
+          (assoc string sh-shell-variables)))))
+
+(defun sh-add (var delta)
+  "Insert an addition of VAR and prefix DELTA for Bourne (type) shell."
+  (interactive
+   (let ((sh-add-buffer (current-buffer)))
+     (list (completing-read "Variable: " 'sh-add-completer)
+          (prefix-numeric-value current-prefix-arg))))
+  (insert (sh-feature '((bash . "$[ ")
+                       (ksh88 . "$(( ")
+                       (posix . "$(( ")
+                       (rc . "`{expr $")
+                       (sh . "`expr $")
+                       (zsh . "$[ ")))
+         (sh-remember-variable var)
+         (if (< delta 0) " - " " + ")
+         (number-to-string (abs delta))
+         (sh-feature '((bash . " ]")
+                       (ksh88 . " ))")
+                       (posix . " ))")
+                       (rc . "}")
+                       (sh . "`")
+                       (zsh . " ]")))))
+
+
+
+(define-skeleton sh-function
+  "Insert a function definition.  See `sh-feature'."
+  (bash eval sh-modify ksh88
+       3 "() {")
+  (ksh88 "name: "
+        "function " str " {" \n
+        > _ \n
+        < "}" \n)
+  (rc eval sh-modify ksh88
+      1 "fn ")
+  (sh ()
+      "() {" \n
+      > _ \n
+      < "}" \n))
+
+
+
+(define-skeleton sh-if
+  "Insert an if statement.  See `sh-feature'."
+  (csh "condition: "
+       "if( " str " ) then" \n
+       > _ \n
+       ( "other condition, %s: "
+        < "else if( " str " ) then" \n
+        > _ \n)
+       < "else" \n
+       > _ \n
+       resume:
+       < "endif" \n)
+  (es "condition: "
+      > "if { " str " } {" \n
+      > _ \n
+      ( "other condition, %s: "
+       "} { " str " } {" > \n
+       > _ \n)
+      "} {" > \n
+      > _ \n
+      resume:
+      ?} > \n)
+  (rc "condition: "
+      > "if( " str " ) {" \n
+      > _ \n
+      ( "other condition, %s: "
+       "} else if( " str " ) {"  > \n
+       > _ \n)
+      "} else {" > \n
+      > _ \n
+      resume:
+      ?} > \n)
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      > "if " str "; then" \n
+      > _ \n
+      ( "other condition, %s: "
+       >  "elif " str "; then" > \n
+       > \n)
+      "else" > \n
+      > \n
+      resume:
+      "fi" > \n))
+
+
+
+(define-skeleton sh-repeat
+  "Insert a repeat loop definition.  See `sh-feature'."
+  (es nil
+      > "forever {" \n
+      > _ \n
+      ?} > \n)
+  (zsh "factor: "
+       > "repeat " str "; do" > \n
+       >  \n
+       "done" > \n))
+
+;;;(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
+
+
+
+(define-skeleton sh-select
+  "Insert a select statement.  See `sh-feature'."
+  (ksh88 "Index variable: "
+        > "select " str " in " _ "; do" \n
+        > ?$ str \n
+        "done" > \n)
+  (bash eval sh-append ksh88))
+;;;(put 'sh-select 'menu-enable '(sh-feature sh-select))
+
+
+
+(define-skeleton sh-tmp-file
+  "Insert code to setup temporary file handling.  See `sh-feature'."
+  (bash eval identity ksh88)
+  (csh (file-name-nondirectory (buffer-file-name))
+       "set tmp = /tmp/" str ".$$" \n
+       "onintr exit" \n _
+       (and (goto-char (point-max))
+           (not (bolp))
+           ?\n)
+       "exit:\n"
+       "rm $tmp* >&/dev/null" > \n)
+  (es (file-name-nondirectory (buffer-file-name))
+      > "local( signals = $signals sighup sigint; tmp = /tmp/" str
+      ".$pid ) {" \n
+      > "catch @ e {" \n
+      > "rm $tmp^* >[2]/dev/null" \n
+      "throw $e" \n
+      "} {" > \n
+      _ \n
+      ?} > \n
+      ?} > \n)
+  (ksh88 eval sh-modify sh
+        7 "EXIT")
+  (rc (file-name-nondirectory (buffer-file-name))
+      > "tmp = /tmp/" str ".$pid" \n
+      "fn sigexit { rm $tmp^* >[2]/dev/null }" \n)
+  (sh (file-name-nondirectory (buffer-file-name))
+      > "TMP=${TMPDIR:-/tmp}/" str ".$$" \n
+      "trap \"rm $TMP* 2>/dev/null\" " ?0 \n))
+
+
+
+(define-skeleton sh-until
+  "Insert an until loop.  See `sh-feature'."
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      > "until " str "; do" \n
+      > _ \n
+      "done" > \n))
+;;;(put 'sh-until 'menu-enable '(sh-feature sh-until))
+
+
+
+(define-skeleton sh-while
+  "Insert a while loop.  See `sh-feature'."
+  (csh eval sh-modify sh
+       2 ""
+       3 "while( "
+       5 " )"
+       10 '<
+       11 "end")
+  (es eval sh-modify sh
+      3 "while { "
+      5 " } {"
+      10 ?} )
+  (rc eval sh-modify sh
+      3 "while( "
+      5 " ) {"
+      10 ?} )
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      > "while " str "; do" \n
+      > _ \n
+      "done" > \n))
+
+
+
+(define-skeleton sh-while-getopts
+  "Insert a while getopts loop.  See `sh-feature'.
+Prompts for an options string which consists of letters for each recognized
+option followed by a colon `:' if the option accepts an argument."
+  (bash eval sh-modify sh
+       18 "${0##*/}")
+  (csh nil
+       "while( 1 )" \n
+       > "switch( \"$1\" )" \n
+       '(setq input '("- x" . 2))
+       > >
+       ( "option, %s: "
+        < "case " '(eval str)
+        '(if (string-match " +" str)
+             (setq v1 (substring str (match-end 0))
+                   str (substring str 0 (match-beginning 0)))
+           (setq v1 nil))
+        str ?: \n
+        > "set " v1 & " = $2" | -4 & _ \n
+        (if v1 "shift") & \n
+        "breaksw" \n)
+       < "case --:" \n
+       > "shift" \n
+       < "default:" \n
+       > "break" \n
+       resume:
+       < < "endsw" \n
+       "shift" \n
+       < "end" \n)
+  (ksh88 eval sh-modify sh
+        16 "print"
+        18 "${0##*/}"
+        36 "OPTIND-1")
+  (posix eval sh-modify sh
+        18 "$(basename $0)")
+  (sh "optstring: "
+      > "while getopts :" str " OPT; do" \n
+      > "case $OPT in" \n
+      '(setq v1 (append (vconcat str) nil))
+      ( (prog1 (if v1 (char-to-string (car v1)))
+         (if (eq (nth 1 v1) ?:)
+             (setq v1 (nthcdr 2 v1)
+                   v2 "\"$OPTARG\"")
+           (setq v1 (cdr v1)
+                 v2 nil)))
+       > str "|+" str (propertize ")" 'syntax-table sh-st-punc) \n
+       > _ v2 \n
+       > ";;" \n)
+      > "*" (propertize ")" 'syntax-table sh-st-punc) \n
+      > "echo" " \"usage: " "`basename $0`"
+      " [+-" '(setq v1 (point)) str
+      '(save-excursion
+        (while (search-backward ":" v1 t)
+          (replace-match " ARG] [+-" t t)))
+      (if (eq (preceding-char) ?-) -5)
+      (if (and (sequencep v1) (length v1)) "] " "} ")
+      "[--] ARGS...\"" \n
+      "exit 2"  > \n
+      "esac" >
+      \n "done"
+      > \n
+      "shift " (sh-add "OPTIND" -1) \n))
+
+
+
+(defun sh-assignment (arg)
+  "Remember preceding identifier for future completion and do self-insert."
+  (interactive "p")
+  (self-insert-command arg)
+  (if (<= arg 1)
+      (sh-remember-variable
+       (save-excursion
+        (if (re-search-forward (sh-feature sh-assignment-regexp)
+                               (prog1 (point)
+                                 (beginning-of-line 1))
+                               t)
+            (match-string 1))))))
+
+
+
+(defun sh-maybe-here-document (arg)
+  "Insert self.  Without prefix, following unquoted `<' inserts here document.
+The document is bounded by `sh-here-document-word'."
+  (interactive "*P")
+  (self-insert-command (prefix-numeric-value arg))
+  (or arg
+      (not (eq (char-after (- (point) 2)) last-command-char))
+      (save-excursion
+       (backward-char 2)
+       (sh-quoted-p))
+      (progn
+       (insert sh-here-document-word)
+       (or (eolp) (looking-at "[ \t]") (insert ? ))
+       (end-of-line 1)
+       (while
+           (sh-quoted-p)
+         (end-of-line 2))
+       (newline)
+       (save-excursion (insert ?\n sh-here-document-word)))))
+
+\f
+;; various other commands
+
+(autoload 'comint-dynamic-complete "comint"
+  "Dynamically perform completion at point." t)
+
+(autoload 'shell-dynamic-complete-command "shell"
+  "Dynamically complete the command at point." t)
+
+(autoload 'comint-dynamic-complete-filename "comint"
+  "Dynamically complete the filename at point." t)
+
+(autoload 'shell-dynamic-complete-environment-variable "shell"
+  "Dynamically complete the environment variable at point." t)
+
+
+
+(defun sh-newline-and-indent ()
+  "Strip unquoted whitespace, insert newline, and indent like current line."
+  (interactive "*")
+  (indent-to (prog1 (current-indentation)
+              (delete-region (point)
+                             (progn
+                               (or (zerop (skip-chars-backward " \t"))
+                                   (if (sh-quoted-p)
+                                       (forward-char)))
+                               (point)))
+              (newline))))
+
+(defun sh-beginning-of-command ()
+  "Move point to successive beginnings of commands."
+  (interactive)
+  (if (re-search-backward sh-beginning-of-command nil t)
+      (goto-char (match-beginning 2))))
+
+(defun sh-end-of-command ()
+  "Move point to successive ends of commands."
+  (interactive)
+  (if (re-search-forward sh-end-of-command nil t)
+      (goto-char (match-end 1))))
+
+(provide 'sh-script)
+
+;;; sh-script.el ends here