-;;; 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,
-;; Inc.
+;; Copyright (C) 1993-1997, 1999, 2001-2014 Free Software Foundation, Inc.
;; Author: Daniel Pfeiffer <occitan@esperanto.org>
;; Version: 2.0f
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
;; Keywords: languages, unix
;; This file is part of GNU Emacs.
'((ash . sh)
(bash . jsh)
(bash2 . jsh)
+ (dash . ash)
(dtksh . ksh)
(es . rc)
(itcsh . tcsh)
es Extensible Shell
sh Bourne Shell
ash Almquist Shell
+ dash Debian Almquist Shell
jsh Bourne Shell with Job Control
bash GNU Bourne Again Shell
ksh88 Korn Shell '88
posix IEEE 1003.2 Shell Standard
wsh ? Shell"
:type '(repeat (cons symbol symbol))
+ :version "24.4" ; added dash
:group 'sh-script)
. ((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.
: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
?~ "_"
?, "_"
?= "."
+ ?\; "."
+ ?| "."
+ ?& "."
?< "."
?> ".")
"The syntax table to use for Shell-Script mode.
(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)
"." "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
:type '(repeat (cons (symbol :tag "Shell")
(choice (repeat string)
(sexp :format "Evaluate: %v"))))
+ :version "24.4" ; bash4 additions
:group 'sh-script)
(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'.")
(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))))))))
+ (or
+ (pcase (char-after)
+ ;; ((...)) or $((...)) or $[...] or ${...}. Nested
+ ;; parenthesis can occur inside the first of these forms, so
+ ;; parse backward recursively.
+ (`?\( (eq ?\( (char-before)))
+ ((or `?\{ `?\[) (eq ?\$ (char-before))))
+ (sh--inside-noncommand-expression (1- (point))))))))
(defun sh-font-lock-open-heredoc (start string eol)
"Determine the syntax of the \\n after a <<EOF.
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
(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
"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)
;; 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)
(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)))
(defun sh-smie--sh-keyword-p (tok)
"Non-nil if TOK (at which we're looking) really is a keyword."
- (if (equal tok "in")
- (sh-smie--sh-keyword-in-p)
- (sh-smie--keyword-p tok)))
+ (cond
+ ((looking-at "[[:alnum:]_]+=") nil)
+ ((equal tok "in") (sh-smie--sh-keyword-in-p))
+ (t (sh-smie--keyword-p))))
+
+(defun sh-smie--default-forward-token ()
+ (forward-comment (point-max))
+ (buffer-substring-no-properties
+ (point)
+ (progn (if (zerop (skip-syntax-forward "."))
+ (while (progn (skip-syntax-forward "w_'")
+ (looking-at "\\\\"))
+ (forward-char 2)))
+ (point))))
+
+(defun sh-smie--default-backward-token ()
+ (forward-comment (- (point)))
+ (let ((pos (point))
+ (n (skip-syntax-backward ".")))
+ (if (or (zerop n)
+ (and (eq n -1)
+ (let ((p (point)))
+ (if (eq -1 (% (skip-syntax-backward "\\") 2))
+ t
+ (goto-char p)
+ nil))))
+ (while
+ (progn (skip-syntax-backward "w_'")
+ (or (not (zerop (skip-syntax-backward "\\")))
+ (when (eq ?\\ (char-before (1- (point))))
+ (let ((p (point)))
+ (forward-char -1)
+ (if (eq -1 (% (skip-syntax-backward "\\") 2))
+ t
+ (goto-char p)
+ nil))))))
+ (goto-char (- (point) (% (skip-syntax-backward "\\") 2))))
+ (buffer-substring-no-properties (point) pos)))
(defun sh-smie-sh-forward-token ()
(if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
";")
(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
tok))
(t
(let* ((pos (point))
- (tok (smie-default-forward-token)))
+ (tok (sh-smie--default-forward-token)))
(cond
((equal tok ")") "case-)")
((equal tok "(") "case-(")
(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))
(goto-char (match-beginning 1))
(match-string-no-properties 1))
(t
- (let ((tok (smie-default-backward-token)))
+ (let ((tok (sh-smie--default-backward-token)))
(cond
((equal tok ")") "case-)")
((equal tok "(") "case-(")
(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
- (ignore-errors
- (skip-chars-backward " \t")
- (sh-smie--looking-back-at-continuation-p))))))
- ;; After a line-continuation, make sure the rest is indented.
- (let* ((sh-indent-after-continuation nil)
- (indent (smie-indent-calculate))
- (initial (sh-smie--continuation-start-indent)))
- (when (and (numberp indent) (numberp initial)
- (<= indent initial))
- `(column . ,(+ initial sh-indentation)))))
- (`(:before . ,(or `"(" `"{" `"["))
- (if (smie-rule-hanging-p) (smie-rule-parent)))
+ ;; After a line-continuation, make sure the rest is indented.
+ (guard sh-indent-after-continuation)
+ (guard (save-excursion
+ (ignore-errors
+ (skip-chars-backward " \t")
+ (sh-smie--looking-back-at-continuation-p))))
+ (let initial (sh-smie--continuation-start-indent))
+ (guard (let* ((sh-indent-after-continuation nil)
+ (indent (smie-indent-calculate)))
+ (and (numberp indent) (numberp initial)
+ (<= indent initial)))))
+ `(column . ,(+ initial sh-indentation)))
+ (`(:before . ,(or `"(" `"{" `"[" "while" "if" "for" "case"))
+ (if (not (smie-rule-prev-p "&&" "||" "|"))
+ (when (smie-rule-hanging-p)
+ (smie-rule-parent))
+ (unless (smie-rule-bolp)
+ (while (equal "|" (nth 2 (smie-backward-sexp 'halfexp))))
+ `(column . ,(smie-indent-virtual)))))
;; FIXME: Maybe this handling of ;; should be made into
;; a smie-rule-terminator function that takes the substitute ";" as arg.
(`(:before . ,(or `";;" `";&" `";;&"))
(smie-rule-bolp))))
(current-column)
(smie-indent-calculate)))))
- (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
+ (`(:before . ,(or `"|" `"&&" `"||"))
+ (unless (smie-rule-parent-p token)
+ (smie-backward-sexp token)
+ `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+ (smie-indent-virtual)))))
+
+ ;; 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")
";")
(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
;; tok))
(t
(let* ((pos (point))
- (tok (smie-default-forward-token)))
+ (tok (sh-smie--default-forward-token)))
(cond
;; ((equal tok ")") "case-)")
((and tok (string-match "\\`[a-z]" tok)
(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))
;; (goto-char (match-beginning 1))
;; (match-string-no-properties 1))
(t
- (let ((tok (smie-default-backward-token)))
+ (let ((tok (sh-smie--default-backward-token)))
(cond
;; ((equal tok ")") "case-)")
((and tok (string-match "\\`[a-z]" tok)
(assoc tok smie-grammar)
- (not (save-excursion (sh-smie--keyword-p tok))))
+ (not (save-excursion (sh-smie--keyword-p))))
" word ")
(t tok)))))))
(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)
;; 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.
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)
(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)))))
+ (add-function :around (local 'smie--hanging-eolp-function)
+ (lambda (orig)
+ (if (looking-at "[ \t]*\\\\\n")
+ (goto-char (match-end 0))
+ (funcall orig))))
+ (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))
(message "No indentation for this shell type.")
- (setq indent-line-function 'sh-basic-indent-line))
+ (setq-local indent-line-function 'sh-basic-indent-line))
(when font-lock-mode
(setq font-lock-set-defaults nil)
(font-lock-set-defaults)
(font-lock-fontify-buffer))
+ (setq sh-shell-process nil)
(run-hooks 'sh-set-shell-hook))
;; I commented this out because nobody calls it -- rms.
;;(defun sh-abbrevs (ancestor &rest list)
-;; "Iff it isn't, define the current shell as abbrev table and fill that.
+;; "If it isn't, define the current shell as abbrev table and fill that.
;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
;;table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs
;;according to the remaining arguments NAMEi EXPANSIONi ...
(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)
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
((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.
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.
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)
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)))))))
)
(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
;; Originally this was sh-learn-region-indent (beg end)
;; However, in practice this was awkward so I changed it to
-;; use the whole buffer. Use narrowing if needbe.
+;; use the whole buffer. Use narrowing if need be.
(defun sh-learn-buffer-indent (&optional arg)
"Learn how to indent the buffer the way it currently is.
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'.
(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)
(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))
(or (not (looking-back "[^<]<<"))
(save-excursion
(backward-char 2)
- (sh-quoted-p))
+ (or (sh-quoted-p)
+ (sh--inside-noncommand-expression (point))))
(nth 8 (syntax-ppss))
(let ((tabs (if (string-match "\\`-" sh-here-document-word)
(make-string (/ (current-indentation) tab-width) ?\t)