]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
Update copyright year to 2014 by running admin/update-copyright.
[gnu-emacs] / lisp / progmodes / sh-script.el
index daa83620051ddd21efe0fbe16d1eb4f6410a741f..3ae9a21c3e499f4ee0277c0281b66c3882f8cfe0 100644 (file)
@@ -1,6 +1,7 @@
-;;; sh-script.el --- shell-script editing commands for Emacs
+;;; sh-script.el --- shell-script editing commands for Emacs  -*- lexical-binding:t -*-
 
-;; Copyright (C) 1993-1997, 1999, 2001-2012  Free Software Foundation, Inc.
+;; Copyright (C) 1993-1997, 1999, 2001-2014 Free Software Foundation,
+;; Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Version: 2.0f
@@ -334,11 +335,11 @@ shell it really is."
      . ((nil
         ;; function FOO
         ;; function FOO()
-         "^\\s-*function\\s-+\\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*\\(?:()\\)?"
+         "^\\s-*function\\s-+\\\([[:alpha:]_][[:alnum:]_]*\\)\\s-*\\(?:()\\)?"
          1)
        ;; FOO()
        (nil
-        "^\\s-*\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()"
+        "^\\s-*\\([[:alpha:]_][[:alnum:]_]*\\)\\s-*()"
         1)
        )))
   "Alist of regular expressions for recognizing shell function definitions.
@@ -352,6 +353,28 @@ See `sh-feature' and `imenu-generic-expression'."
   :group 'sh-script
   :version "20.4")
 
+(defun sh-current-defun-name ()
+  "Find the name of function or variable at point.
+For use in `add-log-current-defun-function'."
+  (save-excursion
+    (end-of-line)
+    (when (re-search-backward
+          (concat "\\(?:"
+                  ;; function FOO
+                  ;; function FOO()
+                  "^\\s-*function\\s-+\\\([[:alpha:]_][[:alnum:]_]*\\)\\s-*\\(?:()\\)?"
+                  "\\)\\|\\(?:"
+                  ;; FOO()
+                  "^\\s-*\\([[:alpha:]_][[:alnum:]_]*\\)\\s-*()"
+                  "\\)\\|\\(?:"
+                  ;; FOO=
+                  "^\\([[:alpha:]_][[:alnum:]_]*\\)="
+                  "\\)")
+          nil t)
+      (or (match-string-no-properties 1)
+         (match-string-no-properties 2)
+         (match-string-no-properties 3)))))
+
 (defvar sh-shell-variables nil
   "Alist of shell variable names that should be included in completion.
 These are used for completion in addition to all the variables named
@@ -474,6 +497,9 @@ 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)
+    (define-key map "\C-c\C-n" 'sh-send-line-or-region-and-step)
+    (define-key map "\C-c\C-d" 'sh-cd-here)
+    (define-key map "\C-c\C-z" 'sh-show-shell)
 
     (define-key map [remap delete-backward-char]
       'backward-delete-char-untabify)
@@ -647,7 +673,9 @@ removed when closing the here document."
          "." "alias" "bg" "bind" "builtin" "caller" "compgen" "complete"
           "declare" "dirs" "disown" "enable" "fc" "fg" "help" "history"
           "jobs" "kill" "let" "local" "popd" "printf" "pushd" "shopt"
-          "source" "suspend" "typeset" "unalias")
+          "source" "suspend" "typeset" "unalias"
+          ;; bash4
+          "mapfile" "readarray")
 
     ;; The next entry is only used for defining the others
     (bourne sh-append shell
@@ -711,6 +739,7 @@ implemented as aliases.  See `sh-feature'."
   :type '(repeat (cons (symbol :tag "Shell")
                       (choice (repeat string)
                               (sexp :format "Evaluate: %v"))))
+  :version "24.4"                       ; bash4 additions
   :group 'sh-script)
 
 
@@ -914,6 +943,7 @@ See `sh-feature'.")
     (rpm sh-append rpm2
         ("%{?\\(\\sw+\\)"  1 font-lock-keyword-face))
     (rpm2 sh-append shell
+         ("^Summary:\\(.*\\)$" (1 font-lock-doc-face t))
          ("^\\(\\sw+\\):"  1 font-lock-variable-name-face)))
   "Default expressions to highlight in Shell Script modes.  See `sh-feature'.")
 
@@ -940,6 +970,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 <<.
@@ -948,7 +989,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))
@@ -1426,6 +1468,63 @@ command `sh-make-vars-local'.
 The default is t because I assume that in one Emacs session one is
 frequently editing existing scripts with different styles.")
 
+\f
+;; inferior shell interaction
+;; TODO: support multiple interactive shells
+(defvar-local sh-shell-process nil
+  "The inferior shell process for interaction.")
+
+(defvar explicit-shell-file-name)
+
+(defun sh-shell-process (force)
+  "Get a shell process for interaction.
+If FORCE is non-nil and no process found, create one."
+  (if (process-live-p sh-shell-process)
+      sh-shell-process
+    (setq sh-shell-process
+          (let ((found nil) proc
+                (procs (process-list)))
+            (while (and (not found) procs
+                        (process-live-p (setq proc (pop procs)))
+                        (process-command proc))
+              (when (string-equal sh-shell (file-name-nondirectory
+                                            (car (process-command proc))))
+                (setq found proc)))
+            (or found
+                (and force
+                     (get-buffer-process
+                      (let ((explicit-shell-file-name sh-shell-file))
+                        (shell)))))))))
+
+(defun sh-show-shell ()
+  "Pop the shell interaction buffer."
+  (interactive)
+  (pop-to-buffer (process-buffer (sh-shell-process t))))
+
+(defun sh-send-text (text)
+  "Send the text to the `sh-shell-process'."
+  (comint-send-string (sh-shell-process t) (concat text "\n")))
+
+(defun sh-cd-here ()
+  "Change directory in the current interaction shell to the current one."
+  (interactive)
+  (sh-send-text (concat "cd " default-directory)))
+
+(defun sh-send-line-or-region-and-step ()
+  "Send the current line to the inferior shell and step to the next line.
+When the region is active, send the region instead."
+  (interactive)
+  (let (from to end)
+    (if (use-region-p)
+        (setq from (region-beginning)
+              to (region-end)
+              end to)
+      (setq from (line-beginning-position)
+            to (line-end-position)
+            end (1+ to)))
+    (sh-send-text (buffer-substring-no-properties from to))
+    (goto-char end)))
+
 \f
 ;; mode-command and utility functions
 
@@ -1485,42 +1584,44 @@ 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 (concat 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)
   (sh-electric-here-document-mode 1)
-  (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 'defun-prompt-regexp)
-       (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
+  (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]+\\)"))
+  (setq-local add-log-current-defun-function #'sh-current-defun-name)
+  (add-hook 'completion-at-point-functions
+            #'sh-completion-at-point-function nil t)
   ;; Parse or insert magic number for exec, and set all variables depending
   ;; on the shell thus determined.
   (sh-set-shell
@@ -1579,6 +1680,41 @@ This adds rules for comments and assignments."
   "Function to get better fontification including keywords and builtins."
   (sh-font-lock-keywords-1 t))
 
+;;; Completion
+
+(defun sh--vars-before-point ()
+  (save-excursion
+    (let ((vars ()))
+      (while (re-search-backward "^[ \t]*\\([[:alnum:]_]+\\)=" nil t)
+        (push (match-string 1) vars))
+      vars)))
+
+;; (defun sh--var-completion-table (string pred action)
+;;   (complete-with-action action (sh--vars-before-point) string pred))
+
+(defun sh--cmd-completion-table (string pred action)
+  (let ((cmds
+         (append (when (fboundp 'imenu--make-index-alist)
+                   (mapcar #'car (imenu--make-index-alist)))
+                 (mapcar (lambda (v) (concat v "="))
+                         (sh--vars-before-point))
+                 (locate-file-completion-table
+                  exec-path exec-suffixes string pred t)
+                 '("if" "while" "until" "for"))))
+    (complete-with-action action cmds string pred)))
+
+(defun sh-completion-at-point-function ()
+  (save-excursion
+    (skip-chars-forward "[:alnum:]_")
+    (let ((end (point))
+          (_ (skip-chars-backward "[:alnum:]_"))
+          (start (point)))
+      (cond
+       ((eq (char-before) ?$)
+        (list start end (sh--vars-before-point)))
+       ((sh-smie--keyword-p)
+        (list start end #'sh--cmd-completion-table))))))
+
 ;;; Indentation and navigation with SMIE.
 
 (require 'smie)
@@ -1587,14 +1723,16 @@ This adds rules for comments and assignments."
 ;; 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
+(defvar sh-use-smie t
   "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."
+(defun sh-smie--keyword-p ()
+  "Non-nil if we're at a keyword position.
+A keyword position is one where if we're looking at something that looks
+like a keyword, then it is a keyword."
   (let ((prev (funcall smie-backward-token-function)))
     (if (zerop (length prev))
-        (looking-back "\\s(" (1- (point)))
+        (looking-back "\\`\\|\\s(" (1- (point)))
       (assoc prev smie-grammar))))
 
 (defun sh-smie--newline-semi-p (&optional tok)
@@ -1668,12 +1806,14 @@ Does not preserve point."
       (setq prev (funcall smie-backward-token-function))
       (cond
        ((zerop (length prev))
-        (if newline
-            (progn (cl-assert words) (setq res 'word))
+       (cond
+        (newline (cl-assert words) (setq res 'word))
+        ((bobp) (setq res 'word))
+        (t
           (setq words t)
           (condition-case nil
               (forward-sexp -1)
-            (scan-error (setq res 'unknown)))))
+            (scan-error (setq res 'unknown))))))
        ((equal prev ";")
         (if words (setq newline t)
           (setq res 'keyword)))
@@ -1689,7 +1829,7 @@ Does not preserve point."
   "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)))
+    (sh-smie--keyword-p)))
 
 (defun sh-smie-sh-forward-token ()
   (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
@@ -1705,7 +1845,7 @@ Does not preserve point."
             ";")
         (let ((semi (sh-smie--newline-semi-p)))
           (forward-line 1)
-          (if semi ";"
+          (if (or semi (eobp)) ";"
             (sh-smie-sh-forward-token))))
     (forward-comment (point-max))
     (cond
@@ -1739,8 +1879,7 @@ Does not preserve point."
                        (line-beginning-position)))))
 
 (defun sh-smie-sh-backward-token ()
-  (let ((bol (line-beginning-position))
-        pos tok)
+  (let ((bol (line-beginning-position)))
     (forward-comment (- (point)))
     (cond
      ((and (bolp) (not (bobp))
@@ -1791,7 +1930,8 @@ May return nil if the line should not be treated as continued."
 (defun sh-smie-sh-rules (kind token)
   (pcase (cons kind token)
     (`(:elem . basic) sh-indentation)
-    (`(:after . "case-)") (or sh-indentation smie-indent-basic))
+    (`(:after . "case-)") (- (sh-var-value 'sh-indent-for-case-alt)
+                             (sh-var-value 'sh-indent-for-case-label)))
     ((and `(:before . ,_)
           (guard (when sh-indent-after-continuation
                    (save-excursion
@@ -1825,12 +1965,27 @@ May return nil if the line should not be treated as continued."
                  (current-column)
                (smie-indent-calculate)))))
     (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
+    ;; Attempt at backward compatibility with the old config variables.
+    (`(:before . "fi") (sh-var-value 'sh-indent-for-fi))
+    (`(:before . "done") (sh-var-value 'sh-indent-for-done))
+    (`(:after . "else") (sh-var-value 'sh-indent-after-else))
+    (`(:after . "if") (sh-var-value 'sh-indent-after-if))
+    (`(:before . "then") (sh-var-value 'sh-indent-for-then))
+    (`(:before . "do") (sh-var-value 'sh-indent-for-do))
+    (`(:after . "do")
+     (sh-var-value (if (smie-rule-hanging-p)
+                       'sh-indent-after-loop-construct 'sh-indent-after-do)))
+    ;; sh-indent-after-done: aligned completely differently.
+    (`(:after . "in") (sh-var-value 'sh-indent-for-case-label))
+    ;; sh-indent-for-continuation: Line continuations are handled differently.
+    (`(:after . ,(or `"(" `"{" `"[")) (sh-var-value 'sh-indent-after-open))
+    ;; sh-indent-after-function: we don't handle it differently.
     ))
 
 ;; (defconst sh-smie-csh-grammar
 ;;   (smie-prec2->grammar
 ;;    (smie-bnf->prec2
-;;     '((exp)                   ;A constant, or a $var, or a sequence of them…
+;;     '((exp)              ;A constant, or a $var, or a sequence of them...
 ;;       (elseifcmd (cmd)
 ;;                  (cmd "else" "else-if" exp "then" elseifcmd))
 ;;       (cmd ("switch" branches "endsw")
@@ -1916,7 +2071,7 @@ Point should be before the newline."
             ";")
         (let ((semi (sh-smie--rc-newline-semi-p)))
           (forward-line 1)
-          (if semi ";"
+          (if (or semi (eobp)) ";"
             (sh-smie-rc-forward-token))))
     (forward-comment (point-max))
     (cond
@@ -1938,14 +2093,13 @@ Point should be before the newline."
                (not
                 (save-excursion
                   (goto-char pos)
-                  (sh-smie--keyword-p tok))))
+                  (sh-smie--keyword-p))))
           " 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)
+  (let ((bol (line-beginning-position)))
     (forward-comment (- (point)))
     (cond
      ((and (bolp) (not (bobp))
@@ -1977,7 +2131,7 @@ Point should be before the newline."
          ;; ((equal tok ")") "case-)")
          ((and tok (string-match "\\`[a-z]" tok)
                (assoc tok smie-grammar)
-               (not (save-excursion (sh-smie--keyword-p tok))))
+               (not (save-excursion (sh-smie--keyword-p))))
           " word ")
          (t tok)))))))
 
@@ -1985,8 +2139,9 @@ Point should be before the newline."
   (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)))
+    (`(:after . ";")
+     (if (smie-rule-parent-p "case")
+         (smie-rule-parent (sh-var-value 'sh-indent-after-case))))
     (`(:before . "{")
      (save-excursion
        (when (sh-smie--rc-after-special-arg-p)
@@ -2001,6 +2156,7 @@ Point should be before the newline."
     ;; with "(exp)", which is rarely the right thing to do, but is better
     ;; than nothing.
     (`(:list-intro . ,(or `"for" `"if" `"while")) t)
+    ;; sh-indent-after-switch: handled implicitly by the default { rule.
     ))
 
 ;;; End of SMIE code.
@@ -2075,11 +2231,18 @@ 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 (format "Shell \(default %s\): "
-                                             sh-shell-file)
-                                     interpreter-mode-alist
-                                     (lambda (x) (eq (cdr x) 'sh-mode))
-                                     nil nil nil sh-shell-file)
+  (interactive (list (completing-read
+                      (format "Shell \(default %s\): "
+                              sh-shell-file)
+                      ;; This used to use interpreter-mode-alist, but that is
+                      ;; no longer appropriate now that uses regexps.
+                      ;; Maybe there could be a separate variable that lists
+                      ;; the shells, used here and to construct i-mode-alist.
+                      ;; But the following is probably good enough:
+                      (append (mapcar (lambda (e) (symbol-name (car e)))
+                                      sh-ancestor-alist)
+                              '("csh" "rc" "sh"))
+                      nil nil nil nil sh-shell-file)
                     (eq executable-query 'function)
                     t))
   (if (string-match "\\.exe\\'" shell)
@@ -2092,39 +2255,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 (set (make-local-variable '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)
-        (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")))
-          (set (make-local-variable 'parse-sexp-lookup-properties) t)
-          (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
+        (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")))
+        (unless sh-use-smie
+          (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
-                (set (make-local-variable 'sh-regexp-for-done)
-                     (sh-mkword-regexpr (regexp-opt regexp t)))))
+                (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.
-          (set (make-local-variable 'indent-line-function) 'sh-indent-line))
+          (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))
@@ -2134,6 +2297,7 @@ Calls the value of `sh-set-shell-hook' if set."
     (setq font-lock-set-defaults nil)
     (font-lock-set-defaults)
     (font-lock-fontify-buffer))
+  (setq sh-shell-process nil)
   (run-hooks 'sh-set-shell-hook))
 
 
@@ -2366,7 +2530,6 @@ which in this buffer is currently %s.
 
 (defun sh-read-variable (var)
   "Read a new value for indentation variable VAR."
-  (interactive "*variable? ") ;; to test
   (let ((minibuffer-help-form `(sh-help-string-for-variable
                                (quote ,var)))
        val)
@@ -2956,6 +3119,7 @@ This takes into account that there may be nested open..close pairings.
 OPEN and CLOSE are regexps denoting the tokens to be matched.
 Optional parameter DEPTH (usually 1) says how many to look for."
   (let ((parse-sexp-ignore-comments t)
+        (forward-sexp-function nil)
        prev)
     (setq depth (or depth 1))
     (save-excursion
@@ -3012,12 +3176,9 @@ IGNORE-ERROR is non-nil."
      ((eq val '/)
       (/ (- sh-basic-offset) 2))
      (t
-      (if ignore-error
-      (progn
-       (message "Don't know how to handle %s's value of %s" var val)
-       0)
-      (error "Don't know how to handle %s's value of %s" var val))
-      ))))
+      (funcall (if ignore-error #'message #'error)
+               "Don't know how to handle %s's value of %s" var val)
+      0))))
 
 (defun sh-set-var-value (var value &optional no-symbol)
   "Set variable VAR to VALUE.
@@ -3142,33 +3303,35 @@ If variable `sh-blink' is non-nil then momentarily go to the line
 we are indenting relative to, if applicable."
   (interactive "P")
   (sh-must-support-indent)
-  (let* ((info (sh-get-indent-info))
-        (var (sh-get-indent-var-for-line info))
-        (curr-indent (current-indentation))
-        val msg)
-    (if (stringp var)
-       (message "%s" (setq msg var))
-      (setq val (sh-calculate-indent info))
-
-      (if (eq curr-indent val)
-         (setq msg (format "%s is %s" var (symbol-value var)))
-       (setq msg
-             (if val
-                 (format "%s (%s) would change indent from %d to: %d"
-                         var (symbol-value var) curr-indent val)
-               (format "%s (%s) would leave line as is"
-                       var (symbol-value var)))
-             ))
-      (if (and arg var)
-         (describe-variable var)))
-    (if sh-blink
-       (let ((info (sh-get-indent-info)))
-         (if (and info (listp (car info))
-                  (eq (car (car info)) t))
-             (sh-blink (nth 1 (car info))  msg)
-           (message "%s" msg)))
-      (message "%s" msg))
-    ))
+  (if sh-use-smie
+      (smie-config-show-indent)
+    (let* ((info (sh-get-indent-info))
+           (var (sh-get-indent-var-for-line info))
+           (curr-indent (current-indentation))
+           val msg)
+      (if (stringp var)
+          (message "%s" (setq msg var))
+        (setq val (sh-calculate-indent info))
+
+        (if (eq curr-indent val)
+            (setq msg (format "%s is %s" var (symbol-value var)))
+          (setq msg
+                (if val
+                    (format "%s (%s) would change indent from %d to: %d"
+                            var (symbol-value var) curr-indent val)
+                  (format "%s (%s) would leave line as is"
+                          var (symbol-value var)))
+                ))
+        (if (and arg var)
+            (describe-variable var)))
+      (if sh-blink
+          (let ((info (sh-get-indent-info)))
+            (if (and info (listp (car info))
+                     (eq (car (car info)) t))
+                (sh-blink (nth 1 (car info))  msg)
+              (message "%s" msg)))
+        (message "%s" msg))
+      )))
 
 (defun sh-set-indent ()
   "Set the indentation for the current line.
@@ -3176,34 +3339,36 @@ If the current line is controlled by an indentation variable, prompt
 for a new value for it."
   (interactive)
   (sh-must-support-indent)
-  (let* ((info (sh-get-indent-info))
-        (var (sh-get-indent-var-for-line info))
-        val old-val indent-val)
-    (if (stringp var)
-       (message "Cannot set indent - %s" var)
-      (setq old-val (symbol-value var))
-      (setq val (sh-read-variable var))
-      (condition-case nil
-         (progn
-           (set var val)
-           (setq indent-val (sh-calculate-indent info))
-           (if indent-val
-               (message "Variable: %s  Value: %s  would indent to: %d"
-                        var (symbol-value var) indent-val)
-             (message "Variable: %s  Value: %s  would leave line as is."
-                      var (symbol-value var)))
-           ;; I'm not sure about this, indenting it now?
-           ;; No.  Because it would give the impression that an undo would
-           ;; restore thing, but the value has been altered.
-           ;; (sh-indent-line)
-           )
-       (error
-        (set var old-val)
-        (message "Bad value for %s, restoring to previous value %s"
-                 var old-val)
-        (sit-for 1)
-        nil))
-      )))
+  (if sh-use-smie
+      (smie-config-set-indent)
+    (let* ((info (sh-get-indent-info))
+           (var (sh-get-indent-var-for-line info))
+           val old-val indent-val)
+      (if (stringp var)
+          (message "Cannot set indent - %s" var)
+        (setq old-val (symbol-value var))
+        (setq val (sh-read-variable var))
+        (condition-case nil
+            (progn
+              (set var val)
+              (setq indent-val (sh-calculate-indent info))
+              (if indent-val
+                  (message "Variable: %s  Value: %s  would indent to: %d"
+                           var (symbol-value var) indent-val)
+                (message "Variable: %s  Value: %s  would leave line as is."
+                         var (symbol-value var)))
+              ;; I'm not sure about this, indenting it now?
+              ;; No.  Because it would give the impression that an undo would
+              ;; restore thing, but the value has been altered.
+              ;; (sh-indent-line)
+              )
+          (error
+           (set var old-val)
+           (message "Bad value for %s, restoring to previous value %s"
+                    var old-val)
+           (sit-for 1)
+           nil))
+        ))))
 
 
 (defun sh-learn-line-indent (arg)
@@ -3217,55 +3382,57 @@ If the value can be represented by one of the symbols then do so
 unless optional argument ARG (the prefix when interactive) is non-nil."
   (interactive "*P")
   (sh-must-support-indent)
-  ;; I'm not sure if we show allow learning on an empty line.
-  ;; Though it might occasionally be useful I think it usually
-  ;; would just be confusing.
-  (if (save-excursion
-       (beginning-of-line)
-       (looking-at "\\s-*$"))
-      (message "sh-learn-line-indent ignores empty lines.")
-    (let* ((info (sh-get-indent-info))
-          (var (sh-get-indent-var-for-line info))
-          ival sval diff new-val
-          (no-symbol arg)
-          (curr-indent (current-indentation)))
-      (cond
-       ((stringp var)
-       (message "Cannot learn line - %s" var))
-       ((eq var 'sh-indent-comment)
-       ;; This is arbitrary...
-       ;; - if curr-indent is 0, set to curr-indent
-       ;; - else if it has the indentation of a "normal" line,
-       ;;   then set to t
-       ;; - else set to curr-indent.
-       (setq sh-indent-comment
-             (if (= curr-indent 0)
-                 0
-               (let* ((sh-indent-comment t)
-                      (val2 (sh-calculate-indent info)))
-                 (if (= val2 curr-indent)
-                     t
-                   curr-indent))))
-       (message "%s set to %s" var (symbol-value var))
-       )
-       ((numberp (setq sval (sh-var-value var)))
-       (setq ival (sh-calculate-indent info))
-       (setq diff (- curr-indent ival))
-
-       (sh-debug "curr-indent: %d   ival: %d  diff: %d  var:%s  sval %s"
-                 curr-indent ival diff  var sval)
-       (setq new-val (+ sval diff))
-;;;      I commented out this because someone might want to replace
-;;;      a value of `+' with the current value of sh-basic-offset
-;;;      or vice-versa.
-;;;      (if (= 0 diff)
-;;;          (message "No change needed!")
-       (sh-set-var-value var new-val no-symbol)
-       (message "%s set to %s" var (symbol-value var))
-       )
-       (t
-       (debug)
-       (message "Cannot change %s" var))))))
+  (if sh-use-smie
+      (smie-config-set-indent)
+    ;; I'm not sure if we show allow learning on an empty line.
+    ;; Though it might occasionally be useful I think it usually
+    ;; would just be confusing.
+    (if (save-excursion
+          (beginning-of-line)
+          (looking-at "\\s-*$"))
+        (message "sh-learn-line-indent ignores empty lines.")
+      (let* ((info (sh-get-indent-info))
+             (var (sh-get-indent-var-for-line info))
+             ival sval diff new-val
+             (no-symbol arg)
+             (curr-indent (current-indentation)))
+        (cond
+         ((stringp var)
+          (message "Cannot learn line - %s" var))
+         ((eq var 'sh-indent-comment)
+          ;; This is arbitrary...
+          ;; - if curr-indent is 0, set to curr-indent
+          ;; - else if it has the indentation of a "normal" line,
+          ;;   then set to t
+          ;; - else set to curr-indent.
+          (setq sh-indent-comment
+                (if (= curr-indent 0)
+                    0
+                  (let* ((sh-indent-comment t)
+                         (val2 (sh-calculate-indent info)))
+                    (if (= val2 curr-indent)
+                        t
+                      curr-indent))))
+          (message "%s set to %s" var (symbol-value var))
+          )
+         ((numberp (setq sval (sh-var-value var)))
+          (setq ival (sh-calculate-indent info))
+          (setq diff (- curr-indent ival))
+
+          (sh-debug "curr-indent: %d   ival: %d  diff: %d  var:%s  sval %s"
+                    curr-indent ival diff  var sval)
+          (setq new-val (+ sval diff))
+          ;; I commented out this because someone might want to replace
+          ;; a value of `+' with the current value of sh-basic-offset
+          ;; or vice-versa.
+          ;;(if (= 0 diff)
+          ;;     (message "No change needed!")
+          (sh-set-var-value var new-val no-symbol)
+          (message "%s set to %s" var (symbol-value var))
+          )
+         (t
+          (debug)
+          (message "Cannot change %s" var)))))))
 
 
 
@@ -3297,26 +3464,23 @@ so that `occur-next' and `occur-prev' will work."
        )
       (goto-char (point-max))
       (setq start (point))
-      (insert line)
-      (if occur-point
-         (setq occur-point (point)))
-      (insert message)
-      (if point
-         (add-text-properties
-          start (point)
-          '(mouse-face highlight
-            help-echo "mouse-2: go to the line where I learned this")))
-      (insert "\n")
-      (if point
-         (progn
-           (put-text-property start (point) 'occur-target m1)
-           (if occur-point
-               (put-text-property start occur-point
-                                  'occur-match t))
-           ))
-      )))
-
-
+      (let ((inhibit-read-only t))
+        (insert line)
+        (if occur-point
+            (setq occur-point (point)))
+        (insert message)
+        (if point
+            (add-text-properties
+             start (point)
+             '(mouse-face highlight
+                          help-echo "mouse-2: go to the line where I learned this")))
+        (insert "\n")
+        (when point
+          (put-text-property start (point) 'occur-target m1)
+          (if occur-point
+              (put-text-property start occur-point
+                                 'occur-match t))
+          )))))
 
 ;; Is this really worth having?
 (defvar sh-learned-buffer-hook nil
@@ -3366,202 +3530,204 @@ removed in the future.
 This command can often take a long time to run."
   (interactive "P")
   (sh-must-support-indent)
-  (save-excursion
-    (goto-char (point-min))
-    (let ((learned-var-list nil)
-         (out-buffer "*indent*")
-         (num-diffs 0)
-         previous-set-info
-         (max 17)
-         vec
-         msg
-         (comment-col nil) ;; number if all same, t if seen diff values
-         (comments-always-default t) ;; nil if we see one not default
-         initial-msg
-         (specified-basic-offset (and arg (numberp arg)
-                                      (> arg 0)))
-         (linenum 0)
-         suggested)
-      (setq vec (make-vector max 0))
-      (sh-mark-init out-buffer)
-
-      (if specified-basic-offset
-         (progn
-           (setq sh-basic-offset arg)
-           (setq initial-msg
-                 (format "Using specified sh-basic-offset of %d"
-                         sh-basic-offset)))
-       (setq initial-msg
-             (format "Initial value of sh-basic-offset: %s"
-                     sh-basic-offset)))
-
-      (while (< (point) (point-max))
-       (setq linenum (1+ linenum))
-       ;; (if (zerop (% linenum 10))
-       (message "line %d" linenum)
-       ;; )
-       (unless (looking-at "\\s-*$") ;; ignore empty lines!
-         (let* ((sh-indent-comment t) ;; info must return default indent
-                (info (sh-get-indent-info))
-                (var (sh-get-indent-var-for-line info))
-                sval ival diff new-val
-                (curr-indent (current-indentation)))
-           (cond
-            ((null var)
-             nil)
-            ((stringp var)
-             nil)
-            ((numberp (setq sval (sh-var-value var 'no-error)))
-             ;; the numberp excludes comments since sval will be t.
-             (setq ival (sh-calculate-indent))
-             (setq diff (- curr-indent ival))
-             (setq new-val (+ sval diff))
-             (sh-set-var-value var new-val 'no-symbol)
-             (unless (looking-at "\\s-*#") ;; don't learn from comments
-               (if (setq previous-set-info (assoc var learned-var-list))
-                   (progn
-                     ;; it was already there, is it same value ?
-                     (unless (eq (symbol-value var)
-                                 (nth 1 previous-set-info))
-                       (sh-mark-line
-                        (format "Variable %s was set to %s"
-                                var (symbol-value var))
-                        (point) out-buffer t t)
-                       (sh-mark-line
-                        (format "  but was previously set to %s"
-                                (nth 1 previous-set-info))
-                        (nth 2 previous-set-info) out-buffer t)
-                       (setq num-diffs (1+ num-diffs))
-                       ;; (delete previous-set-info  learned-var-list)
-                       (setcdr previous-set-info
-                               (list (symbol-value var) (point)))
-                       )
-                     )
-                 (setq learned-var-list
-                       (append (list (list var (symbol-value var)
-                                           (point)))
-                               learned-var-list)))
-               (if (numberp new-val)
-                   (progn
-                     (sh-debug
-                      "This line's indent value: %d"  new-val)
-                     (if (< new-val 0)
-                         (setq new-val (- new-val)))
-                     (if (< new-val max)
-                         (aset vec new-val (1+ (aref vec new-val))))))
-               ))
-            ((eq var 'sh-indent-comment)
-             (unless (= curr-indent (sh-calculate-indent info))
-               ;; this is not the default indentation
-               (setq comments-always-default nil)
-               (if comment-col ;; then we have see one before
-                   (or (eq comment-col curr-indent)
-                       (setq comment-col t)) ;; seen a different one
-                 (setq comment-col curr-indent))
-               ))
-            (t
-             (sh-debug "Cannot learn this line!!!")
-             ))
-           (sh-debug
-            "at %s learned-var-list is %s" (point) learned-var-list)
-           ))
-       (forward-line 1)
-       ) ;; while
-      (if sh-debug
-         (progn
-           (setq msg (format
-                      "comment-col = %s  comments-always-default = %s"
-                      comment-col comments-always-default))
-           ;; (message msg)
-           (sh-mark-line  msg nil out-buffer)))
-      (cond
-       ((eq comment-col 0)
-       (setq msg  "\nComments are all in 1st column.\n"))
-       (comments-always-default
-       (setq msg  "\nComments follow default indentation.\n")
-       (setq comment-col t))
-       ((numberp comment-col)
-       (setq msg  (format "\nComments are in col %d." comment-col)))
-       (t
-       (setq msg  "\nComments seem to be mixed, leaving them as is.\n")
-       (setq comment-col nil)
-       ))
-      (sh-debug msg)
-      (sh-mark-line  msg nil out-buffer)
-
-      (sh-mark-line initial-msg nil out-buffer t t)
-
-      (setq suggested (sh-guess-basic-offset vec))
-
-      (if (and suggested (not specified-basic-offset))
-         (let ((new-value
-                (cond
-                 ;; t => set it if we have a single value as a number
-                 ((and (eq sh-learn-basic-offset t) (numberp suggested))
-                  suggested)
-                 ;; other non-nil => set it if only one value was found
-                 (sh-learn-basic-offset
-                  (if (numberp suggested)
-                      suggested
-                    (if (= (length suggested) 1)
-                        (car suggested))))
-                 (t
-                  nil))))
-           (if new-value
-               (progn
-                 (setq learned-var-list
-                       (append (list (list 'sh-basic-offset
-                                           (setq sh-basic-offset new-value)
-                                           (point-max)))
-                               learned-var-list))
-                 ;; Not sure if we need to put this line in, since
-                 ;; it will appear in the "Learned variable settings".
-                 (sh-mark-line
-                  (format "Changed sh-basic-offset to: %d" sh-basic-offset)
-                  nil out-buffer))
-             (sh-mark-line
-              (if (listp suggested)
-                  (format "Possible value(s) for sh-basic-offset:  %s"
-                          (mapconcat 'int-to-string suggested " "))
-                (format "Suggested sh-basic-offset:  %d" suggested))
-              nil out-buffer))))
-
-
-      (setq learned-var-list
-           (append (list (list 'sh-indent-comment comment-col (point-max)))
-                   learned-var-list))
-      (setq sh-indent-comment comment-col)
-      (let ((name (buffer-name)))
-       (sh-mark-line  "\nLearned variable settings:" nil out-buffer)
-       (if arg
-           ;; Set learned variables to symbolic rather than numeric
-           ;; values where possible.
-           (dolist (learned-var (reverse learned-var-list))
-             (let ((var (car learned-var))
-                   (val (nth 1 learned-var)))
-               (when (and (not (eq var 'sh-basic-offset))
-                          (numberp val))
-                 (sh-set-var-value var val)))))
-       (dolist (learned-var (reverse learned-var-list))
-         (let ((var (car learned-var)))
-           (sh-mark-line (format "  %s %s" var (symbol-value var))
-                         (nth 2 learned-var) out-buffer)))
-       (with-current-buffer out-buffer
-         (goto-char (point-min))
-         (insert
-          (format "Indentation values for buffer %s.\n" name)
-          (format "%d indentation variable%s different values%s\n\n"
-                  num-diffs
-                  (if (= num-diffs 1)
-                      " has"   "s have")
-                  (if (zerop num-diffs)
-                      "." ":"))
-          )))
-      ;; Are abnormal hooks considered bad form?
-      (run-hook-with-args 'sh-learned-buffer-hook learned-var-list)
-      (and (called-interactively-p 'any)
-          (or sh-popup-occur-buffer (> num-diffs 0))
-          (pop-to-buffer out-buffer)))))
+  (if sh-use-smie
+      (smie-config-guess)
+    (save-excursion
+      (goto-char (point-min))
+      (let ((learned-var-list nil)
+            (out-buffer "*indent*")
+            (num-diffs 0)
+            previous-set-info
+            (max 17)
+            vec
+            msg
+            (comment-col nil) ;; number if all same, t if seen diff values
+            (comments-always-default t) ;; nil if we see one not default
+            initial-msg
+            (specified-basic-offset (and arg (numberp arg)
+                                         (> arg 0)))
+            (linenum 0)
+            suggested)
+        (setq vec (make-vector max 0))
+        (sh-mark-init out-buffer)
+
+        (if specified-basic-offset
+            (progn
+              (setq sh-basic-offset arg)
+              (setq initial-msg
+                    (format "Using specified sh-basic-offset of %d"
+                            sh-basic-offset)))
+          (setq initial-msg
+                (format "Initial value of sh-basic-offset: %s"
+                        sh-basic-offset)))
+
+        (while (< (point) (point-max))
+          (setq linenum (1+ linenum))
+          ;; (if (zerop (% linenum 10))
+          (message "line %d" linenum)
+          ;; )
+          (unless (looking-at "\\s-*$") ;; ignore empty lines!
+            (let* ((sh-indent-comment t) ;; info must return default indent
+                   (info (sh-get-indent-info))
+                   (var (sh-get-indent-var-for-line info))
+                   sval ival diff new-val
+                   (curr-indent (current-indentation)))
+              (cond
+               ((null var)
+                nil)
+               ((stringp var)
+                nil)
+               ((numberp (setq sval (sh-var-value var 'no-error)))
+                ;; the numberp excludes comments since sval will be t.
+                (setq ival (sh-calculate-indent))
+                (setq diff (- curr-indent ival))
+                (setq new-val (+ sval diff))
+                (sh-set-var-value var new-val 'no-symbol)
+                (unless (looking-at "\\s-*#") ;; don't learn from comments
+                  (if (setq previous-set-info (assoc var learned-var-list))
+                      (progn
+                        ;; it was already there, is it same value ?
+                        (unless (eq (symbol-value var)
+                                    (nth 1 previous-set-info))
+                          (sh-mark-line
+                           (format "Variable %s was set to %s"
+                                   var (symbol-value var))
+                           (point) out-buffer t t)
+                          (sh-mark-line
+                           (format "  but was previously set to %s"
+                                   (nth 1 previous-set-info))
+                           (nth 2 previous-set-info) out-buffer t)
+                          (setq num-diffs (1+ num-diffs))
+                          ;; (delete previous-set-info  learned-var-list)
+                          (setcdr previous-set-info
+                                  (list (symbol-value var) (point)))
+                          )
+                        )
+                    (setq learned-var-list
+                          (append (list (list var (symbol-value var)
+                                              (point)))
+                                  learned-var-list)))
+                  (if (numberp new-val)
+                      (progn
+                        (sh-debug
+                         "This line's indent value: %d"  new-val)
+                        (if (< new-val 0)
+                            (setq new-val (- new-val)))
+                        (if (< new-val max)
+                            (aset vec new-val (1+ (aref vec new-val))))))
+                  ))
+               ((eq var 'sh-indent-comment)
+                (unless (= curr-indent (sh-calculate-indent info))
+                  ;; this is not the default indentation
+                  (setq comments-always-default nil)
+                  (if comment-col ;; then we have see one before
+                      (or (eq comment-col curr-indent)
+                          (setq comment-col t)) ;; seen a different one
+                    (setq comment-col curr-indent))
+                  ))
+               (t
+                (sh-debug "Cannot learn this line!!!")
+                ))
+              (sh-debug
+               "at %s learned-var-list is %s" (point) learned-var-list)
+              ))
+          (forward-line 1)
+          ) ;; while
+        (if sh-debug
+            (progn
+              (setq msg (format
+                         "comment-col = %s  comments-always-default = %s"
+                         comment-col comments-always-default))
+              ;; (message msg)
+              (sh-mark-line  msg nil out-buffer)))
+        (cond
+         ((eq comment-col 0)
+          (setq msg  "\nComments are all in 1st column.\n"))
+         (comments-always-default
+          (setq msg  "\nComments follow default indentation.\n")
+          (setq comment-col t))
+         ((numberp comment-col)
+          (setq msg  (format "\nComments are in col %d." comment-col)))
+         (t
+          (setq msg  "\nComments seem to be mixed, leaving them as is.\n")
+          (setq comment-col nil)
+          ))
+        (sh-debug msg)
+        (sh-mark-line  msg nil out-buffer)
+
+        (sh-mark-line initial-msg nil out-buffer t t)
+
+        (setq suggested (sh-guess-basic-offset vec))
+
+        (if (and suggested (not specified-basic-offset))
+            (let ((new-value
+                   (cond
+                    ;; t => set it if we have a single value as a number
+                    ((and (eq sh-learn-basic-offset t) (numberp suggested))
+                     suggested)
+                    ;; other non-nil => set it if only one value was found
+                    (sh-learn-basic-offset
+                     (if (numberp suggested)
+                         suggested
+                       (if (= (length suggested) 1)
+                           (car suggested))))
+                    (t
+                     nil))))
+              (if new-value
+                  (progn
+                    (setq learned-var-list
+                          (append (list (list 'sh-basic-offset
+                                              (setq sh-basic-offset new-value)
+                                              (point-max)))
+                                  learned-var-list))
+                    ;; Not sure if we need to put this line in, since
+                    ;; it will appear in the "Learned variable settings".
+                    (sh-mark-line
+                     (format "Changed sh-basic-offset to: %d" sh-basic-offset)
+                     nil out-buffer))
+                (sh-mark-line
+                 (if (listp suggested)
+                     (format "Possible value(s) for sh-basic-offset:  %s"
+                             (mapconcat 'int-to-string suggested " "))
+                   (format "Suggested sh-basic-offset:  %d" suggested))
+                 nil out-buffer))))
+
+
+        (setq learned-var-list
+              (append (list (list 'sh-indent-comment comment-col (point-max)))
+                      learned-var-list))
+        (setq sh-indent-comment comment-col)
+        (let ((name (buffer-name)))
+          (sh-mark-line  "\nLearned variable settings:" nil out-buffer)
+          (if arg
+              ;; Set learned variables to symbolic rather than numeric
+              ;; values where possible.
+              (dolist (learned-var (reverse learned-var-list))
+                (let ((var (car learned-var))
+                      (val (nth 1 learned-var)))
+                  (when (and (not (eq var 'sh-basic-offset))
+                             (numberp val))
+                    (sh-set-var-value var val)))))
+          (dolist (learned-var (reverse learned-var-list))
+            (let ((var (car learned-var)))
+              (sh-mark-line (format "  %s %s" var (symbol-value var))
+                            (nth 2 learned-var) out-buffer)))
+          (with-current-buffer out-buffer
+            (goto-char (point-min))
+            (let ((inhibit-read-only t))
+              (insert
+               (format "Indentation values for buffer %s.\n" name)
+               (format "%d indentation variable%s different values%s\n\n"
+                       num-diffs
+                       (if (= num-diffs 1)
+                           " has"   "s have")
+                       (if (zerop num-diffs)
+                           "." ":"))))))
+        ;; Are abnormal hooks considered bad form?
+        (run-hook-with-args 'sh-learned-buffer-hook learned-var-list)
+        (and (called-interactively-p 'any)
+             (or sh-popup-occur-buffer (> num-diffs 0))
+             (pop-to-buffer out-buffer))))))
 
 (defun sh-guess-basic-offset (vec)
   "See if we can determine a reasonable value for `sh-basic-offset'.
@@ -3577,11 +3743,11 @@ Return values:
         (i 1)
         (totals (make-vector max 0)))
     (while (< i max)
-      (aset totals i (+ (aref totals i) (* 4 (aref vec i))))
+      (cl-incf (aref totals i) (* 4 (aref vec i)))
       (if (zerop (% i 2))
-         (aset totals i (+ (aref totals i) (aref vec (/ i 2)))))
+         (cl-incf (aref totals i) (aref vec (/ i 2))))
       (if (< (* i 2) max)
-         (aset totals i (+ (aref totals i) (aref vec (* i 2)))))
+         (cl-incf (aref totals i) (aref vec (* i 2))))
       (setq i (1+ i)))
 
     (let ((x nil)
@@ -3590,10 +3756,10 @@ Return values:
       (setq i 1)
       (while (< i max)
        (if (/= (aref totals i) 0)
-           (setq x (append x (list (cons i (aref totals i))))))
+           (push (cons i (aref totals i)) x))
        (setq i (1+ i)))
 
-      (setq x (sort x (lambda (a b) (> (cdr a) (cdr b)))))
+      (setq x (sort (nreverse x) (lambda (a b) (> (cdr a) (cdr b)))))
       (setq tot (apply '+ (append totals nil)))
       (sh-debug (format "vec: %s\ntotals: %s\ntot: %d"
                        vec totals tot))