]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
(calendar-dst-check-each-year-flag): Avoid
[gnu-emacs] / lisp / progmodes / sh-script.el
index e2d82f7f13be495e0cb4c8a1ca27814bea226dd5..83b4bdea759e7f2bfbc4ecce2e9eaf65051747b0 100644 (file)
@@ -1,7 +1,7 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs
 
-;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005
-;;  Free Software Foundation, Inc.
+;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005,
+;;  2006  Free Software Foundation, Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Version: 2.0f
@@ -22,8 +22,8 @@
 
 ;; 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.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
   (require 'comint))
 (require 'executable)
 
+(defvar font-lock-comment-face)
+(defvar font-lock-set-defaults)
+(defvar font-lock-string-face)
 
 
 (defgroup sh nil
-  "Shell programming utilities"
+  "Shell programming utilities."
   :group 'unix
   :group 'languages)
 
 (defgroup sh-script nil
-  "Shell script mode"
+  "Shell script mode."
+  :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
   :group 'sh
   :prefix "sh-")
 
@@ -352,6 +356,7 @@ 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].")
+;;;###autoload(put 'sh-shell 'safe-local-variable 'symbolp)
 
 (defvar sh-mode-abbrev-table nil)
 
@@ -487,7 +492,10 @@ This is buffer-local in every such buffer.")
     map)
   "Keymap used in Shell-Script mode.")
 
-
+(defvar sh-skeleton-pair-default-alist '((?( _ ?)) (?\))
+                                     (?[ ?\s _ ?\s ?]) (?\])
+                                     (?{ _ ?}) (?\}))
+  "Value to use for `skeleton-pair-default-alist' in Shell-Script mode.")
 
 (defcustom sh-dynamic-complete-functions
   '(shell-dynamic-complete-environment-variable
@@ -792,7 +800,7 @@ See `sh-feature'.")
 \f
 ;; Font-Lock support
 
-(defface sh-heredoc-face
+(defface sh-heredoc
   '((((min-colors 88) (class color)
       (background dark))
      (:foreground "yellow1" :weight bold))
@@ -806,14 +814,28 @@ See `sh-feature'.")
      (:weight bold)))
   "Face to show a here-document"
   :group 'sh-indentation)
-(defvar sh-heredoc-face 'sh-heredoc-face)
+
+;; These colours are probably icky.  It's just a placeholder though.
+(defface sh-quoted-exec
+  '((((class color) (background dark))
+     (:foreground "salmon"))
+    (((class color) (background light))
+     (:foreground "magenta"))
+    (t
+     (:weight bold)))
+  "Face to show quoted execs like ``"
+  :group 'sh-indentation)
+
+;; backward-compatibility alias
+(put 'sh-heredoc-face 'face-alias 'sh-heredoc)
+(defvar sh-heredoc-face 'sh-heredoc)
 
 (defface sh-escaped-newline '((t :inherit font-lock-string-face))
   "Face used for (non-escaped) backslash at end of a line in Shell-script mode."
   :group 'sh-script
   :version "22.1")
 
-(defvar sh-font-lock-keywords
+(defvar sh-font-lock-keywords-var
   '((csh sh-append shell
         ("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1
           font-lock-variable-name-face))
@@ -823,7 +845,7 @@ See `sh-feature'.")
          font-lock-variable-name-face))
 
     (rc sh-append es)
-
+    (bash sh-append shell ("\\$(\\(\\sw+\\)" (1 'sh-quoted-exec t) ))
     (sh sh-append shell
        ;; Variable names.
        ("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
@@ -836,7 +858,7 @@ See `sh-feature'.")
         1 font-lock-negation-char-face))
 
     ;; The next entry is only used for defining the others
-    (shell sh-append executable-font-lock-keywords
+    (shell
            ;; Using font-lock-string-face here confuses sh-get-indent-info.
            ("\\(^\\|[^\\]\\)\\(\\\\\\\\\\)*\\(\\\\\\)$" 3 'sh-escaped-newline)
           ("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
@@ -848,11 +870,11 @@ See `sh-feature'.")
          ("^\\(\\sw+\\):"  1 font-lock-variable-name-face)))
   "Default expressions to highlight in Shell Script modes.  See `sh-feature'.")
 
-(defvar sh-font-lock-keywords-1
+(defvar sh-font-lock-keywords-var-1
   '((sh "[ \t]in\\>"))
   "Subdued level highlighting for Shell Script modes.")
 
-(defvar sh-font-lock-keywords-2 ()
+(defvar sh-font-lock-keywords-var-2 ()
   "Gaudy level highlighting for Shell Script modes.")
 
 ;; These are used for the syntax table stuff (derived from cperl-mode).
@@ -861,7 +883,15 @@ See `sh-feature'.")
 (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\\)+\\).*\\(\n\\)")
+(defconst sh-escaped-line-re
+  ;; Should match until the real end-of-continued line, but if that is not
+  ;; possible (because we bump into EOB or the search bound), then we should
+  ;; match until the search bound.
+  "\\(?:\\(?:.*[^\\\n]\\)?\\(?:\\\\\\\\\\)*\\\\\n\\)*.*")
+
+(defconst sh-here-doc-open-re
+  (concat "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\)+\\)"
+          sh-escaped-line-re "\\(\n\\)"))
 
 (defvar sh-here-doc-markers nil)
 (make-variable-buffer-local 'sh-here-doc-markers)
@@ -875,7 +905,9 @@ If non-nil INDENTED indicates that the EOF was indented."
          ;; 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|"))
+                     eof-re "['\"]?\\([ \t|;&)<>]"
+                      sh-escaped-line-re
+                      "\\)?\\s|"))
         ;; A regexp that will find other EOFs.
         (ere (concat "^" (if indented "[ \t]*") eof-re "\n"))
         (start (save-excursion
@@ -914,7 +946,8 @@ If non-nil INDENTED indicates that the EOF was indented."
 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 <<)."
+be indented (i.e. a <<- was used rather than just <<).
+Point is at the beginning of the next line."
   (unless (or (memq (char-before start) '(?< ?>))
              (sh-in-comment-or-string start))
     ;; We're looking at <<STRING, so we add "^STRING$" to the syntactic
@@ -925,6 +958,20 @@ be indented (i.e. a <<- was used rather than just <<)."
        (setq sh-here-doc-re
              (concat sh-here-doc-open-re "\\|^\\([ \t]*\\)"
                      (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))))
+    (let ((ppss (save-excursion (syntax-ppss (1- (point))))))
+      (if (nth 4 ppss)
+          ;; The \n not only starts the heredoc but also closes a comment.
+          ;; Let's close the comment just before the \n.
+          (put-text-property (1- (point)) (point) 'syntax-table '(12))) ;">"
+      (if (or (nth 5 ppss) (> (count-lines start (point)) 1))
+          ;; If the sh-escaped-line-re part of sh-here-doc-re has matched
+          ;; several lines, make sure we refontify them together.
+          ;; Furthermore, if (nth 5 ppss) is non-nil (i.e. the \n is
+          ;; escaped), it means the right \n is actually further down.
+          ;; Don't bother fixing it now, but place a multiline property so
+          ;; that when jit-lock-context-* refontifies the rest of the
+          ;; buffer, it also refontifies the current line with it.
+          (put-text-property start (point) 'font-lock-multiline t)))
     sh-here-doc-syntax))
 
 (defun sh-font-lock-here-doc (limit)
@@ -932,6 +979,59 @@ be indented (i.e. a <<- was used rather than just <<)."
   ;; This looks silly, but it's because `sh-here-doc-re' keeps changing.
   (re-search-forward sh-here-doc-re limit t))
 
+(defun sh-quoted-subshell (limit)
+  "Search for a subshell embedded in a string.
+Find all the unescaped \" characters within said subshell, remembering that
+subshells can nest."
+  ;; FIXME: This can (and often does) match multiple lines, yet it makes no
+  ;; effort to handle multiline cases correctly, so it ends up being
+  ;; rather flakey.
+  (when (and (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" limit t)
+             ;; Make sure the " we matched is an opening quote.
+            (eq ?\" (nth 3 (syntax-ppss))))
+    ;; bingo we have a $( or a ` inside a ""
+    (let ((char (char-after (point)))
+          (continue t)
+          (pos (point))
+          (data nil)      ;; value to put into match-data (and return)
+          (last nil)      ;; last char seen
+          (bq  (equal (match-string 1) "`")) ;; ` state flip-flop
+          (seen nil)                         ;; list of important positions
+          (nest 1))                          ;; subshell nesting level
+      (while (and continue char (<= pos limit))
+        ;; unescaped " inside a $( ... ) construct.
+        ;; state machine time...
+        ;; \ => ignore next char;
+        ;; ` => increase or decrease nesting level based on bq flag
+        ;; ) [where nesting > 0] => decrease nesting
+        ;; ( [where nesting > 0] => increase nesting
+        ;; ( [preceeded by $ ]   => increase nesting
+        ;; " [nesting <= 0 ]     => terminate, we're done.
+        ;; " [nesting >  0 ]     => remember this, it's not a proper "
+        ;; FIXME: don't count parens that appear within quotes.
+        (cond
+         ((eq ?\\ last) nil)
+         ((eq ?\` char) (setq nest (+ nest (if bq -1 1)) bq (not bq)))
+         ((and (> nest 0) (eq ?\) char))   (setq nest (1- nest)))
+         ((and (eq ?$ last) (eq ?\( char)) (setq nest (1+ nest)))
+         ((and (> nest 0) (eq ?\( char))   (setq nest (1+ nest)))
+         ((eq char ?\")
+          (if (>= 0 nest) (setq continue nil) (push pos seen))))
+        ;;(message "POS: %d [%d]" pos nest)
+        (setq last char
+              pos  (1+ pos)
+              char (char-after pos)) )
+      ;; FIXME: why construct a costly match data to pass to
+      ;; sh-apply-quoted-subshell rather than apply the highlight
+      ;; directly here?  -- Stef
+      (when seen
+        ;;(message "SEEN: %S" seen)
+        (setq data (list (current-buffer)))
+        (dolist(P seen)
+          (setq data (cons P (cons (1+ P) data))))
+        (store-match-data data))
+      data) ))
+
 (defun sh-is-quoted-p (pos)
   (and (eq (char-before pos) ?\\)
        (not (sh-is-quoted-p (1- pos)))))
@@ -952,18 +1052,37 @@ be indented (i.e. a <<- was used rather than just <<)."
                  (when (memq (char-before) '(?\" ?\'))
                    (condition-case nil (progn (backward-sexp 1) t)
                      (error nil)))))
-         (forward-comment (- (point-max)))
+          (while (progn
+                   (forward-comment (- (point-max)))
+                   ;; Maybe we've bumped into an escaped newline.
+                   (sh-is-quoted-p (point)))
+            (backward-char 1))
          (when (eq (char-before) ?|)
            (backward-char 1) t)))
     (when (save-excursion (backward-char 2) (looking-at ";;\\|in"))
       sh-st-punc)))
 
+(defun sh-apply-quoted-subshell ()
+  "Apply the `sh-st-punc' syntax to all the matches in `match-data'.
+This is used to flag quote characters in subshell constructs inside strings
+\(which should therefore not be treated as normal quote characters\)"
+  (let ((m (match-data)) a b)
+    (while m
+      (setq a (car  m)
+            b (cadr m)
+            m (cddr m))
+      (put-text-property a b 'syntax-table sh-st-punc))) 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)
+    ;; In a '...' the backslash is not escaping.
+    ("\\(\\\\\\)'" 1 ,sh-st-punc)
+    ;; Make sure $@ and @? are correctly recognized as sexps.
+    ("\\$\\([?@]\\)" 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
@@ -973,14 +1092,19 @@ be indented (i.e. a <<- was used rather than just <<)."
          (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)))))
+    (")" 0 (sh-font-lock-paren (match-beginning 0)))
+    ;; highlight (possibly nested) subshells inside "" quoted regions correctly.
+    ;; This should be at the very end because it uses syntax-ppss.
+    (sh-quoted-subshell
+     (1 (sh-apply-quoted-subshell) t t))))
 
 (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))
+  (let ((q (nth 3 state)))
+    (if q
+        (if (char-valid-p q)
+            (if (eq q ?\`) 'sh-quoted-exec font-lock-string-face)
+          sh-heredoc-face)
+      font-lock-comment-face)))
 
 (defgroup sh-indentation nil
   "Variables controlling indentation in shell scripts.
@@ -1294,7 +1418,7 @@ 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-mode-map>
 \\[sh-case]     case statement
 \\[sh-for]      for loop
 \\[sh-function]         function definition
@@ -1347,24 +1471,29 @@ with your script for an edit-interpret-debug cycle."
   (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 'skeleton-pair-filter-function)
   (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-filter-function)
   (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)
+  (make-local-variable 'skeleton-pair-default-alist)
+  (setq skeleton-pair-default-alist sh-skeleton-pair-default-alist)
   (setq skeleton-end-hook (lambda ()
                            (or (eolp) (newline) (indent-relative)))
        paragraph-start (concat page-delimiter "\\|$")
        paragraph-separate paragraph-start
        comment-start "# "
+       comment-start-skip "#+[\t ]*"
+       local-abbrev-table sh-mode-abbrev-table
        comint-dynamic-complete-functions sh-dynamic-complete-functions
        ;; we can't look if previous line ended with `\'
        comint-prompt-regexp "^[ \t]*"
+       imenu-case-fold-search nil
        font-lock-defaults
        `((sh-font-lock-keywords
           sh-font-lock-keywords-1 sh-font-lock-keywords-2)
@@ -1374,25 +1503,37 @@ with your script for an edit-interpret-debug cycle."
          (font-lock-syntactic-face-function
           . sh-font-lock-syntactic-face-function))
        skeleton-pair-alist '((?` _ ?`))
-       skeleton-pair-filter 'sh-quoted-p
+       skeleton-pair-filter-function 'sh-quoted-p
        skeleton-further-elements '((< '(- (min sh-indentation
                                                (current-column)))))
-       skeleton-filter 'sh-feature
+       skeleton-filter-function '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))
-  (run-hooks 'sh-mode-hook))
+  (sh-set-shell
+   (cond ((save-excursion
+            (goto-char (point-min))
+            (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)"))
+          (match-string 2))
+         ((not buffer-file-name)
+          sh-shell-file)
+         ;; Checks that use `buffer-file-name' follow.
+         ((string-match "\\.m?spec\\'" buffer-file-name)
+          "rpm")
+         ((string-match "[.]sh\\>" buffer-file-name)
+          "sh")
+         ((string-match "[.]bash\\>" buffer-file-name)
+          "bash")
+         ((string-match "[.]ksh\\>" buffer-file-name)
+          "ksh")
+         ((string-match "[.]csh\\>" buffer-file-name)
+          "csh")
+         (t
+          sh-shell-file))
+   nil nil)
+  (run-mode-hooks 'sh-mode-hook))
 
 ;;;###autoload
 (defalias 'shell-script-mode 'sh-mode)
@@ -1401,13 +1542,14 @@ with your script for an edit-interpret-debug cycle."
 (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
+  (sh-feature sh-font-lock-keywords-var
              (when (stringp (sh-feature sh-assignment-regexp))
                (lambda (list)
                  `((,(sh-feature sh-assignment-regexp)
                     1 font-lock-variable-name-face)
                    ,@keywords
-                   ,@list)))))
+                   ,@list
+                   ,@executable-font-lock-keywords)))))
 
 (defun sh-font-lock-keywords-1 (&optional builtins)
   "Function to get better fontification including keywords."
@@ -1424,10 +1566,10 @@ This adds rules for comments and assignments."
                         "\\>")
                (2 font-lock-keyword-face nil t)
                (6 font-lock-builtin-face))
-              ,@(sh-feature sh-font-lock-keywords-2)))
+              ,@(sh-feature sh-font-lock-keywords-var-2)))
         (,(concat keywords "\\)\\>")
          2 font-lock-keyword-face)
-        ,@(sh-feature sh-font-lock-keywords-1)))))
+        ,@(sh-feature sh-font-lock-keywords-var-1)))))
 
 (defun sh-font-lock-keywords-2 ()
   "Function to get better fontification including keywords and builtins."
@@ -1489,6 +1631,7 @@ This adds rules for comments and assignments."
      ("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).
 When used interactively, insert the proper starting #!-line,
@@ -1521,13 +1664,10 @@ Calls the value of `sh-set-shell-hook' if set."
     (if (eq tem t)
        (setq require-final-newline mode-require-final-newline)))
   (setq
-       comment-start-skip "#+[\t ]*"
-       local-abbrev-table sh-mode-abbrev-table
        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)
+       imenu-generic-expression (sh-feature sh-imenu-generic-expression))
   (make-local-variable 'sh-mode-syntax-table)
   (let ((tem (sh-feature sh-mode-syntax-table-input)))
     (setq sh-mode-syntax-table
@@ -1555,10 +1695,13 @@ Calls the value of `sh-set-shell-hook' if set."
        (message "Indentation setup for shell type %s" sh-shell))
     (message "No indentation for this shell type.")
     (setq indent-line-function 'sh-basic-indent-line))
+  (when font-lock-mode
+    (setq font-lock-set-defaults nil)
+    (font-lock-set-defaults)
+    (font-lock-fontify-buffer))
   (run-hooks 'sh-set-shell-hook))
 
 
-
 (defun sh-feature (alist &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.
@@ -1576,39 +1719,38 @@ Else indexing follows an inheritance logic which works in two ways:
     one shell to be derived from another 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."
+If FUNCTION is non-nil, it is called with one argument,
+the value thus obtained, and the result is used instead."
   (or (if (consp alist)
+         ;; Check for something that isn't a valid alist.
          (let ((l alist))
            (while (and l (consp (car l)))
              (setq l (cdr l)))
            (if l alist)))
-      (if function
-         (cdr (assoc (setq function (cons sh-shell function)) alist)))
-      (let ((sh-shell sh-shell)
-           elt val)
-       (while (and sh-shell
-                   (not (setq elt (assq sh-shell alist))))
-         (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 alist)))
-       (if (and (consp (setq val (cdr elt)))
-                (memq (car val) '(sh-append sh-modify)))
-           (setcdr elt
-                   (setq val
-                         (apply (car val)
-                                (let ((sh-shell (car (cdr val))))
-                                   (if (assq sh-shell alist)
-                                       (sh-feature alist)
-                                     (eval sh-shell)))
-                                (cddr val)))))
-       (if function
-           (nconc alist
-                  (list (cons function
-                              (setq sh-shell (car function)
-                                    val (funcall (cdr function) val))))))
-       val)))
+
+      (let ((orig-sh-shell sh-shell))
+       (let ((sh-shell sh-shell)
+             elt val)
+         (while (and sh-shell
+                     (not (setq elt (assq sh-shell alist))))
+           (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 alist)))
+         (setq val (cdr elt))
+         (if (and (consp val)
+                  (memq (car val) '(sh-append sh-modify)))
+             (setq val
+                   (apply (car val)
+                          ;; Refer to the value for a different shell,
+                          ;; as a kind of inheritance.
+                          (let ((sh-shell (car (cdr val))))
+                            (sh-feature alist))
+                          (cddr val))))
+         (if function
+             (setq sh-shell orig-sh-shell
+                   val (funcall function val)))
+         val))))
 
 
 
@@ -2031,11 +2173,20 @@ STRING       This is ignored for the purposes of calculating
        ;; 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 result
+                    (if (save-excursion
+                          (beginning-of-line)
+                          (not (memq (char-before (- (point) 2)) '(?\s ?\t))))
+                        ;; By convention, if the continuation \ is not
+                        ;; preceded by a SPC or a TAB it means that the line
+                        ;; is cut at a place where spaces cannot be freely
+                        ;; added/removed.  I.e. do not indent the line.
+                        (list '(= nil))
+                      ;; We assume the line being continued is already
+                      ;; properly indented...
+                      ;; (setq prev-line-end (sh-prev-line))
+                      (setq align-point (sh-prev-line nil))
+                      (list '(+ sh-indent-for-continuation))))
              (setq have-result t))
          (beginning-of-line)
          (skip-chars-forward " \t")
@@ -2128,10 +2279,9 @@ STRING        This is ignored for the purposes of calculating
       (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)))
-           ))
+         (setq result (list (if prev-line-end
+                                 (list t prev-line-end)
+                               (list '= 'sh-first-lines-indent)))))
 
       (if (eq result t)
          (setq result nil))
@@ -2310,46 +2460,45 @@ we go to the end of the previous line and do not check for continuations."
   ;;
   (if (bolp)
       nil
-    (let (c min-point
-         (start (point)))
-      (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)
-       ))
-      )))
+    (let ((start (point))
+          (min-point (if (sh-this-is-a-continuation)
+                         (sh-prev-line nil)
+                       (line-beginning-position))))
+      (skip-chars-backward " \t;" min-point)
+      (if (looking-at "\\s-*;;")
+          ;; (message "Found ;; !")
+          ";;"
+        (skip-chars-backward "^)}];\"'`({[" min-point)
+        (let ((c (if (> (point) min-point) (char-before))))
+          (sh-debug "stopping at %d c is %s  start=%d min-point=%d"
+                    (point) c start min-point)
+          (if (not (memq c '(?\n nil ?\;)))
+              ;; c     -- return a string
+              (char-to-string c)
+            ;; Return the leading keyword of the "command" we supposedly
+            ;; skipped over.  Maybe we skipped too far (e.g. past a `do' or
+            ;; `then' that precedes the actual command), so check whether
+            ;; we're looking at such a keyword and if so, move back forward.
+            (let ((boundary (point))
+                  kwd next)
+              (while
+                  (progn
+                    ;; Skip forward over white space newline and \ at eol.
+                    (skip-chars-forward " \t\n\\\\" start)
+                    (if (>= (point) start)
+                        (progn
+                          (sh-debug "point: %d >= start: %d" (point) start)
+                          nil)
+                      (if next (setq boundary next))
+                      (sh-debug "Now at %d   start=%d" (point) start)
+                      (setq kwd (sh-get-word))
+                      (if (member kwd (sh-feature sh-leading-keywords))
+                          (progn
+                            (setq next (point))
+                            t)
+                        nil))))
+              (goto-char boundary)
+              kwd)))))))
 
 
 (defun sh-this-is-a-continuation ()
@@ -2368,7 +2517,7 @@ If AND-MOVE is non-nil then move to end of word."
        (goto-char where))
     (prog1
        (buffer-substring (point)
-                         (progn (skip-chars-forward "^ \t\n;")(point)))
+                         (progn (skip-chars-forward "^ \t\n;&|()")(point)))
       (unless and-move
        (goto-char start)))))
 
@@ -2550,9 +2699,9 @@ If INFO is supplied it is used, else it is calculated from current line."
   (if (numberp blinkpos)
       (save-excursion
        (goto-char blinkpos)
-       (message msg)
+       (if msg (message "%s" msg) (message nil))
        (sit-for blink-matching-delay))
-    (message msg)))
+    (if msg (message "%s" msg) (message nil))))
 
 (defun sh-show-indent (arg)
   "Show the how the currently line would be indented.
@@ -2569,7 +2718,7 @@ we are indenting relative to, if applicable."
         (curr-indent (current-indentation))
         val msg)
     (if (stringp var)
-       (message (setq msg var))
+       (message "%s" (setq msg var))
       (setq val (sh-calculate-indent info))
 
       (if (eq curr-indent val)
@@ -2588,8 +2737,8 @@ we are indenting relative to, if applicable."
          (if (and info (listp (car info))
                   (eq (car (car info)) t))
              (sh-blink (nth 1 (car info))  msg)
-           (message msg)))
-      (message msg))
+           (message "%s" msg)))
+      (message "%s" msg))
     ))
 
 (defun sh-set-indent ()
@@ -2602,7 +2751,7 @@ for a new value for it."
         (var (sh-get-indent-var-for-line info))
         val old-val indent-val)
     (if (stringp var)
-       (message (format "Cannot set indent - %s" var))
+       (message "Cannot set indent - %s" var)
       (setq old-val (symbol-value var))
       (setq val (sh-read-variable var))
       (condition-case nil
@@ -2653,7 +2802,7 @@ unless optional argument ARG (the prefix when interactive) is non-nil."
           (curr-indent (current-indentation)))
       (cond
        ((stringp var)
-       (message (format "Cannot learn line - %s" var)))
+       (message "Cannot learn line - %s" var))
        ((eq var 'sh-indent-comment)
        ;; This is arbitrary...
        ;; - if curr-indent is 0, set to curr-indent
@@ -2693,11 +2842,9 @@ 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'."
-  (save-excursion
-    (set-buffer (get-buffer-create buffer))
+  (with-current-buffer (get-buffer-create buffer)
     (erase-buffer)
-    (occur-mode)
-    ))
+    (occur-mode)))
 
 
 (defun sh-mark-line (message point buffer &optional add-linenum occur-point)
@@ -2970,8 +3117,7 @@ This command can often take a long time to run."
          (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)
+       (with-current-buffer out-buffer
          (goto-char (point-min))
          (insert
           (format "Indentation values for buffer %s.\n" name)
@@ -3242,8 +3388,7 @@ 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)
+        (with-current-buffer sh-add-buffer
           (or sh-shell-variables-initialized
               (sh-shell-initialize-variables))
           (nconc (mapcar (lambda (var)
@@ -3374,7 +3519,7 @@ t means to return a list of all possible completions of STRING.
   "Insert code to setup temporary file handling.  See `sh-feature'."
   (bash sh-append ksh88)
   (csh (file-name-nondirectory (buffer-file-name))
-       "set tmp = /tmp/" str ".$$" \n
+       "set tmp = `mktemp -t " str ".XXXXXX`" \n
        "onintr exit" \n _
        (and (goto-char (point-max))
            (not (bolp))
@@ -3382,8 +3527,8 @@ t means to return a list of all possible completions of STRING.
        "exit:\n"
        "rm $tmp* >&/dev/null" > \n)
   (es (file-name-nondirectory (buffer-file-name))
-      > "local( signals = $signals sighup sigint; tmp = /tmp/" str
-      ".$pid ) {" \n
+      > "local( signals = $signals sighup sigint;" \n
+      > "tmp = `{ mktemp -t " str ".XXXXXX } ) {" \n
       > "catch @ e {" \n
       > "rm $tmp^* >[2]/dev/null" \n
       "throw $e" \n
@@ -3394,10 +3539,10 @@ t means to return a list of all possible completions of STRING.
   (ksh88 sh-modify sh
         7 "EXIT")
   (rc (file-name-nondirectory (buffer-file-name))
-      > "tmp = /tmp/" str ".$pid" \n
+      > "tmp = `{ mktemp -t " str ".XXXXXX }" \n
       "fn sigexit { rm $tmp^* >[2]/dev/null }" \n)
   (sh (file-name-nondirectory (buffer-file-name))
-      > "TMP=${TMPDIR:-/tmp}/" str ".$$" \n
+      > "TMP=`mktemp -t " str ".XXXXXX`" \n
       "trap \"rm $TMP* 2>/dev/null\" " ?0 \n))
 
 
@@ -3533,7 +3678,7 @@ The document is bounded by `sh-here-document-word'."
             (delim (replace-regexp-in-string "['\"]" ""
                                             sh-here-document-word)))
        (insert sh-here-document-word)
-       (or (eolp) (looking-at "[ \t]") (insert ? ))
+       (or (eolp) (looking-at "[ \t]") (insert ?\s))
        (end-of-line 1)
        (while
            (sh-quoted-p)