aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2020-02-03 15:26:59 -0500
committerStefan Monnier2020-02-03 15:26:59 -0500
commitf9504ffba2e2604338c243dd77c877bbb8162e4a (patch)
tree2087e5738ff0dc989a2d0a6cad0947c431ac1b5b
parent45fd45a28309a75f46c686e1b5dbeff00b684cc1 (diff)
downloademacs-f9504ffba2e2604338c243dd77c877bbb8162e4a.tar.gz
emacs-f9504ffba2e2604338c243dd77c877bbb8162e4a.zip
* lisp/progmodes/sh-script.el: Remove old non-SMIE indentation code
(sh-learn-basic-offset, sh-blink, sh-use-smie): Remove config vars. (sh-kw-alist, sh-learned-buffer-hook): Remove var. (sh-must-support-indent, sh-mark-init, sh-mark-line): Remove function. (sh-kw, sh-special-keywords): Remove constant. (sh-help-string-for-variable, sh-read-variable, sh-goto-matching-if) (sh-handle-prev-if, sh-handle-this-else, sh-handle-prev-else) (sh-handle-this-fi, sh-handle-prev-fi, sh-handle-this-then) (sh-handle-prev-then, sh-handle-prev-open, sh-handle-this-close) (sh-goto-matching-case, sh-handle-prev-case, sh-handle-this-esac) (sh-handle-prev-esac, sh-handle-after-case-label) (sh-handle-prev-case-alt-end, sh-safe-forward-sexp) (sh-goto-match-for-done, sh-handle-this-done, sh-handle-prev-done) (sh-handle-this-do, sh-handle-prev-do, sh-find-prev-switch) (sh-handle-this-rc-case, sh-handle-prev-rc-case, sh-check-rule) (sh-get-indent-info, sh-get-indent-var-for-line, sh-prev-line) (sh-prev-stmt, sh-get-word, sh-prev-thing, sh-this-is-a-continuation) (sh-get-kw, sh-find-prev-matching, sh-set-var-value) (sh-calculate-indent, sh-indent-line, sh-blink, sh-guess-basic-offset): Remove functions. (sh-show-indent, sh-set-indent, sh-learn-line-indent) (sh-learn-buffer-indent): Redefine as obsolete aliases.
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/progmodes/sh-script.el1454
2 files changed, 27 insertions, 1430 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 2b9337b9ee2..1fbcd43ab00 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -86,6 +86,9 @@ shows equivalent key bindings for all commands that have them.
86* Changes in Specialized Modes and Packages in Emacs 28.1 86* Changes in Specialized Modes and Packages in Emacs 28.1
87 87
88--- 88---
89** The old non-SMIE indentation of 'sh-mode' has been removed.
90
91---
89** The sb-image.el library is now marked obsolete. 92** The sb-image.el library is now marked obsolete.
90This file was a compatibility kludge which is no longer needed. 93This file was a compatibility kludge which is no longer needed.
91 94
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index cc6d5b46ed2..a241a1e69ba 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -64,61 +64,10 @@
64;; * Indent right half sh-basic-offset 64;; * Indent right half sh-basic-offset
65;; / Indent left half sh-basic-offset. 65;; / Indent left half sh-basic-offset.
66;; 66;;
67;; There are 4 commands to help set the indentation variables:
68;;
69;; `sh-show-indent'
70;; This shows what variable controls the indentation of the current
71;; line and its value.
72;;
73;; `sh-set-indent'
74;; This allows you to set the value of the variable controlling the
75;; current line's indentation. You can enter a number or one of a
76;; number of special symbols to denote the value of sh-basic-offset,
77;; or its negative, or half it, or twice it, etc. If you've used
78;; cc-mode this should be familiar. If you forget which symbols are
79;; valid simply press C-h at the prompt.
80;;
81;; `sh-learn-line-indent'
82;; Simply make the line look the way you want it, then invoke this
83;; command. It will set the variable to the value that makes the line
84;; indent like that. If called with a prefix argument then it will set
85;; the value to one of the symbols if applicable.
86;;
87;; `sh-learn-buffer-indent'
88;; This is the deluxe function! It "learns" the whole buffer (use
89;; narrowing if you want it to process only part). It outputs to a
90;; buffer *indent* any conflicts it finds, and all the variables it has
91;; learned. This buffer is a sort of Occur mode buffer, allowing you to
92;; easily find where something was set. It is popped to automatically
93;; if there are any conflicts found or if `sh-popup-occur-buffer' is
94;; non-nil.
95;; `sh-indent-comment' will be set if all comments follow the same
96;; pattern; if they don't it will be set to nil.
97;; Whether `sh-basic-offset' is set is determined by variable
98;; `sh-learn-basic-offset'.
99;;
100;; Unfortunately, `sh-learn-buffer-indent' can take a long time to run
101;; (e.g. if there are large case statements). Perhaps it does not make
102;; sense to run it on large buffers: if lots of lines have different
103;; indentation styles it will produce a lot of diagnostics in the
104;; *indent* buffer; if there is a consistent style then running
105;; `sh-learn-buffer-indent' on a small region of the buffer should
106;; suffice.
107;;
108;; Saving indentation values 67;; Saving indentation values
109;; ------------------------- 68;; -------------------------
110;; After you've learned the values in a buffer, how to you remember 69;; After you've learned the values in a buffer, how to you remember them?
111;; them? Originally I had hoped that `sh-learn-buffer-indent' 70;; There is a minimal way of being able to save indentation values and
112;; would make this unnecessary; simply learn the values when you visit
113;; the buffer.
114;; You can do this automatically like this:
115;; (add-hook 'sh-set-shell-hook #'sh-learn-buffer-indent)
116;;
117;; However... `sh-learn-buffer-indent' is extremely slow,
118;; especially on large-ish buffer. Also, if there are conflicts the
119;; "last one wins" which may not produce the desired setting.
120;;
121;; So...There is a minimal way of being able to save indentation values and
122;; to reload them in another buffer or at another point in time. 71;; to reload them in another buffer or at another point in time.
123;; 72;;
124;; Use `sh-name-style' to give a name to the indentation settings of 73;; Use `sh-name-style' to give a name to the indentation settings of
@@ -132,7 +81,7 @@
132;; Indentation variables - buffer local or global? 81;; Indentation variables - buffer local or global?
133;; ---------------------------------------------- 82;; ----------------------------------------------
134;; I think that often having them buffer-local makes sense, 83;; I think that often having them buffer-local makes sense,
135;; especially if one is using `sh-learn-buffer-indent'. However, if 84;; especially if one is using `smie-config-guess'. However, if
136;; a user sets values using customization, these changes won't appear 85;; a user sets values using customization, these changes won't appear
137;; to work if the variables are already local! 86;; to work if the variables are already local!
138;; 87;;
@@ -175,18 +124,10 @@
175;; - Indenting many lines is slow. It currently does each line 124;; - Indenting many lines is slow. It currently does each line
176;; independently, rather than saving state information. 125;; independently, rather than saving state information.
177;; 126;;
178;; - `sh-learn-buffer-indent' is extremely slow.
179;;
180;; - "case $x in y) echo ;; esac)" the last ) is mis-identified as being
181;; part of a case-pattern. You need to add a semi-colon after "esac" to
182;; coerce sh-script into doing the right thing.
183;;
184;; - "echo $z in ps | head)" the last ) is mis-identified as being part of 127;; - "echo $z in ps | head)" the last ) is mis-identified as being part of
185;; a case-pattern. You need to put the "in" between quotes to coerce 128;; a case-pattern. You need to put the "in" between quotes to coerce
186;; sh-script into doing the right thing. 129;; sh-script into doing the right thing.
187;; 130;;
188;; - A line starting with "}>foo" is not indented like "} >foo".
189;;
190;; Richard Sharman <rsharman@pobox.com> June 1999. 131;; Richard Sharman <rsharman@pobox.com> June 1999.
191 132
192;;; Code: 133;;; Code:
@@ -474,10 +415,10 @@ This is buffer-local in every such buffer.")
474 (define-key map "\C-c\C-i" 'sh-if) 415 (define-key map "\C-c\C-i" 'sh-if)
475 (define-key map "\C-c\C-f" 'sh-for) 416 (define-key map "\C-c\C-f" 'sh-for)
476 (define-key map "\C-c\C-c" 'sh-case) 417 (define-key map "\C-c\C-c" 'sh-case)
477 (define-key map "\C-c?" 'sh-show-indent) 418 (define-key map "\C-c?" #'smie-config-show-indent)
478 (define-key map "\C-c=" 'sh-set-indent) 419 (define-key map "\C-c=" #'smie-config-set-indent)
479 (define-key map "\C-c<" 'sh-learn-line-indent) 420 (define-key map "\C-c<" #'smie-config-set-indent)
480 (define-key map "\C-c>" 'sh-learn-buffer-indent) 421 (define-key map "\C-c>" #'smie-config-guess)
481 (define-key map "\C-c\C-\\" 'sh-backslash-region) 422 (define-key map "\C-c\C-\\" 'sh-backslash-region)
482 423
483 (define-key map "\C-c+" 'sh-add) 424 (define-key map "\C-c+" 'sh-add)
@@ -493,17 +434,14 @@ This is buffer-local in every such buffer.")
493 (define-key map [remap backward-sentence] 'sh-beginning-of-command) 434 (define-key map [remap backward-sentence] 'sh-beginning-of-command)
494 (define-key map [remap forward-sentence] 'sh-end-of-command) 435 (define-key map [remap forward-sentence] 'sh-end-of-command)
495 (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map)) 436 (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map))
496 (define-key menu-map [sh-learn-buffer-indent] 437 (define-key menu-map [smie-config-guess]
497 '(menu-item "Learn buffer indentation" sh-learn-buffer-indent 438 '(menu-item "Learn buffer indentation" smie-config-guess
498 :help "Learn how to indent the buffer the way it currently is.")) 439 :help "Learn how to indent the buffer the way it currently is."))
499 (define-key menu-map [sh-learn-line-indent] 440 (define-key menu-map [smie-config-show-indent]
500 '(menu-item "Learn line indentation" sh-learn-line-indent 441 '(menu-item "Show indentation" smie-config-show-indent
501 :help "Learn how to indent a line as it currently is indented"))
502 (define-key menu-map [sh-show-indent]
503 '(menu-item "Show indentation" sh-show-indent
504 :help "Show the how the current line would be indented")) 442 :help "Show the how the current line would be indented"))
505 (define-key menu-map [sh-set-indent] 443 (define-key menu-map [smie-config-set-indent]
506 '(menu-item "Set indentation" sh-set-indent 444 '(menu-item "Set indentation" smie-config-set-indent
507 :help "Set the indentation for the current line")) 445 :help "Set the indentation for the current line"))
508 446
509 (define-key menu-map [sh-pair] 447 (define-key menu-map [sh-pair]
@@ -1196,20 +1134,8 @@ and command `sh-reset-indent-vars-to-global-values'."
1196 :options '(sh-electric-here-document-mode) 1134 :options '(sh-electric-here-document-mode)
1197 :group 'sh-script) 1135 :group 'sh-script)
1198 1136
1199(defcustom sh-learn-basic-offset nil
1200 "When `sh-guess-basic-offset' should learn `sh-basic-offset'.
1201
1202nil mean: never.
1203t means: only if there seems to be an obvious value.
1204Anything else means: whenever we have a \"good guess\" as to the value."
1205 :type '(choice
1206 (const :tag "Never" nil)
1207 (const :tag "Only if sure" t)
1208 (const :tag "If have a good guess" usually))
1209 :group 'sh-indentation)
1210
1211(defcustom sh-popup-occur-buffer nil 1137(defcustom sh-popup-occur-buffer nil
1212 "Controls when `sh-learn-buffer-indent' pops the `*indent*' buffer. 1138 "Controls when `smie-config-guess' pops the `*indent*' buffer.
1213If t it is always shown. If nil, it is shown only when there 1139If t it is always shown. If nil, it is shown only when there
1214are conflicts." 1140are conflicts."
1215 :type '(choice 1141 :type '(choice
@@ -1217,14 +1143,6 @@ are conflicts."
1217 (const :tag "Always" t)) 1143 (const :tag "Always" t))
1218 :group 'sh-indentation) 1144 :group 'sh-indentation)
1219 1145
1220(defcustom sh-blink t
1221 "If non-nil, `sh-show-indent' shows the line indentation is relative to.
1222The position on the line is not necessarily meaningful.
1223In some cases the line will be the matching keyword, but this is not
1224always the case."
1225 :type 'boolean
1226 :group 'sh-indentation)
1227
1228(defcustom sh-first-lines-indent 0 1146(defcustom sh-first-lines-indent 0
1229 "The indentation of the first non-blank non-comment line. 1147 "The indentation of the first non-blank non-comment line.
1230Usually 0 meaning first column. 1148Usually 0 meaning first column.
@@ -1567,11 +1485,9 @@ following commands are available, based on the current shell's syntax:
1567\\[sh-while] while loop 1485\\[sh-while] while loop
1568 1486
1569For sh and rc shells indentation commands are: 1487For sh and rc shells indentation commands are:
1570\\[sh-show-indent] Show the variable controlling this line's indentation. 1488\\[smie-config-show-indent] Show the rules controlling this line's indentation.
1571\\[sh-set-indent] Set then variable controlling this line's indentation. 1489\\[smie-config-set-indent] Change the rules controlling this line's indentation.
1572\\[sh-learn-line-indent] Change the indentation variable so this line 1490\\[smie-config-guess] Try to tweak the indentation rules so the
1573would indent to the way it currently is.
1574\\[sh-learn-buffer-indent] Set the indentation variables so the
1575buffer indents as it currently is indented. 1491buffer indents as it currently is indented.
1576 1492
1577 1493
@@ -1738,13 +1654,6 @@ This adds rules for comments and assignments."
1738 1654
1739(require 'smie) 1655(require 'smie)
1740 1656
1741;; The SMIE code should generally be preferred, but it currently does not obey
1742;; the various indentation custom-vars, and it misses some important features
1743;; of the old code, mostly: sh-learn-line/buffer-indent, sh-show-indent,
1744;; sh-name/save/load-style.
1745(defvar sh-use-smie t
1746 "Whether to use the SMIE code for navigation and indentation.")
1747
1748(defun sh-smie--keyword-p () 1657(defun sh-smie--keyword-p ()
1749 "Non-nil if we're at a keyword position. 1658 "Non-nil if we're at a keyword position.
1750A keyword position is one where if we're looking at something that looks 1659A keyword position is one where if we're looking at something that looks
@@ -2279,60 +2188,6 @@ Point should be before the newline."
2279(defvar sh-regexp-for-done nil 2188(defvar sh-regexp-for-done nil
2280 "A buffer-local regexp to match opening keyword for done.") 2189 "A buffer-local regexp to match opening keyword for done.")
2281 2190
2282(defvar sh-kw-alist nil
2283 "A buffer-local, since it is shell-type dependent, list of keywords.")
2284
2285;; ( key-word first-on-this on-prev-line )
2286;; This is used to set `sh-kw-alist' which is a list of sublists each
2287;; having 3 elements:
2288;; a keyword
2289;; a rule to check when the keyword appears on "this" line
2290;; a rule to check when the keyword appears on "the previous" line
2291;; The keyword is usually a string and is the first word on a line.
2292;; If this keyword appears on the line whose indentation is to be
2293;; calculated, the rule in element 2 is called. If this returns
2294;; non-zero, the resulting point (which may be changed by the rule)
2295;; is used as the default indentation.
2296;; If it returned false or the keyword was not found in the table,
2297;; then the keyword from the previous line is looked up and the rule
2298;; in element 3 is called. In this case, however,
2299;; `sh-get-indent-info' does not stop but may keep going and test
2300;; other keywords against rules in element 3. This is because the
2301;; preceding line could have, for example, an opening "if" and an
2302;; opening "while" keyword and we need to add the indentation offsets
2303;; for both.
2304;;
2305(defconst sh-kw
2306 '((sh
2307 ("if" nil sh-handle-prev-if)
2308 ("elif" sh-handle-this-else sh-handle-prev-else)
2309 ("else" sh-handle-this-else sh-handle-prev-else)
2310 ("fi" sh-handle-this-fi sh-handle-prev-fi)
2311 ("then" sh-handle-this-then sh-handle-prev-then)
2312 ("(" nil sh-handle-prev-open)
2313 ("{" nil sh-handle-prev-open)
2314 ("[" nil sh-handle-prev-open)
2315 ("}" sh-handle-this-close nil)
2316 (")" sh-handle-this-close nil)
2317 ("]" sh-handle-this-close nil)
2318 ("case" nil sh-handle-prev-case)
2319 ("esac" sh-handle-this-esac sh-handle-prev-esac)
2320 (case-label nil sh-handle-after-case-label) ;; ???
2321 (";;" nil sh-handle-prev-case-alt-end) ;; ???
2322 (";;&" nil sh-handle-prev-case-alt-end) ;Like ";;" with diff semantics.
2323 (";&" nil sh-handle-prev-case-alt-end) ;Like ";;" with diff semantics.
2324 ("done" sh-handle-this-done sh-handle-prev-done)
2325 ("do" sh-handle-this-do sh-handle-prev-do))
2326
2327 ;; Note: we don't need specific stuff for bash and zsh shells;
2328 ;; the regexp `sh-regexp-for-done' handles the extra keywords
2329 ;; these shells use.
2330 (rc
2331 ("{" nil sh-handle-prev-open)
2332 ("}" sh-handle-this-close nil)
2333 ("case" sh-handle-this-rc-case sh-handle-prev-rc-case))))
2334
2335
2336 2191
2337(defun sh-set-shell (shell &optional no-query-flag insert-flag) 2192(defun sh-set-shell (shell &optional no-query-flag insert-flag)
2338 "Set this buffer's shell to SHELL (a string). 2193 "Set this buffer's shell to SHELL (a string).
@@ -2400,16 +2255,6 @@ whose value is the shell name (don't quote it)."
2400 (funcall mksym "rules") 2255 (funcall mksym "rules")
2401 :forward-token (funcall mksym "forward-token") 2256 :forward-token (funcall mksym "forward-token")
2402 :backward-token (funcall mksym "backward-token"))) 2257 :backward-token (funcall mksym "backward-token")))
2403 (unless sh-use-smie
2404 (setq-local sh-kw-alist (sh-feature sh-kw))
2405 (let ((regexp (sh-feature sh-kws-for-done)))
2406 (if regexp
2407 (setq-local sh-regexp-for-done
2408 (sh-mkword-regexpr (regexp-opt regexp t)))))
2409 (message "setting up indent stuff")
2410 ;; sh-mode has already made indent-line-function local
2411 ;; but do it in case this is called before that.
2412 (setq-local indent-line-function #'sh-indent-line))
2413 (if sh-make-vars-local 2258 (if sh-make-vars-local
2414 (sh-make-vars-local)) 2259 (sh-make-vars-local))
2415 (message "Indentation setup for shell type %s" sh-shell)) 2260 (message "Indentation setup for shell type %s" sh-shell))
@@ -2564,11 +2409,6 @@ region, clear header."
2564 (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2))) 2409 (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2)))
2565 2410
2566;; Indentation stuff. 2411;; Indentation stuff.
2567(defun sh-must-support-indent ()
2568 "Signal an error if the shell type for this buffer is not supported.
2569Also, the buffer must be in Shell-script mode."
2570 (unless sh-indent-supported-here
2571 (error "This buffer's shell does not support indentation through Emacs")))
2572 2412
2573(defun sh-make-vars-local () 2413(defun sh-make-vars-local ()
2574 "Make the indentation variables local to this buffer. 2414 "Make the indentation variables local to this buffer.
@@ -2589,654 +2429,12 @@ Then, if variable `sh-make-vars-local' is non-nil, make them local."
2589 (if sh-make-vars-local 2429 (if sh-make-vars-local
2590 (mapcar 'make-local-variable sh-var-list))) 2430 (mapcar 'make-local-variable sh-var-list)))
2591 2431
2592
2593;; Theoretically these are only needed in shell and derived modes.
2594;; However, the routines which use them are only called in those modes.
2595(defconst sh-special-keywords "then\\|do")
2596
2597(defun sh-help-string-for-variable (var)
2598 "Construct a string for `sh-read-variable' when changing variable VAR ."
2599 (let ((msg (documentation-property var 'variable-documentation))
2600 (msg2 ""))
2601 (unless (memq var '(sh-first-lines-indent sh-indent-comment))
2602 (setq msg2
2603 (format "\n
2604You can enter a number (positive to increase indentation,
2605negative to decrease indentation, zero for no change to indentation).
2606
2607Or, you can enter one of the following symbols which are relative to
2608the value of variable `sh-basic-offset'
2609which in this buffer is currently %s.
2610
2611\t%s."
2612 sh-basic-offset
2613 (mapconcat (lambda (x)
2614 (nth (1- (length x)) x))
2615 sh-symbol-list "\n\t"))))
2616 (concat
2617 ;; The following shows the global not the local value!
2618 ;; (format "Current value of %s is %s\n\n" var (symbol-value var))
2619 msg msg2)))
2620
2621(defun sh-read-variable (var)
2622 "Read a new value for indentation variable VAR."
2623 (let ((minibuffer-help-form `(sh-help-string-for-variable
2624 (quote ,var)))
2625 val)
2626 (setq val (read-from-minibuffer
2627 (format "New value for %s (press %s for help): "
2628 var (single-key-description help-char))
2629 (format "%s" (symbol-value var))
2630 nil t))
2631 val))
2632
2633
2634
2635(defun sh-in-comment-or-string (start) 2432(defun sh-in-comment-or-string (start)
2636 "Return non-nil if START is in a comment or string." 2433 "Return non-nil if START is in a comment or string."
2637 (save-excursion 2434 (save-excursion
2638 (let ((state (syntax-ppss start))) 2435 (let ((state (syntax-ppss start)))
2639 (or (nth 3 state) (nth 4 state))))) 2436 (or (nth 3 state) (nth 4 state)))))
2640 2437
2641(defun sh-goto-matching-if ()
2642 "Go to the matching if for a fi.
2643This handles nested if..fi pairs."
2644 (let ((found (sh-find-prev-matching "\\bif\\b" "\\bfi\\b" 1)))
2645 (if found
2646 (goto-char found))))
2647
2648
2649;; Functions named sh-handle-this-XXX are called when the keyword on the
2650;; line whose indentation is being handled contain XXX;
2651;; those named sh-handle-prev-XXX are when XXX appears on the previous line.
2652
2653(defun sh-handle-prev-if ()
2654 (list '(+ sh-indent-after-if)))
2655
2656(defun sh-handle-this-else ()
2657 (if (sh-goto-matching-if)
2658 ;; (list "aligned to if")
2659 (list "aligned to if" '(+ sh-indent-for-else))
2660 nil
2661 ))
2662
2663(defun sh-handle-prev-else ()
2664 (if (sh-goto-matching-if)
2665 (list '(+ sh-indent-after-if))
2666 ))
2667
2668(defun sh-handle-this-fi ()
2669 (if (sh-goto-matching-if)
2670 (list "aligned to if" '(+ sh-indent-for-fi))
2671 nil
2672 ))
2673
2674(defun sh-handle-prev-fi ()
2675 ;; Why do we have this rule? Because we must go back to the if
2676 ;; to get its indent. We may continue back from there.
2677 ;; We return nil because we don't have anything to add to result,
2678 ;; the side affect of setting align-point is all that matters.
2679 ;; we could return a comment (a string) but I can't think of a good one...
2680 (sh-goto-matching-if)
2681 nil)
2682
2683(defun sh-handle-this-then ()
2684 (let ((p (sh-goto-matching-if)))
2685 (if p
2686 (list '(+ sh-indent-for-then))
2687 )))
2688
2689(defun sh-handle-prev-then ()
2690 (let ((p (sh-goto-matching-if)))
2691 (if p
2692 (list '(+ sh-indent-after-if))
2693 )))
2694
2695(defun sh-handle-prev-open ()
2696 (save-excursion
2697 (let ((x (sh-prev-stmt)))
2698 (if (and x
2699 (progn
2700 (goto-char x)
2701 (or
2702 (looking-at "function\\b")
2703 (looking-at "\\s-*\\S-+\\s-*()")
2704 )))
2705 (list '(+ sh-indent-after-function))
2706 (list '(+ sh-indent-after-open)))
2707 )))
2708
2709(defun sh-handle-this-close ()
2710 (forward-char 1) ;; move over ")"
2711 (if (sh-safe-forward-sexp -1)
2712 (list "aligned to opening paren")))
2713
2714(defun sh-goto-matching-case ()
2715 (let ((found (sh-find-prev-matching "\\bcase\\b" "\\besac\\b" 1)))
2716 (if found (goto-char found))))
2717
2718(defun sh-handle-prev-case ()
2719 ;; This is typically called when point is on same line as a case
2720 ;; we shouldn't -- and can't find prev-case
2721 (if (looking-at ".*\\<case\\>")
2722 (list '(+ sh-indent-for-case-label))
2723 (error "We don't seem to be on a line with a case"))) ;; debug
2724
2725(defun sh-handle-this-esac ()
2726 (if (sh-goto-matching-case)
2727 (list "aligned to matching case")))
2728
2729(defun sh-handle-prev-esac ()
2730 (if (sh-goto-matching-case)
2731 (list "matching case")))
2732
2733(defun sh-handle-after-case-label ()
2734 (if (sh-goto-matching-case)
2735 (list '(+ sh-indent-for-case-alt))))
2736
2737(defun sh-handle-prev-case-alt-end ()
2738 (if (sh-goto-matching-case)
2739 (list '(+ sh-indent-for-case-label))))
2740
2741(defun sh-safe-forward-sexp (&optional arg)
2742 "Try and do a `forward-sexp', but do not error.
2743Return new point if successful, nil if an error occurred."
2744 (condition-case nil
2745 (progn
2746 (forward-sexp (or arg 1))
2747 (point)) ;; return point if successful
2748 (error
2749 (sh-debug "oops!(1) %d" (point))
2750 nil))) ;; return nil if fail
2751
2752(defun sh-goto-match-for-done ()
2753 (let ((found (sh-find-prev-matching sh-regexp-for-done sh-re-done 1)))
2754 (if found
2755 (goto-char found))))
2756
2757(defun sh-handle-this-done ()
2758 (if (sh-goto-match-for-done)
2759 (list "aligned to do stmt" '(+ sh-indent-for-done))))
2760
2761(defun sh-handle-prev-done ()
2762 (if (sh-goto-match-for-done)
2763 (list "previous done")))
2764
2765(defun sh-handle-this-do ()
2766 (if (sh-goto-match-for-done)
2767 (list '(+ sh-indent-for-do))))
2768
2769(defun sh-handle-prev-do ()
2770 (cond
2771 ((save-restriction
2772 (narrow-to-region (point) (line-beginning-position))
2773 (sh-goto-match-for-done))
2774 (sh-debug "match for done found on THIS line")
2775 (list '(+ sh-indent-after-loop-construct)))
2776 ((sh-goto-match-for-done)
2777 (sh-debug "match for done found on PREV line")
2778 (list '(+ sh-indent-after-do)))
2779 (t
2780 (message "match for done NOT found")
2781 nil)))
2782
2783;; for rc:
2784(defun sh-find-prev-switch ()
2785 "Find the line for the switch keyword matching this line's case keyword."
2786 (re-search-backward "\\<switch\\>" nil t))
2787
2788(defun sh-handle-this-rc-case ()
2789 (if (sh-find-prev-switch)
2790 (list '(+ sh-indent-after-switch))
2791 ;; (list '(+ sh-indent-for-case-label))
2792 nil))
2793
2794(defun sh-handle-prev-rc-case ()
2795 (list '(+ sh-indent-after-case)))
2796
2797(defun sh-check-rule (n thing)
2798 (let ((rule (nth n (assoc thing sh-kw-alist)))
2799 (val nil))
2800 (if rule
2801 (progn
2802 (setq val (funcall rule))
2803 (sh-debug "rule (%d) for %s at %d is %s\n-> returned %s"
2804 n thing (point) rule val)))
2805 val))
2806
2807
2808(defun sh-get-indent-info ()
2809 "Return indent-info for this line.
2810This is a list. nil means the line is to be left as is.
2811Otherwise it contains one or more of the following sublists:
2812\(t NUMBER) NUMBER is the base location in the buffer that indentation is
2813 relative to. If present, this is always the first of the
2814 sublists. The indentation of the line in question is
2815 derived from the indentation of this point, possibly
2816 modified by subsequent sublists.
2817\(+ VAR)
2818\(- VAR) Get the value of variable VAR and add to or subtract from
2819 the indentation calculated so far.
2820\(= VAR) Get the value of variable VAR and *replace* the
2821 indentation with its value. This only occurs for
2822 special variables such as `sh-indent-comment'.
2823STRING This is ignored for the purposes of calculating
2824 indentation, it is printed in certain cases to help show
2825 what the indentation is based on."
2826 ;; See comments before `sh-kw'.
2827 (save-excursion
2828 (let ((have-result nil)
2829 this-kw
2830 val
2831 (result nil)
2832 (align-point nil)
2833 prev-line-end x)
2834 (beginning-of-line)
2835 ;; Note: setting result to t means we are done and will return nil.
2836 ;;(This function never returns just t.)
2837 (cond
2838 ((or (nth 3 (syntax-ppss (point)))
2839 (eq (get-text-property (point) 'face) 'sh-heredoc))
2840 ;; String continuation -- don't indent
2841 (setq result t)
2842 (setq have-result t))
2843 ((looking-at "\\s-*#") ; was (equal this-kw "#")
2844 (if (bobp)
2845 (setq result t) ;; return nil if 1st line!
2846 (setq result (list '(= sh-indent-comment)))
2847 ;; we still need to get previous line in case
2848 ;; sh-indent-comment is t (indent as normal)
2849 (setq align-point (sh-prev-line nil))
2850 (setq have-result nil)
2851 ))
2852 ) ;; cond
2853
2854 (unless have-result
2855 ;; Continuation lines are handled specially
2856 (if (sh-this-is-a-continuation)
2857 (progn
2858 (setq result
2859 (if (save-excursion
2860 (beginning-of-line)
2861 (not (memq (char-before (- (point) 2)) '(?\s ?\t))))
2862 ;; By convention, if the continuation \ is not
2863 ;; preceded by a SPC or a TAB it means that the line
2864 ;; is cut at a place where spaces cannot be freely
2865 ;; added/removed. I.e. do not indent the line.
2866 (list '(= nil))
2867 ;; We assume the line being continued is already
2868 ;; properly indented...
2869 ;; (setq prev-line-end (sh-prev-line))
2870 (setq align-point (sh-prev-line nil))
2871 (list '(+ sh-indent-for-continuation))))
2872 (setq have-result t))
2873 (beginning-of-line)
2874 (skip-chars-forward " \t")
2875 (setq this-kw (sh-get-kw)))
2876
2877 ;; Handle "this" keyword: first word on the line we're
2878 ;; calculating indentation info for.
2879 (if this-kw
2880 (if (setq val (sh-check-rule 1 this-kw))
2881 (progn
2882 (setq align-point (point))
2883 (sh-debug
2884 "this - setting align-point to %d" align-point)
2885 (setq result (append result val))
2886 (setq have-result t)
2887 ;; set prev-line to continue processing remainder
2888 ;; of this line as a previous line
2889 (setq prev-line-end (point))
2890 ))))
2891
2892 (unless have-result
2893 (setq prev-line-end (sh-prev-line 'end)))
2894
2895 (if prev-line-end
2896 (save-excursion
2897 ;; We start off at beginning of this line.
2898 ;; Scan previous statements while this is <=
2899 ;; start of previous line.
2900 (goto-char prev-line-end)
2901 (setq x t)
2902 (while (and x (setq x (sh-prev-thing)))
2903 (sh-debug "at %d x is: %s result is: %s" (point) x result)
2904 (cond
2905 ((and (equal x ")")
2906 (equal (get-text-property (1- (point)) 'syntax-table)
2907 sh-st-punc))
2908 (sh-debug "Case label) here")
2909 (setq x 'case-label)
2910 (if (setq val (sh-check-rule 2 x))
2911 (progn
2912 (setq result (append result val))
2913 (setq align-point (point))))
2914 (or (bobp)
2915 (forward-char -1))
2916 (skip-chars-forward "*0-9?[]a-z")
2917 )
2918 ((string-match "[])}]" x)
2919 (setq x (sh-safe-forward-sexp -1))
2920 (if x
2921 (progn
2922 (setq align-point (point))
2923 (setq result (append result
2924 (list "aligned to opening paren")))
2925 )))
2926 ((string-match "[[({]" x)
2927 (sh-debug "Checking special thing: %s" x)
2928 (if (setq val (sh-check-rule 2 x))
2929 (setq result (append result val)))
2930 (forward-char -1)
2931 (setq align-point (point)))
2932 ((string-match "[\"'`]" x)
2933 (sh-debug "Skipping back for %s" x)
2934 ;; this was oops-2
2935 (setq x (sh-safe-forward-sexp -1)))
2936 ((stringp x)
2937 (sh-debug "Checking string %s at %s" x (point))
2938 (if (setq val (sh-check-rule 2 x))
2939 ;; (or (eq t (car val))
2940 ;; (eq t (car (car val))))
2941 (setq result (append result val)))
2942 ;; not sure about this test Wed Jan 27 23:48:35 1999
2943 (setq align-point (point))
2944 (unless (bolp)
2945 (forward-char -1)))
2946 (t
2947 (error "Don't know what to do with %s" x))
2948 )
2949 ) ;; while
2950 (sh-debug "result is %s" result)
2951 )
2952 (sh-debug "No prev line!")
2953 (sh-debug "result: %s align-point: %s" result align-point)
2954 )
2955
2956 (if align-point
2957 ;; was: (setq result (append result (list (list t align-point))))
2958 (setq result (append (list (list t align-point)) result))
2959 )
2960 (sh-debug "result is now: %s" result)
2961
2962 (or result
2963 (setq result (list (if prev-line-end
2964 (list t prev-line-end)
2965 (list '= 'sh-first-lines-indent)))))
2966
2967 (if (eq result t)
2968 (setq result nil))
2969 (sh-debug "result is: %s" result)
2970 result
2971 ) ;; let
2972 ))
2973
2974
2975(defun sh-get-indent-var-for-line (&optional info)
2976 "Return the variable controlling indentation for this line.
2977If there is not [just] one such variable, return a string
2978indicating the problem.
2979If INFO is supplied it is used, else it is calculated."
2980 (let ((var nil)
2981 (result nil)
2982 (reason nil)
2983 sym elt)
2984 (or info
2985 (setq info (sh-get-indent-info)))
2986 (if (null info)
2987 (setq result "this line to be left as is")
2988 (while (and info (null result))
2989 (setq elt (car info))
2990 (cond
2991 ((stringp elt)
2992 (setq reason elt)
2993 )
2994 ((not (listp elt))
2995 (error "sh-get-indent-var-for-line invalid elt: %s" elt))
2996 ;; so it is a list
2997 ((eq t (car elt))
2998 ) ;; nothing
2999 ((symbolp (setq sym (nth 1 elt)))
3000 ;; A bit of a kludge - when we see the sh-indent-comment
3001 ;; ignore other variables. Otherwise it is tricky to
3002 ;; "learn" the comment indentation.
3003 (if (eq var 'sh-indent-comment)
3004 (setq result var)
3005 (if var
3006 (setq result
3007 "this line is controlled by more than 1 variable.")
3008 (setq var sym))))
3009 (t
3010 (error "sh-get-indent-var-for-line invalid list elt: %s" elt)))
3011 (setq info (cdr info))
3012 ))
3013 (or result
3014 (setq result var))
3015 (or result
3016 (setq result reason))
3017 (if (null result)
3018 ;; e.g. just had (t POS)
3019 (setq result "line has default indentation"))
3020 result))
3021
3022
3023
3024;; Finding the previous line isn't trivial.
3025;; We must *always* go back one more and see if that is a continuation
3026;; line -- it is the PREVIOUS line which is continued, not the one
3027;; we are going to!
3028;; Also, we want to treat a whole "here document" as one big line,
3029;; because we may want to align to the beginning of it.
3030;;
3031;; What we do:
3032;; - go back to previous non-empty line
3033;; - if this is in a here-document, go to the beginning of it
3034;; - while previous line is continued, go back one line
3035(defun sh-prev-line (&optional end)
3036 "Back to end of previous non-comment non-empty line.
3037Go to beginning of logical line unless END is non-nil, in which case
3038we go to the end of the previous line and do not check for continuations."
3039 (save-excursion
3040 (beginning-of-line)
3041 (forward-comment (- (point-max)))
3042 (unless end (beginning-of-line))
3043 (when (and (not (bobp))
3044 (eq (get-text-property (1- (point)) 'face) 'sh-heredoc))
3045 (let ((p1 (previous-single-property-change (1- (point)) 'face)))
3046 (when p1
3047 (goto-char p1)
3048 (if end
3049 (end-of-line)
3050 (beginning-of-line)))))
3051 (unless end
3052 ;; we must check previous lines to see if they are continuation lines
3053 ;; if so, we must return position of first of them
3054 (while (and (sh-this-is-a-continuation)
3055 (>= 0 (forward-line -1))))
3056 (beginning-of-line)
3057 (skip-chars-forward " \t"))
3058 (point)))
3059
3060
3061(defun sh-prev-stmt ()
3062 "Return the address of the previous stmt or nil."
3063 ;; This is used when we are trying to find a matching keyword.
3064 ;; Searching backward for the keyword would certainly be quicker, but
3065 ;; it is hard to remove "false matches" -- such as if the keyword
3066 ;; appears in a string or quote. This way is slower, but (I think) safer.
3067 (interactive)
3068 (save-excursion
3069 (let ((going t)
3070 (start (point))
3071 (found nil)
3072 (prev nil))
3073 (skip-chars-backward " \t;|&({[")
3074 (while (and (not found)
3075 (not (bobp))
3076 going)
3077 ;; Do a backward-sexp if possible, else backup bit by bit...
3078 (if (sh-safe-forward-sexp -1)
3079 (progn
3080 (if (looking-at sh-special-keywords)
3081 (progn
3082 (setq found prev))
3083 (setq prev (point))
3084 ))
3085 ;; backward-sexp failed
3086 (if (zerop (skip-chars-backward " \t()[]{};`'"))
3087 (forward-char -1))
3088 (if (bolp)
3089 (let ((back (sh-prev-line nil)))
3090 (if back
3091 (goto-char back)
3092 (setq going nil)))))
3093 (unless found
3094 (skip-chars-backward " \t")
3095 (if (or (and (bolp) (not (sh-this-is-a-continuation)))
3096 (eq (char-before) ?\;)
3097 (looking-at "\\s-*[|&]"))
3098 (setq found (point)))))
3099 (if found
3100 (goto-char found))
3101 (if found
3102 (progn
3103 (skip-chars-forward " \t|&({[")
3104 (setq found (point))))
3105 (if (>= (point) start)
3106 (progn
3107 (debug "We didn't move!")
3108 (setq found nil))
3109 (or found
3110 (sh-debug "Did not find prev stmt.")))
3111 found)))
3112
3113
3114(defun sh-get-word ()
3115 "Get a shell word skipping whitespace from point."
3116 (interactive)
3117 (skip-chars-forward "\t ")
3118 (let ((start (point)))
3119 (while
3120 (if (looking-at "[\"'`]")
3121 (sh-safe-forward-sexp)
3122 ;; (> (skip-chars-forward "^ \t\n\"'`") 0)
3123 (> (skip-chars-forward "-_$[:alnum:]") 0)
3124 ))
3125 (buffer-substring start (point))
3126 ))
3127
3128(defun sh-prev-thing ()
3129 "Return the previous thing this logical line."
3130 ;; This is called when `sh-get-indent-info' is working backwards on
3131 ;; the previous line(s) finding what keywords may be relevant for
3132 ;; indenting. It moves over sexps if possible, and will stop
3133 ;; on a ; and at the beginning of a line if it is not a continuation
3134 ;; line.
3135 ;;
3136 ;; Added a kludge for ";;"
3137 ;; Possible return values:
3138 ;; nil - nothing
3139 ;; a string - possibly a keyword
3140 ;;
3141 (if (bolp)
3142 nil
3143 (let ((start (point))
3144 (min-point (if (sh-this-is-a-continuation)
3145 (sh-prev-line nil)
3146 (line-beginning-position))))
3147 (skip-chars-backward " \t;" min-point)
3148 (if (looking-at "\\s-*;[;&]")
3149 ;; (message "Found ;; !")
3150 ";;"
3151 (skip-chars-backward "^)}];\"'`({[" min-point)
3152 (let ((c (if (> (point) min-point) (char-before))))
3153 (sh-debug "stopping at %d c is %s start=%d min-point=%d"
3154 (point) c start min-point)
3155 (if (not (memq c '(?\n nil ?\;)))
3156 ;; c -- return a string
3157 (char-to-string c)
3158 ;; Return the leading keyword of the "command" we supposedly
3159 ;; skipped over. Maybe we skipped too far (e.g. past a `do' or
3160 ;; `then' that precedes the actual command), so check whether
3161 ;; we're looking at such a keyword and if so, move back forward.
3162 (let ((boundary (point))
3163 kwd next)
3164 (while
3165 (progn
3166 ;; Skip forward over white space newline and \ at eol.
3167 (skip-chars-forward " \t\n\\\\" start)
3168 (if (>= (point) start)
3169 (progn
3170 (sh-debug "point: %d >= start: %d" (point) start)
3171 nil)
3172 (if next (setq boundary next))
3173 (sh-debug "Now at %d start=%d" (point) start)
3174 (setq kwd (sh-get-word))
3175 (if (member kwd (sh-feature sh-leading-keywords))
3176 (progn
3177 (setq next (point))
3178 t)
3179 nil))))
3180 (goto-char boundary)
3181 kwd)))))))
3182
3183
3184(defun sh-this-is-a-continuation ()
3185 "Return non-nil if current line is a continuation of previous line."
3186 (save-excursion
3187 (and (zerop (forward-line -1))
3188 (looking-at ".*\\\\$")
3189 (not (nth 4 (parse-partial-sexp (match-beginning 0) (match-end 0)
3190 nil nil nil t))))))
3191
3192(defun sh-get-kw (&optional where and-move)
3193 "Return first word of line from WHERE.
3194If AND-MOVE is non-nil then move to end of word."
3195 (let ((start (point)))
3196 (if where
3197 (goto-char where))
3198 (prog1
3199 (buffer-substring (point)
3200 (progn (skip-chars-forward "^ \t\n;&|")(point)))
3201 (unless and-move
3202 (goto-char start)))))
3203
3204(defun sh-find-prev-matching (open close &optional depth)
3205 "Find a matching token for a set of opening and closing keywords.
3206This takes into account that there may be nested open..close pairings.
3207OPEN and CLOSE are regexps denoting the tokens to be matched.
3208Optional parameter DEPTH (usually 1) says how many to look for."
3209 (let ((parse-sexp-ignore-comments t)
3210 (forward-sexp-function nil)
3211 prev)
3212 (setq depth (or depth 1))
3213 (save-excursion
3214 (condition-case nil
3215 (while (and
3216 (/= 0 depth)
3217 (not (bobp))
3218 (setq prev (sh-prev-stmt)))
3219 (goto-char prev)
3220 (save-excursion
3221 (if (looking-at "\\\\\n")
3222 (progn
3223 (forward-char 2)
3224 (skip-chars-forward " \t")))
3225 (cond
3226 ((looking-at open)
3227 (setq depth (1- depth))
3228 (sh-debug "found open at %d - depth = %d" (point) depth))
3229 ((looking-at close)
3230 (setq depth (1+ depth))
3231 (sh-debug "found close - depth = %d" depth))
3232 (t
3233 ))))
3234 (error nil))
3235 (if (eq depth 0)
3236 prev ;; (point)
3237 nil)
3238 )))
3239
3240 2438
3241(defun sh-var-value (var &optional ignore-error) 2439(defun sh-var-value (var &optional ignore-error)
3242 "Return the value of variable VAR, interpreting symbols. 2440 "Return the value of variable VAR, interpreting symbols.
@@ -3268,620 +2466,16 @@ IGNORE-ERROR is non-nil."
3268 "Don't know how to handle %s's value of %s" var val) 2466 "Don't know how to handle %s's value of %s" var val)
3269 0)))) 2467 0))))
3270 2468
3271(defun sh-set-var-value (var value &optional no-symbol) 2469(define-obsolete-function-alias 'sh-show-indent
3272 "Set variable VAR to VALUE. 2470 #'smie-config-show-indent "28.1")
3273Unless optional argument NO-SYMBOL is non-nil, then if VALUE is
3274can be represented by a symbol then do so."
3275 (cond
3276 (no-symbol
3277 (set var value))
3278 ((= value sh-basic-offset)
3279 (set var '+))
3280 ((= value (- sh-basic-offset))
3281 (set var '-))
3282 ((eq value (* 2 sh-basic-offset))
3283 (set var '++))
3284 ((eq value (* 2 (- sh-basic-offset)))
3285 (set var '--))
3286 ((eq value (/ sh-basic-offset 2))
3287 (set var '*))
3288 ((eq value (/ (- sh-basic-offset) 2))
3289 (set var '/))
3290 (t
3291 (set var value)))
3292 )
3293
3294
3295(defun sh-calculate-indent (&optional info)
3296 "Return the indentation for the current line.
3297If INFO is supplied it is used, else it is calculated from current line."
3298 (let ((ofs 0)
3299 (base-value 0)
3300 elt a b val)
3301 (or info
3302 (setq info (sh-get-indent-info)))
3303 (when info
3304 (while info
3305 (sh-debug "info: %s ofs=%s" info ofs)
3306 (setq elt (car info))
3307 (cond
3308 ((stringp elt)) ;; do nothing?
3309 ((listp elt)
3310 (setq a (car (car info)))
3311 (setq b (nth 1 (car info)))
3312 (cond
3313 ((eq a t)
3314 (save-excursion
3315 (goto-char b)
3316 (setq val (current-indentation)))
3317 (setq base-value val))
3318 ((symbolp b)
3319 (setq val (sh-var-value b))
3320 (cond
3321 ((eq a '=)
3322 (cond
3323 ((null val)
3324 ;; no indentation
3325 ;; set info to nil so we stop immediately
3326 (setq base-value nil ofs nil info nil))
3327 ((eq val t) (setq ofs 0)) ;; indent as normal line
3328 (t
3329 ;; The following assume the (t POS) come first!
3330 (setq ofs val base-value 0)
3331 (setq info nil)))) ;; ? stop now
3332 ((eq a '+) (setq ofs (+ ofs val)))
3333 ((eq a '-) (setq ofs (- ofs val)))
3334 (t
3335 (error "sh-calculate-indent invalid a a=%s b=%s" a b))))
3336 (t
3337 (error "sh-calculate-indent invalid elt: a=%s b=%s" a b))))
3338 (t
3339 (error "sh-calculate-indent invalid elt %s" elt)))
3340 (sh-debug "a=%s b=%s val=%s base-value=%s ofs=%s"
3341 a b val base-value ofs)
3342 (setq info (cdr info)))
3343 ;; return value:
3344 (sh-debug "at end: base-value: %s ofs: %s" base-value ofs)
3345
3346 (cond
3347 ((or (null base-value)(null ofs))
3348 nil)
3349 ((and (numberp base-value)(numberp ofs))
3350 (sh-debug "base (%d) + ofs (%d) = %d"
3351 base-value ofs (+ base-value ofs))
3352 (+ base-value ofs)) ;; return value
3353 (t
3354 (error "sh-calculate-indent: Help. base-value=%s ofs=%s"
3355 base-value ofs)
3356 nil)))))
3357 2471
2472(define-obsolete-function-alias 'sh-set-indent #'smie-config-set-indent "28.1")
3358 2473
3359(defun sh-indent-line () 2474(define-obsolete-function-alias 'sh-learn-line-indent
3360 "Indent the current line." 2475 #'smie-config-set-indent "28.1")
3361 (interactive)
3362 (let ((indent (sh-calculate-indent))
3363 (pos (- (point-max) (point))))
3364 (when indent
3365 (beginning-of-line)
3366 (skip-chars-forward " \t")
3367 (indent-line-to indent)
3368 ;; If initial point was within line's indentation,
3369 ;; position after the indentation. Else stay at same point in text.
3370 (if (> (- (point-max) pos) (point))
3371 (goto-char (- (point-max) pos))))))
3372
3373
3374(defun sh-blink (blinkpos &optional msg)
3375 "Move cursor momentarily to BLINKPOS and display MSG."
3376 ;; We can get here without it being a number on first line
3377 (if (numberp blinkpos)
3378 (save-excursion
3379 (goto-char blinkpos)
3380 (if msg (message "%s" msg) (message nil))
3381 (sit-for blink-matching-delay))
3382 (if msg (message "%s" msg) (message nil))))
3383
3384(defun sh-show-indent (arg)
3385 "Show how the current line would be indented.
3386This tells you which variable, if any, controls the indentation of
3387this line.
3388If optional arg ARG is non-null (called interactively with a prefix),
3389a pop up window describes this variable.
3390If variable `sh-blink' is non-nil then momentarily go to the line
3391we are indenting relative to, if applicable."
3392 (interactive "P")
3393 (sh-must-support-indent)
3394 (if sh-use-smie
3395 (smie-config-show-indent)
3396 (let* ((info (sh-get-indent-info))
3397 (var (sh-get-indent-var-for-line info))
3398 (curr-indent (current-indentation))
3399 val msg)
3400 (if (stringp var)
3401 (message "%s" (setq msg var))
3402 (setq val (sh-calculate-indent info))
3403
3404 (if (eq curr-indent val)
3405 (setq msg (format "%s is %s" var (symbol-value var)))
3406 (setq msg
3407 (if val
3408 (format "%s (%s) would change indent from %d to: %d"
3409 var (symbol-value var) curr-indent val)
3410 (format "%s (%s) would leave line as is"
3411 var (symbol-value var)))
3412 ))
3413 (if (and arg var)
3414 (describe-variable var)))
3415 (if sh-blink
3416 (let ((info (sh-get-indent-info)))
3417 (if (and info (listp (car info))
3418 (eq (car (car info)) t))
3419 (sh-blink (nth 1 (car info)) msg)
3420 (message "%s" msg)))
3421 (message "%s" msg))
3422 )))
3423 2476
3424(defun sh-set-indent () 2477(define-obsolete-function-alias 'sh-learn-buffer-indent
3425 "Set the indentation for the current line. 2478 #'smie-config-guess "28.1")
3426If the current line is controlled by an indentation variable, prompt
3427for a new value for it."
3428 (interactive)
3429 (sh-must-support-indent)
3430 (if sh-use-smie
3431 (smie-config-set-indent)
3432 (let* ((info (sh-get-indent-info))
3433 (var (sh-get-indent-var-for-line info))
3434 val old-val indent-val)
3435 (if (stringp var)
3436 (message "Cannot set indent - %s" var)
3437 (setq old-val (symbol-value var))
3438 (setq val (sh-read-variable var))
3439 (condition-case nil
3440 (progn
3441 (set var val)
3442 (setq indent-val (sh-calculate-indent info))
3443 (if indent-val
3444 (message "Variable: %s Value: %s would indent to: %d"
3445 var (symbol-value var) indent-val)
3446 (message "Variable: %s Value: %s would leave line as is."
3447 var (symbol-value var)))
3448 ;; I'm not sure about this, indenting it now?
3449 ;; No. Because it would give the impression that an undo would
3450 ;; restore thing, but the value has been altered.
3451 ;; (sh-indent-line)
3452 )
3453 (error
3454 (set var old-val)
3455 (message "Bad value for %s, restoring to previous value %s"
3456 var old-val)
3457 (sit-for 1)
3458 nil))
3459 ))))
3460
3461
3462(defun sh-learn-line-indent (arg)
3463 "Learn how to indent a line as it currently is indented.
3464
3465If there is an indentation variable which controls this line's indentation,
3466then set it to a value which would indent the line the way it
3467presently is.
3468
3469If the value can be represented by one of the symbols then do so
3470unless optional argument ARG (the prefix when interactive) is non-nil."
3471 (interactive "*P")
3472 (sh-must-support-indent)
3473 (if sh-use-smie
3474 (smie-config-set-indent)
3475 ;; I'm not sure if we show allow learning on an empty line.
3476 ;; Though it might occasionally be useful I think it usually
3477 ;; would just be confusing.
3478 (if (save-excursion
3479 (beginning-of-line)
3480 (looking-at "\\s-*$"))
3481 (message "sh-learn-line-indent ignores empty lines.")
3482 (let* ((info (sh-get-indent-info))
3483 (var (sh-get-indent-var-for-line info))
3484 ival sval diff new-val
3485 (no-symbol arg)
3486 (curr-indent (current-indentation)))
3487 (cond
3488 ((stringp var)
3489 (message "Cannot learn line - %s" var))
3490 ((eq var 'sh-indent-comment)
3491 ;; This is arbitrary...
3492 ;; - if curr-indent is 0, set to curr-indent
3493 ;; - else if it has the indentation of a "normal" line,
3494 ;; then set to t
3495 ;; - else set to curr-indent.
3496 (setq sh-indent-comment
3497 (if (= curr-indent 0)
3498 0
3499 (let* ((sh-indent-comment t)
3500 (val2 (sh-calculate-indent info)))
3501 (if (= val2 curr-indent)
3502 t
3503 curr-indent))))
3504 (message "%s set to %s" var (symbol-value var))
3505 )
3506 ((numberp (setq sval (sh-var-value var)))
3507 (setq ival (sh-calculate-indent info))
3508 (setq diff (- curr-indent ival))
3509
3510 (sh-debug "curr-indent: %d ival: %d diff: %d var:%s sval %s"
3511 curr-indent ival diff var sval)
3512 (setq new-val (+ sval diff))
3513 ;; I commented out this because someone might want to replace
3514 ;; a value of `+' with the current value of sh-basic-offset
3515 ;; or vice-versa.
3516 ;;(if (= 0 diff)
3517 ;; (message "No change needed!")
3518 (sh-set-var-value var new-val no-symbol)
3519 (message "%s set to %s" var (symbol-value var))
3520 )
3521 (t
3522 (debug)
3523 (message "Cannot change %s" var)))))))
3524
3525
3526
3527(defun sh-mark-init (buffer)
3528 "Initialize a BUFFER to be used by `sh-mark-line'."
3529 (with-current-buffer (get-buffer-create buffer)
3530 (erase-buffer)
3531 (occur-mode)))
3532
3533
3534(defun sh-mark-line (message point buffer &optional add-linenum occur-point)
3535 "Insert MESSAGE referring to location POINT in current buffer into BUFFER.
3536Buffer BUFFER is in `occur-mode'.
3537If ADD-LINENUM is non-nil the message is preceded by the line number.
3538If OCCUR-POINT is non-nil then the line is marked as a new occurrence
3539so that `occur-next' and `occur-prev' will work."
3540 (let ((m1 (make-marker))
3541 start
3542 (line ""))
3543 (when point
3544 (set-marker m1 point (current-buffer))
3545 (if add-linenum
3546 (setq line (format "%d: " (1+ (count-lines 1 point))))))
3547 (save-excursion
3548 (if (get-buffer buffer)
3549 (set-buffer (get-buffer buffer))
3550 (set-buffer (get-buffer-create buffer))
3551 (occur-mode)
3552 )
3553 (goto-char (point-max))
3554 (setq start (point))
3555 (let ((inhibit-read-only t))
3556 (insert line)
3557 (if occur-point
3558 (setq occur-point (point)))
3559 (insert message)
3560 (if point
3561 (add-text-properties
3562 start (point)
3563 '(mouse-face highlight
3564 help-echo "mouse-2: go to the line where I learned this")))
3565 (insert "\n")
3566 (when point
3567 (put-text-property start (point) 'occur-target m1)
3568 (if occur-point
3569 (put-text-property start occur-point
3570 'occur-match t))
3571 )))))
3572
3573;; Is this really worth having?
3574(defvar sh-learned-buffer-hook nil
3575 "An abnormal hook, called with an alist of learned variables.")
3576;; Example of how to use sh-learned-buffer-hook
3577;;
3578;; (defun what-i-learned (list)
3579;; (let ((p list))
3580;; (with-current-buffer "*scratch*"
3581;; (goto-char (point-max))
3582;; (insert "(setq\n")
3583;; (while p
3584;; (insert (format " %s %s \n"
3585;; (nth 0 (car p)) (nth 1 (car p))))
3586;; (setq p (cdr p)))
3587;; (insert ")\n")
3588;; )))
3589;;
3590;; (add-hook 'sh-learned-buffer-hook #'what-i-learned)
3591
3592
3593;; Originally this was sh-learn-region-indent (beg end)
3594;; However, in practice this was awkward so I changed it to
3595;; use the whole buffer. Use narrowing if need be.
3596(defun sh-learn-buffer-indent (&optional arg)
3597 "Learn how to indent the buffer the way it currently is.
3598
3599If `sh-use-smie' is non-nil, call `smie-config-guess'.
3600Otherwise, run the sh-script specific indent learning command, as
3601described below.
3602
3603Output in buffer \"*indent*\" shows any lines which have conflicting
3604values of a variable, and the final value of all variables learned.
3605When called interactively, pop to this buffer automatically if
3606there are any discrepancies.
3607
3608If no prefix ARG is given, then variables are set to numbers.
3609If a prefix arg is given, then variables are set to symbols when
3610applicable -- e.g. to symbol `+' if the value is that of the
3611basic indent.
3612If a positive numerical prefix is given, then `sh-basic-offset'
3613is set to the prefix's numerical value.
3614Otherwise, sh-basic-offset may or may not be changed, according
3615to the value of variable `sh-learn-basic-offset'.
3616
3617Abnormal hook `sh-learned-buffer-hook' if non-nil is called when the
3618function completes. The function is abnormal because it is called
3619with an alist of variables learned.
3620
3621This command can often take a long time to run."
3622 (interactive "P")
3623 (sh-must-support-indent)
3624 (if sh-use-smie
3625 (smie-config-guess)
3626 (save-excursion
3627 (goto-char (point-min))
3628 (let ((learned-var-list nil)
3629 (out-buffer "*indent*")
3630 (num-diffs 0)
3631 previous-set-info
3632 (max 17)
3633 vec
3634 msg
3635 (comment-col nil) ;; number if all same, t if seen diff values
3636 (comments-always-default t) ;; nil if we see one not default
3637 initial-msg
3638 (specified-basic-offset (and arg (numberp arg)
3639 (> arg 0)))
3640 (linenum 0)
3641 suggested)
3642 (setq vec (make-vector max 0))
3643 (sh-mark-init out-buffer)
3644
3645 (if specified-basic-offset
3646 (progn
3647 (setq sh-basic-offset arg)
3648 (setq initial-msg
3649 (format "Using specified sh-basic-offset of %d"
3650 sh-basic-offset)))
3651 (setq initial-msg
3652 (format "Initial value of sh-basic-offset: %s"
3653 sh-basic-offset)))
3654
3655 (while (< (point) (point-max))
3656 (setq linenum (1+ linenum))
3657 ;; (if (zerop (% linenum 10))
3658 (message "line %d" linenum)
3659 ;; )
3660 (unless (looking-at "\\s-*$") ;; ignore empty lines!
3661 (let* ((sh-indent-comment t) ;; info must return default indent
3662 (info (sh-get-indent-info))
3663 (var (sh-get-indent-var-for-line info))
3664 sval ival diff new-val
3665 (curr-indent (current-indentation)))
3666 (cond
3667 ((null var)
3668 nil)
3669 ((stringp var)
3670 nil)
3671 ((numberp (setq sval (sh-var-value var 'no-error)))
3672 ;; the numberp excludes comments since sval will be t.
3673 (setq ival (sh-calculate-indent))
3674 (setq diff (- curr-indent ival))
3675 (setq new-val (+ sval diff))
3676 (sh-set-var-value var new-val 'no-symbol)
3677 (unless (looking-at "\\s-*#") ;; don't learn from comments
3678 (if (setq previous-set-info (assoc var learned-var-list))
3679 (progn
3680 ;; it was already there, is it same value ?
3681 (unless (eq (symbol-value var)
3682 (nth 1 previous-set-info))
3683 (sh-mark-line
3684 (format "Variable %s was set to %s"
3685 var (symbol-value var))
3686 (point) out-buffer t t)
3687 (sh-mark-line
3688 (format " but was previously set to %s"
3689 (nth 1 previous-set-info))
3690 (nth 2 previous-set-info) out-buffer t)
3691 (setq num-diffs (1+ num-diffs))
3692 ;; (delete previous-set-info learned-var-list)
3693 (setcdr previous-set-info
3694 (list (symbol-value var) (point)))
3695 )
3696 )
3697 (setq learned-var-list
3698 (append (list (list var (symbol-value var)
3699 (point)))
3700 learned-var-list)))
3701 (if (numberp new-val)
3702 (progn
3703 (sh-debug
3704 "This line's indent value: %d" new-val)
3705 (if (< new-val 0)
3706 (setq new-val (- new-val)))
3707 (if (< new-val max)
3708 (aset vec new-val (1+ (aref vec new-val))))))
3709 ))
3710 ((eq var 'sh-indent-comment)
3711 (unless (= curr-indent (sh-calculate-indent info))
3712 ;; this is not the default indentation
3713 (setq comments-always-default nil)
3714 (if comment-col ;; then we have see one before
3715 (or (eq comment-col curr-indent)
3716 (setq comment-col t)) ;; seen a different one
3717 (setq comment-col curr-indent))
3718 ))
3719 (t
3720 (sh-debug "Cannot learn this line!!!")
3721 ))
3722 (sh-debug
3723 "at %s learned-var-list is %s" (point) learned-var-list)
3724 ))
3725 (forward-line 1)
3726 ) ;; while
3727 (if sh-debug
3728 (progn
3729 (setq msg (format
3730 "comment-col = %s comments-always-default = %s"
3731 comment-col comments-always-default))
3732 ;; (message msg)
3733 (sh-mark-line msg nil out-buffer)))
3734 (cond
3735 ((eq comment-col 0)
3736 (setq msg "\nComments are all in 1st column.\n"))
3737 (comments-always-default
3738 (setq msg "\nComments follow default indentation.\n")
3739 (setq comment-col t))
3740 ((numberp comment-col)
3741 (setq msg (format "\nComments are in col %d." comment-col)))
3742 (t
3743 (setq msg "\nComments seem to be mixed, leaving them as is.\n")
3744 (setq comment-col nil)
3745 ))
3746 (sh-debug msg)
3747 (sh-mark-line msg nil out-buffer)
3748
3749 (sh-mark-line initial-msg nil out-buffer t t)
3750
3751 (setq suggested (sh-guess-basic-offset vec))
3752
3753 (if (and suggested (not specified-basic-offset))
3754 (let ((new-value
3755 (cond
3756 ;; t => set it if we have a single value as a number
3757 ((and (eq sh-learn-basic-offset t) (numberp suggested))
3758 suggested)
3759 ;; other non-nil => set it if only one value was found
3760 (sh-learn-basic-offset
3761 (if (numberp suggested)
3762 suggested
3763 (if (= (length suggested) 1)
3764 (car suggested))))
3765 (t
3766 nil))))
3767 (if new-value
3768 (progn
3769 (setq learned-var-list
3770 (append (list (list 'sh-basic-offset
3771 (setq sh-basic-offset new-value)
3772 (point-max)))
3773 learned-var-list))
3774 ;; Not sure if we need to put this line in, since
3775 ;; it will appear in the "Learned variable settings".
3776 (sh-mark-line
3777 (format "Changed sh-basic-offset to: %d" sh-basic-offset)
3778 nil out-buffer))
3779 (sh-mark-line
3780 (if (listp suggested)
3781 (format "Possible value(s) for sh-basic-offset: %s"
3782 (mapconcat 'int-to-string suggested " "))
3783 (format "Suggested sh-basic-offset: %d" suggested))
3784 nil out-buffer))))
3785
3786
3787 (setq learned-var-list
3788 (append (list (list 'sh-indent-comment comment-col (point-max)))
3789 learned-var-list))
3790 (setq sh-indent-comment comment-col)
3791 (let ((name (buffer-name)))
3792 (sh-mark-line "\nLearned variable settings:" nil out-buffer)
3793 (if arg
3794 ;; Set learned variables to symbolic rather than numeric
3795 ;; values where possible.
3796 (dolist (learned-var (reverse learned-var-list))
3797 (let ((var (car learned-var))
3798 (val (nth 1 learned-var)))
3799 (when (and (not (eq var 'sh-basic-offset))
3800 (numberp val))
3801 (sh-set-var-value var val)))))
3802 (dolist (learned-var (reverse learned-var-list))
3803 (let ((var (car learned-var)))
3804 (sh-mark-line (format " %s %s" var (symbol-value var))
3805 (nth 2 learned-var) out-buffer)))
3806 (with-current-buffer out-buffer
3807 (goto-char (point-min))
3808 (let ((inhibit-read-only t))
3809 (insert
3810 (format "Indentation values for buffer %s.\n" name)
3811 (format "%d indentation variable%s different values%s\n\n"
3812 num-diffs
3813 (if (= num-diffs 1)
3814 " has" "s have")
3815 (if (zerop num-diffs)
3816 "." ":"))))))
3817 (run-hook-with-args 'sh-learned-buffer-hook learned-var-list)
3818 (and (called-interactively-p 'any)
3819 (or sh-popup-occur-buffer (> num-diffs 0))
3820 (pop-to-buffer out-buffer))))))
3821
3822(defun sh-guess-basic-offset (vec)
3823 "See if we can determine a reasonable value for `sh-basic-offset'.
3824This is experimental, heuristic and arbitrary!
3825Argument VEC is a vector of information collected by
3826`sh-learn-buffer-indent'.
3827Return values:
3828 number - there appears to be a good single value
3829 list of numbers - no obvious one, here is a list of one or more
3830 reasonable choices
3831 nil - we couldn't find a reasonable one."
3832 (let* ((max (1- (length vec)))
3833 (i 1)
3834 (totals (make-vector max 0)))
3835 (while (< i max)
3836 (cl-incf (aref totals i) (* 4 (aref vec i)))
3837 (if (zerop (% i 2))
3838 (cl-incf (aref totals i) (aref vec (/ i 2))))
3839 (if (< (* i 2) max)
3840 (cl-incf (aref totals i) (aref vec (* i 2))))
3841 (setq i (1+ i)))
3842
3843 (let ((x nil)
3844 (result nil)
3845 tot sum p)
3846 (setq i 1)
3847 (while (< i max)
3848 (if (/= (aref totals i) 0)
3849 (push (cons i (aref totals i)) x))
3850 (setq i (1+ i)))
3851
3852 (setq x (sort (nreverse x) (lambda (a b) (> (cdr a) (cdr b)))))
3853 (setq tot (apply '+ (append totals nil)))
3854 (sh-debug (format "vec: %s\ntotals: %s\ntot: %d"
3855 vec totals tot))
3856 (cond
3857 ((zerop (length x))
3858 (message "no values!")) ;; we return nil
3859 ((= (length x) 1)
3860 (message "only value is %d" (car (car x)))
3861 (setq result (car (car x)))) ;; return single value
3862 ((> (cdr (car x)) (/ tot 2))
3863 ;; 1st is > 50%
3864 (message "basic-offset is probably %d" (car (car x)))
3865 (setq result (car (car x)))) ;; again, return a single value
3866 ((>= (cdr (car x)) (* 2 (cdr (car (cdr x)))))
3867 ;; 1st is >= 2 * 2nd
3868 (message "basic-offset could be %d" (car (car x)))
3869 (setq result (car (car x))))
3870 ((>= (+ (cdr (car x))(cdr (car (cdr x)))) (/ tot 2))
3871 ;; 1st & 2nd together >= 50% - return a list
3872 (setq p x sum 0 result nil)
3873 (while (and p
3874 (<= (setq sum (+ sum (cdr (car p)))) (/ tot 2)))
3875 (setq result (append result (list (car (car p)))))
3876 (setq p (cdr p)))
3877 (message "Possible choices for sh-basic-offset: %s"
3878 (mapconcat 'int-to-string result " ")))
3879 (t
3880 (message "No obvious value for sh-basic-offset. Perhaps %d"
3881 (car (car x)))
3882 ;; result is nil here
3883 ))
3884 result)))
3885 2479
3886;; ======================================================================== 2480;; ========================================================================
3887 2481