]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
Merge from emacs-24; up to 2012-12-06T01:39:03Z!monnier@iro.umontreal.ca
[gnu-emacs] / lisp / progmodes / sh-script.el
index 7b949134c6c1e75b357663167ac0b716c33b3ab4..da30edf272b5914315e045f4481aa01f53890050 100644 (file)
@@ -1,6 +1,7 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs
 
-;; Copyright (C) 1993-1997, 1999, 2001-2011  Free Software Foundation, Inc.
+;; Copyright (C) 1993-1997, 1999, 2001-2013 Free Software Foundation,
+;; Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Version: 2.0f
 
 (eval-when-compile
   (require 'skeleton)
-  (require 'cl)
+  (require 'cl-lib)
   (require 'comint))
 (require 'executable)
 
+(autoload 'comint-completion-at-point "comint")
+(autoload 'comint-filename-completion "comint")
+(autoload 'shell-command-completion "shell")
+(autoload 'shell-environment-variable-completion "shell")
+
 (defvar font-lock-comment-face)
 (defvar font-lock-set-defaults)
 (defvar font-lock-string-face)
@@ -326,7 +332,16 @@ shell it really is."
 
 (defcustom sh-imenu-generic-expression
   `((sh
-     . ((nil "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()" 2))))
+     . ((nil
+        ;; function FOO
+        ;; function FOO()
+         "^\\s-*function\\s-+\\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*\\(?:()\\)?"
+         1)
+       ;; FOO()
+       (nil
+        "^\\s-*\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()"
+        1)
+       )))
   "Alist of regular expressions for recognizing shell function definitions.
 See `sh-feature' and `imenu-generic-expression'."
   :type '(alist :key-type (symbol :tag "Shell")
@@ -460,16 +475,7 @@ This is buffer-local in every such buffer.")
     (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)
-    ;; FIXME: Use post-self-insert-hook.
-    (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)
-
-    (define-key map [remap complete-tag] 'comint-dynamic-complete)
+
     (define-key map [remap delete-backward-char]
       'backward-delete-char-untabify)
     (define-key map "\C-c:" 'sh-set-shell)
@@ -478,10 +484,10 @@ This is buffer-local in every such buffer.")
     (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map))
     (define-key menu-map [sh-learn-buffer-indent]
       '(menu-item "Learn buffer indentation" sh-learn-buffer-indent
-                 :help "Learn how to indent the buffer the way it currently is."))
+        :help "Learn how to indent the buffer the way it currently is."))
     (define-key menu-map [sh-learn-line-indent]
       '(menu-item "Learn line indentation" sh-learn-line-indent
-                 :help "Learn how to indent a line as it currently is indented"))
+        :help "Learn how to indent a line as it currently is indented"))
     (define-key menu-map [sh-show-indent]
       '(menu-item "Show indentation" sh-show-indent
                  :help "Show the how the current line would be indented"))
@@ -491,13 +497,9 @@ This is buffer-local in every such buffer.")
 
     (define-key menu-map [sh-pair]
       '(menu-item "Insert braces and quotes in pairs"
-                 (lambda ()
-                   (interactive)
-                   (require 'skeleton)
-                   (setq skeleton-pair (not skeleton-pair)))
-                 :button (:toggle . (and (boundp 'skeleton-pair)
-                                         skeleton-pair))
-                 :help "Inserting a brace or quote automatically inserts the matching pair"))
+        electric-pair-mode
+        :button (:toggle . (bound-and-true-p electric-pair-mode))
+        :help "Inserting a brace or quote automatically inserts the matching pair"))
 
     (define-key menu-map [sh-s0] '("--"))
     ;; Insert
@@ -506,7 +508,7 @@ This is buffer-local in every such buffer.")
                  :help "Insert a function definition"))
     (define-key menu-map [sh-add]
       '(menu-item "Addition..." sh-add
-                 :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell"))
+        :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell"))
     (define-key menu-map [sh-until]
       '(menu-item "Until Loop" sh-until
                  :help "Insert an until loop"))
@@ -537,16 +539,16 @@ This is buffer-local in every such buffer.")
     (define-key menu-map [sh-s1] '("--"))
     (define-key menu-map [sh-exec]
       '(menu-item "Execute region" sh-execute-region
-                 :help "Pass optional header and region to a subshell for noninteractive execution"))
+        :help "Pass optional header and region to a subshell for noninteractive execution"))
     (define-key menu-map [sh-exec-interpret]
       '(menu-item "Execute script..." executable-interpret
-                 :help "Run script with user-specified args, and collect output in a buffer"))
+        :help "Run script with user-specified args, and collect output in a buffer"))
     (define-key menu-map [sh-set-shell]
       '(menu-item "Set shell type..." sh-set-shell
                  :help "Set this buffer's shell to SHELL (a string)"))
     (define-key menu-map [sh-backslash-region]
       '(menu-item "Backslash region" sh-backslash-region
-                 :help "Insert, align, or delete end-of-line backslashes on the lines in the region."))
+        :help "Insert, align, or delete end-of-line backslashes on the lines in the region."))
     map)
   "Keymap used in Shell-Script mode.")
 
@@ -556,17 +558,19 @@ This is buffer-local in every such buffer.")
   "Value to use for `skeleton-pair-default-alist' in Shell-Script mode.")
 
 (defcustom sh-dynamic-complete-functions
-  '(shell-dynamic-complete-environment-variable
-    shell-dynamic-complete-command
-    comint-dynamic-complete-filename)
+  '(shell-environment-variable-completion
+    shell-command-completion
+    comint-filename-completion)
   "Functions for doing TAB dynamic completion."
   :type '(repeat function)
   :group 'sh-script)
 
 (defcustom sh-assignment-regexp
-  '((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
+  `((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
     ;; actually spaces are only supported in let/(( ... ))
-    (ksh88 . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=")
+    (ksh88 . ,(concat "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?"
+                      "[ \t]*\\(?:[-+*/%&|~^]\\|<<\\|>>\\)?="))
+    (bash . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?\\+?=")
     (rc . "\\<\\([[:alnum:]_*]+\\)[ \t]*=")
     (sh . "\\<\\([[:alnum:]_]+\\)="))
   "Regexp for the variable name and what may follow in an assignment.
@@ -889,7 +893,7 @@ See `sh-feature'.")
          font-lock-variable-name-face))
 
     (rc sh-append es)
-    (bash sh-append shell ("\\$(\\(\\sw+\\)" (1 'sh-quoted-exec t) ))
+    (bash sh-append sh ("\\$(\\(\\sw+\\)" (1 'sh-quoted-exec t) ))
     (sh sh-append shell
        ;; Variable names.
        ("\\$\\({#?\\)?\\([[:alpha:]_][[:alnum:]_]*\\|[-#?@!]\\)" 2
@@ -937,6 +941,17 @@ See `sh-feature'.")
     (concat "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\|[-/~._]\\)+\\)"
             sh-escaped-line-re "\\(\n\\)")))
 
+(defun sh--inside-noncommand-expression (pos)
+  (save-excursion
+    (let ((ppss (syntax-ppss pos)))
+      (when (nth 1 ppss)
+        (goto-char (nth 1 ppss))
+        (pcase (char-after)
+          ;; $((...)) or $[...] or ${...}.
+          (`?\( (and (eq ?\( (char-before))
+                     (eq ?\$ (char-before (1- (point))))))
+          ((or `?\{ `?\[) (eq ?\$ (char-before))))))))
+
 (defun sh-font-lock-open-heredoc (start string eol)
   "Determine the syntax of the \\n after a <<EOF.
 START is the position of <<.
@@ -945,7 +960,8 @@ 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 <<).
 Point is at the beginning of the next line."
   (unless (or (memq (char-before start) '(?< ?>))
-             (sh-in-comment-or-string start))
+             (sh-in-comment-or-string start)
+              (sh--inside-noncommand-expression 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))
@@ -985,7 +1001,7 @@ 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.
+  ;; rather flaky.
   (when (eq ?\" (nth 3 (syntax-ppss))) ; Check we matched an opening quote.
     ;; bingo we have a $( or a ` inside a ""
     (let (;; `state' can be: double-quote, backquote, code.
@@ -995,31 +1011,31 @@ subshells can nest."
       (while (and state (progn (skip-chars-forward "^'\\\\\"`$()" limit)
                                (< (point) limit)))
         ;; unescape " inside a $( ... ) construct.
-        (case (char-after)
-          (?\' (case state
-                 (double-quote nil)
-                 (t (forward-char 1) (skip-chars-forward "^'" limit))))
+        (pcase (char-after)
+          (?\' (pcase state
+                 (`double-quote nil)
+                 (_ (forward-char 1) (skip-chars-forward "^'" limit))))
           (?\\ (forward-char 1))
-          (?\" (case state
-                 (double-quote (setq state (pop states)))
-                 (t (push state states) (setq state 'double-quote)))
+          (?\" (pcase state
+                 (`double-quote (setq state (pop states)))
+                 (_ (push state states) (setq state 'double-quote)))
                (if state (put-text-property (point) (1+ (point))
                                             'syntax-table '(1))))
-          (?\` (case state
-                 (backquote (setq state (pop states)))
-                 (t (push state states) (setq state 'backquote))))
+          (?\` (pcase state
+                 (`backquote (setq state (pop states)))
+                 (_ (push state states) (setq state 'backquote))))
           (?\$ (if (not (eq (char-after (1+ (point))) ?\())
                    nil
                  (forward-char 1)
-                 (case state
-                   (t (push state states) (setq state 'code)))))
-          (?\( (case state
-                 (double-quote nil)
-                 (t (push state states) (setq state 'code))))
-          (?\) (case state
-                 (double-quote nil)
-                 (t (setq state (pop states)))))
-          (t (error "Internal error in sh-font-lock-quoted-subshell")))
+                 (pcase state
+                   (_ (push state states) (setq state 'code)))))
+          (?\( (pcase state
+                 (`double-quote nil)
+                 (_ (push state states) (setq state 'code))))
+          (?\) (pcase state
+                 (`double-quote nil)
+                 (_ (setq state (pop states)))))
+          (_ (error "Internal error in sh-font-lock-quoted-subshell")))
         (forward-char 1)))))
 
 
@@ -1030,50 +1046,57 @@ subshells can nest."
 (defun sh-font-lock-paren (start)
   (unless (nth 8 (syntax-ppss))
     (save-excursion
-      (goto-char start)
-      ;; Skip through all patterns
-      (while
-          (progn
-            (while
-                (progn
-                  (forward-comment (- (point-max)))
-                  (when (and (eolp) (sh-is-quoted-p (point)))
-                    (forward-char -1)
-                    t)))
-            ;; Skip through one pattern
-            (while
-                (or (/= 0 (skip-syntax-backward "w_"))
-                    (/= 0 (skip-chars-backward "-$=?[]*@/\\\\"))
-                    (and (sh-is-quoted-p (1- (point)))
-                         (goto-char (- (point) 2)))
-                    (when (memq (char-before) '(?\" ?\' ?\}))
-                      (condition-case nil (progn (backward-sexp 1) t)
-                        (error nil)))))
-            ;; Patterns can be preceded by an open-paren (Bug#1320).
-            (if (eq (char-before (point)) ?\()
+      (let ((open nil))
+        (goto-char start)
+        ;; Skip through all patterns
+        (while
+            (progn
+              (while
+                  (progn
+                    (forward-comment (- (point-max)))
+                    (when (and (eolp) (sh-is-quoted-p (point)))
+                      (forward-char -1)
+                      t)))
+              ;; Skip through one pattern
+              (while
+                  (or (/= 0 (skip-syntax-backward "w_"))
+                      (/= 0 (skip-chars-backward "-$=?[]*@/\\\\"))
+                      (and (sh-is-quoted-p (1- (point)))
+                           (goto-char (- (point) 2)))
+                      (when (memq (char-before) '(?\" ?\' ?\}))
+                        (condition-case nil (progn (backward-sexp 1) t)
+                          (error nil)))))
+              ;; Patterns can be preceded by an open-paren (bug#1320).
+              (when (eq (char-before (point)) ?\()
+                (backward-char 1)
+                (setq open (point)))
+              (while (progn
+                       (forward-comment (- (point-max)))
+                       ;; Maybe we've bumped into an escaped newline.
+                       (sh-is-quoted-p (point)))
                 (backward-char 1))
-            (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 (progn (backward-char 2)
-                   (if (> start (line-end-position))
-                       (put-text-property (point) (1+ start)
-                                          'syntax-multiline t))
-                   ;; FIXME: The `in' may just be a random argument to
-                   ;; a normal command rather than the real `in' keyword.
-                   ;; I.e. we should look back to try and find the
-                   ;; corresponding `case'.
-                   (and (looking-at ";[;&]\\|\\_<in")
-                        ;; ";; esac )" is a case that looks like a case-pattern
-                        ;; but it's really just a close paren after a case
-                        ;; statement.  I.e. if we skipped over `esac' just now,
-                        ;; we're not looking at a case-pattern.
-                        (not (looking-at "..[ \t\n]+esac[^[:word:]_]"))))
-        sh-st-punc))))
+              (when (eq (char-before) ?|)
+                (backward-char 1) t)))
+        (and (> (point) (1+ (point-min)))
+             (progn (backward-char 2)
+                    (if (> start (line-end-position))
+                        (put-text-property (point) (1+ start)
+                                           'syntax-multiline t))
+                    ;; FIXME: The `in' may just be a random argument to
+                    ;; a normal command rather than the real `in' keyword.
+                    ;; I.e. we should look back to try and find the
+                    ;; corresponding `case'.
+                    (and (looking-at ";[;&]\\|\\_<in")
+                         ;; ";; esac )" is a case that looks
+                         ;; like a case-pattern but it's really just a close
+                         ;; paren after a case statement.  I.e. if we skipped
+                         ;; over `esac' just now, we're not looking
+                         ;; at a case-pattern.
+                         (not (looking-at "..[ \t\n]+esac[^[:word:]_]"))))
+             (progn
+               (when open
+                 (put-text-property open (1+ open) 'syntax-table sh-st-punc))
+               sh-st-punc))))))
 
 (defun sh-font-lock-backslash-quote ()
   (if (eq (save-excursion (nth 3 (syntax-ppss (match-beginning 0)))) ?\')
@@ -1095,7 +1118,7 @@ subshells can nest."
     ;; 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 "_"))
+    ("\\(?:[^|&;<>()`\\\"' \t\n]\\|\\${\\)\\(#+\\)" (1 "_"))
     ;; In a '...' the backslash is not escaping.
     ("\\(\\\\\\)'" (1 (sh-font-lock-backslash-quote)))
     ;; Make sure $@ and $? are correctly recognized as sexps.
@@ -1104,16 +1127,15 @@ subshells can nest."
     (")" (0 (sh-font-lock-paren (match-beginning 0))))
     ;; Highlight (possibly nested) subshells inside "" quoted
     ;; regions correctly.
-    ("\"\\(?:\\(?:[^\\\"]\\|\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)"
+    ("\"\\(?:\\(?:[^\\\"]\\|\\\\.\\)*?\\)??\\(\\$(\\|`\\)"
      (1 (ignore
-         ;; Save excursion because we want to also apply other
-         ;; syntax-propertize rules within the affected region.
-         (if (nth 8 (syntax-ppss))
+         (if (nth 8 (save-excursion (syntax-ppss (match-beginning 0))))
              (goto-char (1+ (match-beginning 0)))
+           ;; Save excursion because we want to also apply other
+           ;; syntax-propertize rules within the affected region.
            (save-excursion
              (sh-font-lock-quoted-subshell end)))))))
    (point) end))
-
 (defun sh-font-lock-syntactic-face-function (state)
   (let ((q (nth 3 state)))
     (if q
@@ -1189,7 +1211,7 @@ This value is used for the `+' and `-' symbols in an indentation variable."
   :group 'sh-indentation)
 (put 'sh-basic-offset 'safe-local-variable 'integerp)
 
-(defcustom sh-indent-comment nil
+(defcustom sh-indent-comment t
   "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
@@ -1200,6 +1222,7 @@ a number means align to that column, e.g. 0 means first column."
          (const :tag "Indent as a normal line."  t)
          (integer :menu-tag "Indent to this col (0 means first col)."
                   :tag "Indent to column number.") )
+  :version "24.3"
   :group 'sh-indentation)
 
 
@@ -1378,10 +1401,10 @@ punctuation characters like '-'."
 
 
 (defconst sh-indent-supported
-  '((sh . t)
+  '((sh . sh)
     (csh . nil)
-    (rc . t))
-  "Shell types that shell indenting can do something with.")
+    (rc . rc))
+  "Indentation rule set to use for each shell type.")
 
 (defvar sh-indent-supported-here nil
   "Non-nil if we support indentation for the current buffer's shell type.")
@@ -1409,7 +1432,7 @@ 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.
+  "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'.
@@ -1463,9 +1486,8 @@ buffer indents as it currently is indented.
 \\[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 '', \"\", ``.
+`sh-electric-here-document-mode' controls whether insertion of two
+unquoted < insert a here document.
 
 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
@@ -1476,41 +1498,41 @@ with your script for an edit-interpret-debug cycle."
   (make-local-variable 'sh-shell-file)
   (make-local-variable 'sh-shell)
 
-  (set (make-local-variable 'skeleton-pair-default-alist)
-       sh-skeleton-pair-default-alist)
-  (set (make-local-variable 'skeleton-end-hook)
-       (lambda () (or (eolp) (newline) (indent-relative))))
-
-  (set (make-local-variable 'paragraph-start) (concat page-delimiter "\\|$"))
-  (set (make-local-variable 'paragraph-separate) paragraph-start)
-  (set (make-local-variable 'comment-start) "# ")
-  (set (make-local-variable 'comment-start-skip) "#+[\t ]*")
-  (set (make-local-variable 'local-abbrev-table) sh-mode-abbrev-table)
-  (set (make-local-variable 'comint-dynamic-complete-functions)
-       sh-dynamic-complete-functions)
+  (setq-local skeleton-pair-default-alist
+             sh-skeleton-pair-default-alist)
+  (setq-local skeleton-end-hook
+             (lambda () (or (eolp) (newline) (indent-relative))))
+
+  (setq-local paragraph-start (concat page-delimiter "\\|$"))
+  (setq-local paragraph-separate paragraph-start)
+  (setq-local comment-start "# ")
+  (setq-local comment-start-skip "#+[\t ]*")
+  (setq-local local-abbrev-table sh-mode-abbrev-table)
+  (setq-local comint-dynamic-complete-functions
+             sh-dynamic-complete-functions)
+  (add-hook 'completion-at-point-functions 'comint-completion-at-point nil t)
   ;; we can't look if previous line ended with `\'
-  (set (make-local-variable 'comint-prompt-regexp) "^[ \t]*")
-  (set (make-local-variable 'imenu-case-fold-search) nil)
-  (set (make-local-variable '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-face-function
-          . sh-font-lock-syntactic-face-function)))
-  (set (make-local-variable 'syntax-propertize-function)
-       #'sh-syntax-propertize-function)
+  (setq-local comint-prompt-regexp "^[ \t]*")
+  (setq-local imenu-case-fold-search nil)
+  (setq 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-face-function
+          . sh-font-lock-syntactic-face-function)))
+  (setq-local syntax-propertize-function #'sh-syntax-propertize-function)
   (add-hook 'syntax-propertize-extend-region-functions
             #'syntax-propertize-multiline 'append 'local)
-  (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`)))
-  (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p)
-  (set (make-local-variable 'skeleton-further-elements)
-       '((< '(- (min sh-indentation (current-column))))))
-  (set (make-local-variable 'skeleton-filter-function) 'sh-feature)
-  (set (make-local-variable 'skeleton-newline-indent-rigidly) t)
-  (set (make-local-variable 'sh-indent-supported-here) nil)
-  (set (make-local-variable 'defun-prompt-regexp)
-       (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
+  (sh-electric-here-document-mode 1)
+  (setq-local skeleton-pair-alist '((?` _ ?`)))
+  (setq-local skeleton-pair-filter-function 'sh-quoted-p)
+  (setq-local skeleton-further-elements
+             '((< '(- (min sh-indentation (current-column))))))
+  (setq-local skeleton-filter-function 'sh-feature)
+  (setq-local skeleton-newline-indent-rigidly t)
+  (setq-local defun-prompt-regexp
+             (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
   ;; Parse or insert magic number for exec, and set all variables depending
   ;; on the shell thus determined.
   (sh-set-shell
@@ -1518,23 +1540,15 @@ with your script for an edit-interpret-debug cycle."
             (goto-char (point-min))
             (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)"))
           (match-string 2))
-         ((not buffer-file-name)
-          sh-shell-file)
+         ((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")
-        ((equal (file-name-nondirectory buffer-file-name) ".profile")
-          "sh")
-         (t
-          sh-shell-file))
+         ((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")
+        ((equal (file-name-nondirectory buffer-file-name) ".profile") "sh")
+         (t sh-shell-file))
    nil nil))
 
 ;;;###autoload
@@ -1577,6 +1591,431 @@ This adds rules for comments and assignments."
   "Function to get better fontification including keywords and builtins."
   (sh-font-lock-keywords-1 t))
 
+;;; Indentation and navigation with SMIE.
+
+(require 'smie)
+
+;; The SMIE code should generally be preferred, but it currently does not obey
+;; the various indentation custom-vars, and it misses some important features
+;; of the old code, mostly: sh-learn-line/buffer-indent, sh-show-indent,
+;; sh-name/save/load-style.
+(defvar sh-use-smie nil
+  "Whether to use the SMIE code for navigation and indentation.")
+
+(defun sh-smie--keyword-p (tok)
+  "Non-nil if TOK (at which we're looking) really is a keyword."
+  (let ((prev (funcall smie-backward-token-function)))
+    (if (zerop (length prev))
+        (looking-back "\\s(" (1- (point)))
+      (assoc prev smie-grammar))))
+
+(defun sh-smie--newline-semi-p (&optional tok)
+  "Return non-nil if a newline should be treated as a semi-colon.
+Here we assume that a newline should be treated as a semi-colon unless it
+comes right after a special keyword.
+This function does not pay attention to line-continuations.
+If TOK is nil, point should be before the newline; otherwise, TOK is the token
+before the newline and in that case point should be just before the token."
+  (save-excursion
+    (unless tok
+      (setq tok (funcall smie-backward-token-function)))
+    (if (and (zerop (length tok))
+             (looking-back "\\s(" (1- (point))))
+        nil
+      (not (numberp (nth 2 (assoc tok smie-grammar)))))))
+
+;;;; SMIE support for `sh'.
+
+(defconst sh-smie-sh-grammar
+  (smie-prec2->grammar
+   (smie-bnf->prec2
+    '((exp)                    ;A constant, or a $var, or a sequence of them...
+      (cmd ("case" exp "in" branches "esac")
+           ("if" cmd "then" cmd "fi")
+           ("if" cmd "then" cmd "else" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd "else" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd
+                                "elif" cmd "then" cmd "else" cmd "fi")
+           ("while" cmd "do" cmd "done")
+           ("until" cmd "do" cmd "done")
+           ("for" exp "in" cmd "do" cmd "done")
+           ("for" exp "do" cmd "done")
+           ("select" exp "in" cmd "do" cmd "done")   ;bash&zsh&ksh88.
+           ("repeat" exp "do" cmd "done")            ;zsh.
+           (exp "always" exp)                        ;zsh.
+           (cmd "|" cmd) (cmd "|&" cmd)
+           (cmd "&&" cmd) (cmd "||" cmd)
+           (cmd ";" cmd) (cmd "&" cmd))
+      (rpattern (rpattern "|" rpattern))
+      (pattern (rpattern) ("case-(" rpattern))
+      (branches (branches ";;" branches)
+                (branches ";&" branches) (branches ";;&" branches) ;bash.
+                (pattern "case-)" cmd)))
+    '((assoc ";;" ";&" ";;&"))
+    '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+(defconst sh-smie--sh-operators
+  (delq nil (mapcar (lambda (x)
+                      (setq x (car x))
+                      (and (stringp x)
+                           (not (string-match "\\`[a-z]" x))
+                           x))
+                    sh-smie-sh-grammar)))
+
+(defconst sh-smie--sh-operators-re (regexp-opt sh-smie--sh-operators))
+(defconst sh-smie--sh-operators-back-re
+  (concat "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*"
+          "\\(" sh-smie--sh-operators-re "\\)"))
+
+(defun sh-smie--sh-keyword-in-p ()
+  "Assuming we're looking at \"in\", return non-nil if it's a keyword.
+Does not preserve point."
+  (let ((forward-sexp-function nil)
+        (words nil)                     ;We've seen words.
+        (newline nil)                   ;We've seen newlines after the words.
+        (res nil)
+        prev)
+    (while (not res)
+      (setq prev (funcall smie-backward-token-function))
+      (cond
+       ((zerop (length prev))
+        (if newline
+            (progn (cl-assert words) (setq res 'word))
+          (setq words t)
+          (condition-case nil
+              (forward-sexp -1)
+            (scan-error (setq res 'unknown)))))
+       ((equal prev ";")
+        (if words (setq newline t)
+          (setq res 'keyword)))
+       ((member prev '("case" "for" "select")) (setq res 'keyword))
+       ((assoc prev smie-grammar) (setq res 'word))
+       (t
+        (if newline
+            (progn (cl-assert words) (setq res 'word))
+          (setq words t)))))
+    (eq res 'keyword)))
+
+(defun sh-smie--sh-keyword-p (tok)
+  "Non-nil if TOK (at which we're looking) really is a keyword."
+  (if (equal tok "in")
+      (sh-smie--sh-keyword-in-p)
+    (sh-smie--keyword-p tok)))
+
+(defun sh-smie-sh-forward-token ()
+  (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
+           (save-excursion
+             (skip-chars-backward " \t")
+             (not (bolp))))
+      (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
+          ;; Right before a here-doc.
+          (let ((forward-sexp-function nil))
+            (forward-sexp 1)
+            ;; Pretend the here-document is a "newline representing a
+            ;; semi-colon", since the here-doc otherwise covers the newline(s).
+            ";")
+        (let ((semi (sh-smie--newline-semi-p)))
+          (forward-line 1)
+          (if semi ";"
+            (sh-smie-sh-forward-token))))
+    (forward-comment (point-max))
+    (cond
+     ((looking-at "\\\\\n") (forward-line 1) (sh-smie-sh-forward-token))
+     ((looking-at sh-smie--sh-operators-re)
+      (goto-char (match-end 0))
+      (let ((tok (match-string-no-properties 0)))
+        (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
+                 (looking-at "[ \t]*\\(?:#\\|$\\)"))
+            (forward-line 1))
+        tok))
+     (t
+      (let* ((pos (point))
+             (tok (smie-default-forward-token)))
+        (cond
+         ((equal tok ")") "case-)")
+         ((equal tok "(") "case-(")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not
+                (save-excursion
+                  (goto-char pos)
+                  (sh-smie--sh-keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie--looking-back-at-continuation-p ()
+  (save-excursion
+    (and (if (eq (char-before) ?\n) (progn (forward-char -1) t) (eolp))
+         (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
+                       (line-beginning-position)))))
+
+(defun sh-smie-sh-backward-token ()
+  (let ((bol (line-beginning-position))
+        pos tok)
+    (forward-comment (- (point)))
+    (cond
+     ((and (bolp) (not (bobp))
+           (equal (syntax-after (1- (point))) (string-to-syntax "|"))
+           (not (nth 3 (syntax-ppss))))
+      ;; Right after a here-document.
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        ;; Pretend the here-document is a "newline representing a
+        ;; semi-colon", since the here-doc otherwise covers the newline(s).
+        ";"))
+     ((< (point) bol)
+      (cond
+       ((sh-smie--looking-back-at-continuation-p)
+        (forward-char -1)
+        (funcall smie-backward-token-function))
+       ((sh-smie--newline-semi-p) ";")
+       (t (funcall smie-backward-token-function))))
+     ((looking-back sh-smie--sh-operators-back-re
+                    (line-beginning-position) 'greedy)
+      (goto-char (match-beginning 1))
+      (match-string-no-properties 1))
+     (t
+      (let ((tok (smie-default-backward-token)))
+        (cond
+         ((equal tok ")") "case-)")
+         ((equal tok "(") "case-(")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not (save-excursion (sh-smie--sh-keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defcustom sh-indent-after-continuation t
+  "If non-nil, try to make sure text is indented after a line continuation."
+  :version "24.3"
+  :type 'boolean
+  :group 'sh-indentation)
+
+(defun sh-smie--continuation-start-indent ()
+  "Return the initial indentation of a continued line.
+May return nil if the line should not be treated as continued."
+  (save-excursion
+    (forward-line -1)
+    (unless (sh-smie--looking-back-at-continuation-p)
+      (current-indentation))))
+
+(defun sh-smie-sh-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) sh-indentation)
+    (`(:after . "case-)") (or sh-indentation smie-indent-basic))
+    ((and `(:before . ,_)
+          (guard (when sh-indent-after-continuation
+                   (save-excursion
+                     (ignore-errors
+                       (skip-chars-backward " \t")
+                       (sh-smie--looking-back-at-continuation-p))))))
+     ;; After a line-continuation, make sure the rest is indented.
+     (let* ((sh-indent-after-continuation nil)
+            (indent (smie-indent-calculate))
+            (initial (sh-smie--continuation-start-indent)))
+       (when (and (numberp indent) (numberp initial)
+                  (<= indent initial))
+         `(column . ,(+ initial sh-indentation)))))
+    (`(:before . ,(or `"(" `"{" `"["))
+     (if (smie-rule-hanging-p) (smie-rule-parent)))
+    ;; FIXME: Maybe this handling of ;; should be made into
+    ;; a smie-rule-terminator function that takes the substitute ";" as arg.
+    (`(:before . ,(or `";;" `";&" `";;&"))
+     (if (and (smie-rule-bolp) (looking-at ";;?&?[ \t]*\\(#\\|$\\)"))
+         (cons 'column (smie-indent-keyword ";"))
+       (smie-rule-separator kind)))
+    (`(:after . ,(or `";;" `";&" `";;&"))
+     (with-demoted-errors
+       (smie-backward-sexp token)
+       (cons 'column
+             (if (or (smie-rule-bolp)
+                     (save-excursion
+                       (and (member (funcall smie-backward-token-function)
+                                    '("in" ";;"))
+                            (smie-rule-bolp))))
+                 (current-column)
+               (smie-indent-calculate)))))
+    (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
+    ))
+
+;; (defconst sh-smie-csh-grammar
+;;   (smie-prec2->grammar
+;;    (smie-bnf->prec2
+;;     '((exp)                   ;A constant, or a $var, or a sequence of them…
+;;       (elseifcmd (cmd)
+;;                  (cmd "else" "else-if" exp "then" elseifcmd))
+;;       (cmd ("switch" branches "endsw")
+;;            ("if" exp)
+;;            ("if" exp "then" cmd "endif")
+;;            ("if" exp "then" cmd "else" cmd "endif")
+;;            ("if" exp "then" elseifcmd "endif")
+;;            ;; ("if" exp "then" cmd "else" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
+;;            ;;                      "else" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
+;;            ;;                      "else" "if" exp "then" cmd "endif")
+;;            ("while" cmd "end")
+;;            ("foreach" cmd "end")
+;;            (cmd "|" cmd) (cmd "|&" cmd)
+;;            (cmd "&&" cmd) (cmd "||" cmd)
+;;            (cmd ";" cmd) (cmd "&" cmd))
+;;       ;; This is a lie, but (combined with the corresponding disambiguation
+;;       ;; rule) it makes it more clear that `case' and `default' are the key
+;;       ;; separators and the `:' is a secondary tokens.
+;;       (branches (branches "case" branches)
+;;                 (branches "default" branches)
+;;                 (exp ":" branches)))
+;;     '((assoc "else" "then" "endif"))
+;;     '((assoc "case" "default") (nonassoc ":"))
+;;     '((assoc ";;" ";&" ";;&"))
+;;     '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+;;;; SMIE support for `rc'.
+
+(defconst sh-smie-rc-grammar
+  (smie-prec2->grammar
+   (smie-bnf->prec2
+    '((exp)                    ;A constant, or a $var, or a sequence of them...
+      (cmd (cmd "case" cmd)
+           ("if" exp)
+           ("switch" exp)
+           ("for" exp) ("while" exp)
+           (cmd "|" cmd) (cmd "|&" cmd)
+           (cmd "&&" cmd) (cmd "||" cmd)
+           (cmd ";" cmd) (cmd "&" cmd))
+      (pattern (pattern "|" pattern))
+      (branches (branches ";;" branches)
+                (branches ";&" branches) (branches ";;&" branches) ;bash.
+                (pattern "case-)" cmd)))
+    '((assoc ";;" ";&" ";;&"))
+    '((assoc "case") (assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+(defun sh-smie--rc-after-special-arg-p ()
+  "Check if we're after the first arg of an if/while/for/... construct.
+Returns the construct's token and moves point before it, if so."
+  (forward-comment (- (point)))
+  (when (looking-back ")\\|\\_<not" (- (point) 3))
+    (ignore-errors
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        (car (member (funcall smie-backward-token-function)
+                     '("if" "for" "switch" "while")))))))
+
+(defun sh-smie--rc-newline-semi-p ()
+  "Return non-nil if a newline should be treated as a semi-colon.
+Point should be before the newline."
+  (save-excursion
+    (let ((tok (funcall smie-backward-token-function)))
+      (if (or (when (equal tok "not") (forward-word 1) t)
+              (and (zerop (length tok)) (eq (char-before) ?\))))
+          (not (sh-smie--rc-after-special-arg-p))
+        (sh-smie--newline-semi-p tok)))))
+
+(defun sh-smie-rc-forward-token ()
+  ;; FIXME: Code duplication with sh-smie-sh-forward-token.
+  (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
+           (save-excursion
+             (skip-chars-backward " \t")
+             (not (bolp))))
+      (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
+          ;; Right before a here-doc.
+          (let ((forward-sexp-function nil))
+            (forward-sexp 1)
+            ;; Pretend the here-document is a "newline representing a
+            ;; semi-colon", since the here-doc otherwise covers the newline(s).
+            ";")
+        (let ((semi (sh-smie--rc-newline-semi-p)))
+          (forward-line 1)
+          (if semi ";"
+            (sh-smie-rc-forward-token))))
+    (forward-comment (point-max))
+    (cond
+     ((looking-at "\\\\\n") (forward-line 1) (sh-smie-rc-forward-token))
+     ;; ((looking-at sh-smie--rc-operators-re)
+     ;;  (goto-char (match-end 0))
+     ;;  (let ((tok (match-string-no-properties 0)))
+     ;;    (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
+     ;;             (looking-at "[ \t]*\\(?:#\\|$\\)"))
+     ;;        (forward-line 1))
+     ;;    tok))
+     (t
+      (let* ((pos (point))
+             (tok (smie-default-forward-token)))
+        (cond
+         ;; ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not
+                (save-excursion
+                  (goto-char pos)
+                  (sh-smie--keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie-rc-backward-token ()
+  ;; FIXME: Code duplication with sh-smie-sh-backward-token.
+  (let ((bol (line-beginning-position))
+        pos tok)
+    (forward-comment (- (point)))
+    (cond
+     ((and (bolp) (not (bobp))
+           (equal (syntax-after (1- (point))) (string-to-syntax "|"))
+           (not (nth 3 (syntax-ppss))))
+      ;; Right after a here-document.
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        ;; Pretend the here-document is a "newline representing a
+        ;; semi-colon", since the here-doc otherwise covers the newline(s).
+        ";"))
+     ((< (point) bol)                   ;We skipped over a newline.
+      (cond
+       ;; A continued line.
+       ((and (eolp)
+             (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
+                           (line-beginning-position)))
+        (forward-char -1)
+        (funcall smie-backward-token-function))
+       ((sh-smie--rc-newline-semi-p) ";")
+       (t (funcall smie-backward-token-function))))
+     ;; ((looking-back sh-smie--sh-operators-back-re
+     ;;                (line-beginning-position) 'greedy)
+     ;;  (goto-char (match-beginning 1))
+     ;;  (match-string-no-properties 1))
+     (t
+      (let ((tok (smie-default-backward-token)))
+        (cond
+         ;; ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not (save-excursion (sh-smie--keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie-rc-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) sh-indentation)
+    ;; (`(:after . "case") (or sh-indentation smie-indent-basic))
+    (`(:after . ";") (if (smie-rule-parent-p "case")
+                         (smie-rule-parent sh-indentation)))
+    (`(:before . "{")
+     (save-excursion
+       (when (sh-smie--rc-after-special-arg-p)
+         `(column . ,(current-column)))))
+    (`(:before . ,(or `"(" `"{" `"["))
+     (if (smie-rule-hanging-p) (smie-rule-parent)))
+    ;; FIXME: SMIE parses "if (exp) cmd" as "(if ((exp) cmd))" so "cmd" is
+    ;; treated as an arg to (exp) by default, which indents it all wrong.
+    ;; To handle it right, we should extend smie-indent-exps so that the
+    ;; preceding keyword can give special rules.  Currently the only special
+    ;; rule we have is the :list-intro hack, which we use here to align "cmd"
+    ;; with "(exp)", which is rarely the right thing to do, but is better
+    ;; than nothing.
+    (`(:list-intro . ,(or `"for" `"if" `"while")) t)
+    ))
+
+;;; End of SMIE code.
 
 (defvar sh-regexp-for-done nil
   "A buffer-local regexp to match opening keyword for done.")
@@ -1665,30 +2104,39 @@ Calls the value of `sh-set-shell-hook' if set."
            (executable-set-magic shell (sh-feature sh-shell-arg)
                                  no-query-flag insert-flag)))
   (setq mode-line-process (format "[%s]" sh-shell))
-  (set (make-local-variable 'sh-shell-variables) nil)
-  (set (make-local-variable 'sh-shell-variables-initialized) nil)
-  (set (make-local-variable 'imenu-generic-expression)
-       (sh-feature sh-imenu-generic-expression))
+  (setq-local sh-shell-variables nil)
+  (setq-local sh-shell-variables-initialized nil)
+  (setq-local imenu-generic-expression
+             (sh-feature sh-imenu-generic-expression))
   (let ((tem (sh-feature sh-mode-syntax-table-input)))
     (when tem
-      (set (make-local-variable 'sh-mode-syntax-table)
-           (apply 'sh-mode-syntax-table tem))
+      (setq-local sh-mode-syntax-table
+                 (apply 'sh-mode-syntax-table tem))
       (set-syntax-table sh-mode-syntax-table)))
   (dolist (var (sh-feature sh-variables))
     (sh-remember-variable var))
-  (if (setq sh-indent-supported-here (sh-feature sh-indent-supported))
+  (if (setq-local 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.
-       (set (make-local-variable 'indent-line-function) 'sh-indent-line)
+        (if sh-use-smie
+            (let ((mksym (lambda (name)
+                           (intern (format "sh-smie-%s-%s"
+                                           sh-indent-supported-here name)))))
+              (smie-setup (symbol-value (funcall mksym "grammar"))
+                          (funcall mksym "rules")
+                          :forward-token  (funcall mksym "forward-token")
+                          :backward-token (funcall mksym "backward-token")))
+          (setq-local parse-sexp-lookup-properties t)
+          (setq-local sh-kw-alist (sh-feature sh-kw))
+          (let ((regexp (sh-feature sh-kws-for-done)))
+            (if regexp
+                (setq-local 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-local indent-line-function 'sh-indent-line))
        (if sh-make-vars-local
            (sh-make-vars-local))
        (message "Indentation setup for shell type %s" sh-shell))
@@ -1875,7 +2323,7 @@ region, clear header."
 \f
 ;; Indentation stuff.
 (defun sh-must-support-indent ()
-  "*Signal an error if the shell type for this buffer is not supported.
+  "Signal an error if the shell type for this buffer is not supported.
 Also, the buffer must be in Shell-script mode."
   (unless sh-indent-supported-here
     (error "This buffer's shell does not support indentation through Emacs")))
@@ -2884,7 +3332,7 @@ 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.")
+  "An abnormal hook, called with an alist of learned variables.")
 ;; Example of how to use sh-learned-buffer-hook
 ;;
 ;; (defun what-i-learned (list)
@@ -3236,8 +3684,9 @@ overwritten if
 (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*")))
+  (interactive
+   (list
+    (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
   (with-current-buffer (get-buffer-create buff)
     (goto-char (point-max))
     (insert "\n")
@@ -3653,10 +4102,13 @@ option followed by a colon `:' if the option accepts an argument."
 (defun sh-maybe-here-document (arg)
   "Insert self.  Without prefix, following unquoted `<' inserts here document.
 The document is bounded by `sh-here-document-word'."
+  (declare (obsolete sh-electric-here-document-mode "24.3"))
   (interactive "*P")
   (self-insert-command (prefix-numeric-value arg))
-  (or arg
-      (not (looking-back "[^<]<<"))
+  (or arg (sh--maybe-here-document)))
+
+(defun sh--maybe-here-document ()
+  (or (not (looking-back "[^<]<<"))
       (save-excursion
        (backward-char 2)
        (sh-quoted-p))
@@ -3677,30 +4129,24 @@ The document is bounded by `sh-here-document-word'."
           (insert ?\n tabs (replace-regexp-in-string
                             "\\`-?[ \t]*" "" delim))))))
 
+(define-minor-mode sh-electric-here-document-mode
+  "Make << insert a here document skeleton."
+  nil nil nil
+  (if sh-electric-here-document-mode
+      (add-hook 'post-self-insert-hook #'sh--maybe-here-document nil t)
+    (remove-hook 'post-self-insert-hook #'sh--maybe-here-document t)))
 \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-beginning-of-command ()
+  ;; FIXME: Redefine using SMIE.
   "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 ()
+  ;; FIXME: Redefine using SMIE.
   "Move point to successive ends of commands."
   (interactive)
   (if (re-search-forward sh-end-of-command nil t)