]> 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 07e9bb85c4e98c20ccab69b09dc2ba136b18f5f2..3ae9a21c3e499f4ee0277c0281b66c3882f8cfe0 100644 (file)
@@ -1,6 +1,6 @@
-;;; 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-2013 Free Software Foundation,
+;; Copyright (C) 1993-1997, 1999, 2001-2014 Free Software Foundation,
 ;; Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
@@ -497,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)
@@ -670,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
@@ -734,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)
 
 
@@ -937,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'.")
 
@@ -1461,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
 
@@ -1526,7 +1590,7 @@ with your script for an edit-interpret-debug cycle."
              (lambda () (or (eolp) (newline) (indent-relative))))
 
   (setq-local paragraph-start (concat page-delimiter "\\|$"))
-  (setq-local paragraph-separate paragraph-start)
+  (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)
@@ -1556,6 +1620,8 @@ with your script for an edit-interpret-debug cycle."
   (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
@@ -1614,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)
@@ -1622,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)
@@ -1703,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)))
@@ -1724,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|\\)\\|$\\)")
@@ -1740,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
@@ -1774,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))
@@ -1826,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
@@ -1860,6 +1965,21 @@ 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
@@ -1951,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
@@ -1973,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))
@@ -2012,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)))))))
 
@@ -2020,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)
@@ -2036,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.
@@ -2110,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)
@@ -2142,14 +2270,14 @@ Calls the value of `sh-set-shell-hook' if set."
                  (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")))
+        (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)))
@@ -2169,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))
 
 
@@ -2401,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)
@@ -2991,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
@@ -3047,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.
@@ -3177,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.
@@ -3211,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)
@@ -3252,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)))))))
 
 
 
@@ -3332,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
@@ -3401,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'.
@@ -3612,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)
@@ -3625,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))