]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
Add new maintainer (deego).
[gnu-emacs] / lisp / progmodes / sh-script.el
index d99e31308705426016c925f9b8501e5e99e52a9d..283fe09cea26daf0117739c18f1a2017cfcf2382 100644 (file)
@@ -1,6 +1,7 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs
 
-;; Copyright (C) 1993, 94, 95, 96, 97, 1999 by Free Software Foundation, Inc.
+;; Copyright (C) 1993, 94, 95, 96, 97, 1999, 2001, 2003
+;;  Free Software Foundation, Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Version: 2.0f
 ;;             ===========
 ;; 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
 ;;    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
@@ -97,7 +98,7 @@
 ;;    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
 ;;    *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
 ;; 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,
+;;
+;; 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
 ;; 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
 ;; 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:
 ;;    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:
@@ -235,8 +236,8 @@ By default we have the following three hierarchies:
 
 csh            C Shell
   jcsh         C Shell with Job Control
-  tcsh         Toronto C Shell
-    itcsh      ? Toronto C Shell
+  tcsh         Turbo C Shell
+    itcsh      ? Turbo C Shell
 rc             Plan 9 Shell
   es           Extensible Shell
 sh             Bourne Shell
@@ -257,7 +258,7 @@ sh          Bourne Shell
 
 
 (defcustom sh-alias-alist
-  (nconc (if (eq system-type 'gnu/linux)
+  (append (if (eq system-type 'gnu/linux)
             '((csh . tcsh)
               (ksh . pdksh)))
         ;; for the time being
@@ -353,61 +354,63 @@ the car and cdr are the same symbol.")
 (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'.
+;; 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-syntax-table
+  '((sh eval sh-mode-syntax-table ()
+       ?\# "<"
+       ?\n ">#"
+       ?\" "\"\""
+       ?\' "\"'"
+       ?\` "\"`"
+       ?! "_"
+       ?% "_"
+       ?: "_"
+       ?. "_"
+       ?^ "_"
+       ?~ "_"
+       ?< "."
+       ?> ".")
+    (csh eval identity sh)
+    (rc eval identity sh))
 
+  "Syntax-table used in Shell-Script mode.  See `sh-feature'.")
 
 (defvar sh-mode-map
   (let ((map (make-sparse-keymap))
@@ -440,21 +443,15 @@ the car and cdr are the same symbol.")
     (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 [remap complete-tag] 'comint-dynamic-complete)
+    (define-key map [remap newline-and-indent] 'sh-newline-and-indent)
+    (define-key map [remap delete-backward-char]
+      'backward-delete-char-untabify)
     (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 [remap beginning-of-defun]
+      'sh-beginning-of-compound-command)
+    (define-key map [remap backward-sentence] 'sh-beginning-of-command)
+    (define-key map [remap forward-sentence] 'sh-end-of-command)
     (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))
@@ -757,17 +754,17 @@ See `sh-feature'."
 See `sh-feature'.")
 
 \f
-;;; Font-Lock support
+;; Font-Lock support
 
 (defface sh-heredoc-face
   '((((class color)
       (background dark))
-     (:foreground "yellow" :bold t))
+     (:foreground "yellow" :weight bold))
     (((class color)
       (background light))
      (:foreground "tan" ))
     (t
-     (:bold t)))
+     (:weight bold)))
   "Face to show a here-document"
   :group 'sh-indentation)
 (defvar sh-heredoc-face 'sh-heredoc-face)
@@ -814,38 +811,80 @@ See `sh-feature'.")
 ;; 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
 
-(defun sh-font-lock-heredoc (start string quoted)
-  "Determine the syntax of the \\n after a <<HEREDOC."
-  (unless (sh-in-comment-or-string start)
+(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* ((eof-re (if eof (regexp-quote eof) ""))
+         ;; A rough regexp that should find the opening <<EOF back.
+        (sre (concat "<<\\(-?\\)\\s-*['\"\\]?"
+                     ;; Use \s| to cheaply check it's an open-heredoc.
+                     eof-re "['\"]?\\([ \t|;&)<>].*\\)?\\s|"))
+        ;; A regexp that will find other EOFs.
+        (ere (concat "^" (if indented "[ \t]*") eof-re "\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 ((ere (concat
-               "^" (if quoted "[ \t]*")
-               (regexp-quote (replace-regexp-in-string "['\"]" "" string))
-               "\\(\n\\)")))
-      (unless (assoc ere font-lock-syntactic-keywords)
-       (let* ( ;; A rough regexp that should find us back.
-              (sre (concat "<<\\(-\\)?\\s-*['\"]?"
-                           (regexp-quote string) "['\"]?[ \t\n]"))
-              (code `(cond
-                      ((save-excursion (re-search-backward ,sre nil t))
-                       ;; This ^STRING$ is indeed following a <<STRING
-                       sh-here-doc-syntax)
-                      ((not (save-excursion (re-search-forward ,sre nil t)))
-                       ;; There's no <<STRING either before or after us,
-                       ;; so we should remove this now obsolete entry.
-                       (setq font-lock-syntactic-keywords
-                             (delq (assoc ,ere font-lock-syntactic-keywords)
-                                   font-lock-syntactic-keywords))
-                       nil))))
-         ;; Use destructive update so the new keyword gets used right away.
-         (setq font-lock-syntactic-keywords
-               (nconc font-lock-syntactic-keywords
-                      (list (font-lock-compile-keyword `(,ere 1 ,code))))))))
+    (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)
@@ -867,19 +906,19 @@ See `sh-feature'.")
       sh-st-punc)))
 
 (defconst sh-font-lock-syntactic-keywords
-  ;; Mark a `#' character as having punctuation syntax in a variable reference.
-  ;; Really we should do this properly.  From Chet Ramey and Brian Fox:
-  ;; "A `#' begins a comment when it is unquoted and at the beginning of a
-  ;; word.  In the shell, words are separated by metacharacters."
-  ;; To do this in a regexp would be slow as it would be anchored to the right.
-  ;; But I can't be bothered to write a function to do it properly and
-  ;; efficiently.  So we only do it properly for `#' in variable references and
-  ;; do it efficiently by anchoring the regexp to the left.
-  `(("\\${?[^}#\n\t ]*\\(##?\\)" 1 ,sh-st-punc)
+  ;; 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.
-    ("[^<>]<<\\(-\\)?\\s-*\\(\\(['\"][^'\"]+['\"]\\|\\sw\\|\\s_\\)+\\).*\\(\n\\)"
-     4 (sh-font-lock-heredoc
-       (match-beginning 0) (match-string 2) (match-end 1)))
+    (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)
+         (and (match-beginning 3) (/= (match-beginning 3) (match-end 3))))
+      nil t))
     ;; Distinguish the special close-paren in `case'.
     (")" 0 (sh-font-lock-paren (match-beginning 0)))))
 
@@ -1012,12 +1051,12 @@ a number means align to that column, e.g. 0 means fist column."
          sh-symbol-list))
 
 (defcustom sh-indent-for-fi 0
-  "*How much to indent a fi relative to an if.   Usually 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."
+  "*How much to indent a done relative to its matching stmt.  Usually 0."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
@@ -1034,7 +1073,7 @@ does not affect then else elif or fi statements themselves."
   :group 'sh-indentation)
 
 (defcustom sh-indent-for-then '+
-  "*How much to indent an then relative to an if."
+  "*How much to indent a then relative to an if."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
@@ -1143,10 +1182,6 @@ punctuation characters like '-'."
 (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
@@ -1184,7 +1219,7 @@ frequently editing existing scripts with different styles.")
 (put 'sh-mode 'mode-class 'special)
 
 ;;;###autoload
-(define-derived-mode sh-mode nil "Shell-script"
+(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.
@@ -1228,7 +1263,7 @@ buffer indents as it currently is indented.
 \\[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
@@ -1237,6 +1272,11 @@ 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."
+  (interactive)
+  (kill-all-local-variables)
+  (setq major-mode 'sh-mode
+       mode-name "Shell-script")
+  (use-local-map sh-mode-map)
   (make-local-variable 'skeleton-end-hook)
   (make-local-variable 'paragraph-start)
   (make-local-variable 'paragraph-separate)
@@ -1257,7 +1297,6 @@ with your script for an edit-interpret-debug cycle."
   (make-local-variable 'sh-shell-variables-initialized)
   (make-local-variable 'imenu-generic-expression)
   (make-local-variable 'sh-indent-supported-here)
-  (make-local-variable 'font-lock-unfontify-region-function)
   (setq skeleton-end-hook (lambda ()
                            (or (eolp) (newline) (indent-relative)))
        paragraph-start (concat page-delimiter "\\|$")
@@ -1271,9 +1310,7 @@ with your script for an edit-interpret-debug cycle."
           sh-font-lock-keywords-1 sh-font-lock-keywords-2)
          nil nil
          ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil
-         (font-lock-syntactic-keywords
-          ;; Copy so we can use destructive update in `sh-font-lock-heredoc'.
-          . ,(copy-sequence sh-font-lock-syntactic-keywords))
+         (font-lock-syntactic-keywords . sh-font-lock-syntactic-keywords)
          (font-lock-syntactic-face-function
           . sh-font-lock-syntactic-face-function))
        skeleton-pair-alist '((?` _ ?`))
@@ -1294,7 +1331,8 @@ with your script for an edit-interpret-debug cycle."
                 ((and buffer-file-name
                       (string-match "\\.m?spec$" buffer-file-name))
                  "rpm")))))
-    (sh-set-shell (or interpreter sh-shell-file) nil nil)))
+    (sh-set-shell (or interpreter sh-shell-file) nil nil))
+  (run-hooks 'sh-mode-hook))
 
 ;;;###autoload
 (defalias 'shell-script-mode 'sh-mode)
@@ -1313,21 +1351,17 @@ This adds rules for comments and assignments."
 
 (defun sh-font-lock-keywords-1 (&optional builtins)
   "Function to get better fontification including keywords."
-  (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\(\\("
-                         (mapconcat 'identity
-                                    (sh-feature sh-leading-keywords)
-                                    "\\|")
-                         "\\)[ \t]+\\)?\\("
-                         (mapconcat 'identity
-                                    (append (sh-feature sh-leading-keywords)
-                                            (sh-feature sh-other-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]+\\)?\\("
-                        (mapconcat 'identity (sh-feature sh-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)))
@@ -1397,12 +1431,21 @@ This adds rules for comments and assignments."
 
 (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.
+When used interactively, insert the proper starting #!-line,
+and make the visited file executable via `executable-set-magic',
+perhaps querying depending on the value of `executable-query'.
+
+When this function is called noninteractively, INSERT-FLAG (the third
+argument) controls whether to insert a #!-line and think about making
+the visited file executable, and NO-QUERY-FLAG (the second argument)
+controls whether to query about making the visited file executable.
+
 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)))
+  (interactive (list (completing-read (format "Shell \(default %s\): "
+                                             sh-shell-file)
+                                     interpreter-mode-alist
+                                     (lambda (x) (eq (cdr x) 'sh-mode))
+                                     nil nil nil sh-shell-file)
                     (eq executable-query 'function)
                     t))
   (if (string-match "\\.exe\\'" shell)
@@ -1422,6 +1465,8 @@ Calls the value of `sh-set-shell-hook' if set."
        sh-shell-variables-initialized nil
        imenu-generic-expression (sh-feature sh-imenu-generic-expression)
        imenu-case-fold-search nil)
+  (set-syntax-table (or (sh-feature sh-mode-syntax-table)
+                       (standard-syntax-table)))
   (dolist (var (sh-feature sh-variables))
     (sh-remember-variable var))
   (make-local-variable 'indent-line-function)
@@ -1503,38 +1548,45 @@ in ALIST."
 
 
 
-;;; 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)))
-
+;; 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-mode-syntax-table (table &rest list)
+  "Copy TABLE and set syntax for successive CHARs according to strings S."
+  (setq table (copy-syntax-table table))
+  (while list
+    (modify-syntax-entry (pop list) (pop list) table))
+  table)
 
 (defun sh-append (ancestor &rest list)
   "Return list composed of first argument (a list) physically appended to rest."
@@ -1624,17 +1676,11 @@ region, clear header."
   (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")))
+    (error "This buffer's shell does not support indentation through Emacs")))
 
 (defun sh-make-vars-local ()
   "Make the indentation variables local to this buffer.
@@ -1644,7 +1690,6 @@ 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."))
 
@@ -1652,7 +1697,6 @@ command `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)))
@@ -1678,9 +1722,9 @@ 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"))))
+                   (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))
@@ -1704,10 +1748,8 @@ which in this buffer is currently %s.
 (defun sh-in-comment-or-string (start)
   "Return non-nil if START is in a comment or string."
   (save-excursion
-    (let (state)
-      (beginning-of-line)
-      (setq state (parse-partial-sexp (point) start nil nil nil t))
-      (or (nth 3 state)(nth 4 state)))))
+    (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.
@@ -1920,8 +1962,9 @@ STRING         This is ignored for the purposes of calculating
       ;; Note: setting result to t means we are done and will return nil.
       ;;(This function never returns just t.)
       (cond
-       ((and (boundp 'font-lock-string-face)
-            (equal (get-text-property (point) 'face) font-lock-string-face))
+       ((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 "#")
@@ -1934,7 +1977,7 @@ STRING         This is ignored for the purposes of calculating
          (setq have-result nil)
          ))
        ) ;; cond
-      
+
       (unless have-result
        ;; Continuation lines are handled specially
        (if (sh-this-is-a-continuation)
@@ -2027,19 +2070,19 @@ STRING       This is ignored for the purposes of calculating
        (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)
@@ -2112,15 +2155,13 @@ If INFO is supplied it is used, else it is calculated."
   "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))
-              (boundp 'font-lock-string-face)
               (equal (get-text-property (1- (point)) 'face)
-                     font-lock-string-face))
+                     sh-heredoc-face))
       (let ((p1 (previous-single-property-change (1- (point)) 'face)))
        (when p1
          (goto-char p1)
@@ -2216,7 +2257,7 @@ we go to the end of the previous line and do not check for continuations."
   ;; Possible return values:
   ;;  nil  -  nothing
   ;; a string - possibly a keyword
-  ;; 
+  ;;
   (if (bolp)
       nil
     (let ((going t)
@@ -2444,7 +2485,6 @@ If INFO is supplied it is used, else it is calculated from current line."
 (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
@@ -2590,7 +2630,7 @@ unless optional argument ARG (the prefix when interactive) is non-nil."
        ((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))
@@ -2610,13 +2650,11 @@ unless optional argument ARG (the prefix when interactive) is non-nil."
 
 (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)
-      )))
+  (save-excursion
+    (set-buffer (get-buffer-create buffer))
+    (erase-buffer)
+    (occur-mode)
+    ))
 
 
 (defun sh-mark-line (message point buffer &optional add-linenum occur-point)
@@ -2626,7 +2664,6 @@ 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
@@ -2638,7 +2675,6 @@ so that `occur-next' and `occur-prev' will work."
          (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))
@@ -2647,14 +2683,17 @@ so that `occur-next' and `occur-prev' will work."
          (setq occur-point (point)))
       (insert message)
       (if point
-         (put-text-property start (point) 'mouse-face 'highlight))
+         (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)
+           (put-text-property start (point) 'occur-target m1)
            (if occur-point
-               (put-text-property occur-point (1+ occur-point)
-                                  'occur-point t))
+               (put-text-property start occur-point
+                                  'occur-match t))
            ))
       )))
 
@@ -2663,8 +2702,8 @@ so that `occur-next' and `occur-prev' will work."
 ;; 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
-;; 
+;; Example of how to use sh-learned-buffer-hook
+;;
 ;; (defun what-i-learned (list)
 ;;   (let ((p list))
 ;;     (save-excursion
@@ -2677,7 +2716,7 @@ so that `occur-next' and `occur-prev' will work."
 ;;     (setq p (cdr p)))
 ;;       (insert ")\n")
 ;;       )))
-;; 
+;;
 ;; (add-hook 'sh-learned-buffer-hook 'what-i-learned)
 
 
@@ -2869,7 +2908,7 @@ This command can often take a long time to run."
                 (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))
@@ -3054,7 +3093,7 @@ This is always added to the end of the buffer."
        < "default:" \n
        > _ \n
        resume:
-       < < "endsw")
+       < < "endsw" \n)
   (es)
   (rc "expression: "
       > "switch( " str " ) {" \n
@@ -3066,7 +3105,7 @@ This is always added to the end of the buffer."
       "case *" > \n
       > _ \n
       resume:
-      ?} > )
+      ?\} > \n)
   (sh "expression: "
       > "case " str " in" \n
       > (read-string "pattern: ")
@@ -3081,7 +3120,7 @@ This is always added to the end of the buffer."
       > "*" (propertize ")" 'syntax-table sh-st-punc) \n
       > _ \n
       resume:
-      "esac" > ))
+      "esac" > \n))
 
 (define-skeleton sh-for
   "Insert a for loop.  See `sh-feature'."
@@ -3091,18 +3130,17 @@ This is always added to the end of the buffer."
        4 " ( "
        6 " )"
        15 '<
-       16 "end"
-       )
+       16 "end")
   (es eval sh-modify rc
       4 " = ")
   (rc eval sh-modify sh
       2 "for( "
       6 " ) {"
-      15 ?} )
+      15 ?\} )
   (sh "Index variable: "
       > "for " str " in " _ "; do" \n
       > _ | ?$ & (sh-remember-variable str) \n
-      "done" > ))
+      "done" > \n))
 
 
 
@@ -3114,7 +3152,7 @@ This is always added to the end of the buffer."
        "while( $" str " <= " (read-string "upper limit: ") " )" \n
        > _ ?$ str \n
        "@ " str "++" \n
-       < "end")
+       < "end" \n)
   (es eval sh-modify rc
       4 " =")
   (ksh88 "Index variable: "
@@ -3123,7 +3161,7 @@ This is always added to the end of the buffer."
         (read-string "upper limit: ")
         " )); do" \n
         > _ ?$ (sh-remember-variable str) > \n
-        "done" > )
+        "done" > \n)
   (posix "Index variable: "
         > str "=1" \n
         "while [ $" str " -le "
@@ -3131,19 +3169,19 @@ This is always added to the end of the buffer."
         " ]; do" \n
         > _ ?$ str \n
         str ?= (sh-add (sh-remember-variable str) 1) \n
-        "done" > )
+        "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" > ))
+      "done" > \n))
 
 
 (defun sh-shell-initialize-variables ()
@@ -3220,13 +3258,13 @@ t means to return a list of all possible completions of STRING.
   (ksh88 "name: "
         "function " str " {" \n
         > _ \n
-        < "}")
+        < "}" \n)
   (rc eval sh-modify ksh88
       1 "fn ")
   (sh ()
       "() {" \n
       > _ \n
-      < "}"))
+      < "}" \n))
 
 
 
@@ -3241,7 +3279,7 @@ t means to return a list of all possible completions of STRING.
        < "else" \n
        > _ \n
        resume:
-       < "endif")
+       < "endif" \n)
   (es "condition: "
       > "if { " str " } {" \n
       > _ \n
@@ -3251,7 +3289,7 @@ t means to return a list of all possible completions of STRING.
       "} {" > \n
       > _ \n
       resume:
-      ?} > )
+      ?\} > \n)
   (rc "condition: "
       > "if( " str " ) {" \n
       > _ \n
@@ -3261,8 +3299,7 @@ t means to return a list of all possible completions of STRING.
       "} else {" > \n
       > _ \n
       resume:
-      ?} >
-      )
+      ?\} > \n)
   (sh "condition: "
       '(setq input (sh-feature sh-test))
       > "if " str "; then" \n
@@ -3273,7 +3310,7 @@ t means to return a list of all possible completions of STRING.
       "else" > \n
       > \n
       resume:
-      "fi" > ))
+      "fi" > \n))
 
 
 
@@ -3282,11 +3319,11 @@ t means to return a list of all possible completions of STRING.
   (es nil
       > "forever {" \n
       > _ \n
-      ?} > )
+      ?\} > \n)
   (zsh "factor: "
        > "repeat " str "; do" > \n
        >  \n
-       "done" > ))
+       "done" > \n))
 
 ;;;(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
 
@@ -3297,7 +3334,7 @@ t means to return a list of all possible completions of STRING.
   (ksh88 "Index variable: "
         > "select " str " in " _ "; do" \n
         > ?$ str \n
-        "done" > )
+        "done" > \n)
   (bash eval sh-append ksh88))
 ;;;(put 'sh-select 'menu-enable '(sh-feature sh-select))
 
@@ -3313,7 +3350,7 @@ t means to return a list of all possible completions of STRING.
            (not (bolp))
            ?\n)
        "exit:\n"
-       "rm $tmp* >&/dev/null" >)
+       "rm $tmp* >&/dev/null" > \n)
   (es (file-name-nondirectory (buffer-file-name))
       > "local( signals = $signals sighup sigint; tmp = /tmp/" str
       ".$pid ) {" \n
@@ -3322,16 +3359,16 @@ t means to return a list of all possible completions of STRING.
       "throw $e" \n
       "} {" > \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 }")
+      "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))
+      "trap \"rm $TMP* 2>/dev/null\" " ?0 \n))
 
 
 
@@ -3341,7 +3378,7 @@ t means to return a list of all possible completions of STRING.
       '(setq input (sh-feature sh-test))
       > "until " str "; do" \n
       > _ \n
-      "done" > ))
+      "done" > \n))
 ;;;(put 'sh-until 'menu-enable '(sh-feature sh-until))
 
 
@@ -3353,20 +3390,20 @@ t means to return a list of all possible completions of STRING.
        3 "while( "
        5 " )"
        10 '<
-       11 "end" )
+       11 "end")
   (es eval sh-modify sh
       3 "while { "
       5 " } {"
-      10 ?} )
+      10 ?\} )
   (rc eval sh-modify sh
       3 "while( "
       5 " ) {"
-      10 ?} )
+      10 ?\} )
   (sh "condition: "
       '(setq input (sh-feature sh-test))
       > "while " str "; do" \n
       > _ \n
-      "done" > ))
+      "done" > \n))
 
 
 
@@ -3398,7 +3435,7 @@ option followed by a colon `:' if the option accepts an argument."
        resume:
        < < "endsw" \n
        "shift" \n
-       < "end")
+       < "end" \n)
   (ksh88 eval sh-modify sh
         16 "print"
         18 "${0##*/}"
@@ -3431,7 +3468,7 @@ option followed by a colon `:' if the option accepts an argument."
       "esac" >
       \n "done"
       > \n
-      "shift " (sh-add "OPTIND" -1)))
+      "shift " (sh-add "OPTIND" -1) \n))