aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2012-05-11 10:24:50 -0400
committerStefan Monnier2012-05-11 10:24:50 -0400
commitff46c759ddf01935f111660d982ec03d83406d24 (patch)
tree91abf4834c71e8f05a967af59aa3aa3a3a579f79
parentdee6c9a34ff9f8ca764465937b6d2621cbb36318 (diff)
downloademacs-ff46c759ddf01935f111660d982ec03d83406d24.tar.gz
emacs-ff46c759ddf01935f111660d982ec03d83406d24.zip
* lisp/progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode.
Provide SMIE-based indentation (not enabled by default yet). (sh-mode-map): Don't bind electric keys. Use electric-pair-mode instead of skeleton-pair. (sh-assignment-regexp): Fit within 80 columns. (sh-indent-supported): Specify actual shell name instead of boolean. (sh--maybe-here-document): New fun, from sh-maybe-here-document. (sh-maybe-here-document): Use it. Make obsolete. (sh-electric-here-document-mode) New minor mode. (sh-mode): Use it. Don't set sh-indent-supported-here here. (sh-smie-sh-grammar, sh-smie--sh-operators, sh-smie--sh-operators-re) (sh-smie--sh-operators-back-re, sh-indent-after-continuation) (sh-smie-rc-grammar, sh-use-smie): New vars. (sh-smie--keyword-p, sh-smie--newline-semi-p, sh-smie--sh-keyword-p) (sh-smie-sh-forward-token, sh-smie--looking-back-at-continuation-p) (sh-smie-sh-backward-token, sh-smie--continuation-start-indent) (sh-smie-sh-rules, sh-smie-rc-rules, sh-smie--sh-keyword-in-p) (sh-smie--rc-after-special-arg-p, sh-smie-rc-backward-token) (sh-smie-sh-rules, sh-smie--rc-newline-semi-p): New functions. (sh-set-shell): Use smie-setup if requested.
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/ChangeLog21
-rw-r--r--lisp/progmodes/sh-script.el548
3 files changed, 512 insertions, 62 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 9c7cb834b8d..a584d5943ea 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -90,6 +90,11 @@ character when doing minibuffer filename prompts.
90 90
91* Changes in Specialized Modes and Packages in Emacs 24.2 91* Changes in Specialized Modes and Packages in Emacs 24.2
92 92
93** `sh-script'
94*** Pairing of parens/quotes uses electric-pair-mode instead of skeleton-pair.
95*** `sh-electric-here-document-mode' now controls auto-insertion of here-docs.
96*** `sh-use-smie' lets you choose a new indentation and navigation code.
97
93** reStructuredText mode 98** reStructuredText mode
94 99
95*** Major merge with upstream development. 100*** Major merge with upstream development.
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 17d0fcb6427..a70257b2a83 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,26 @@
12012-05-11 Stefan Monnier <monnier@iro.umontreal.ca> 12012-05-11 Stefan Monnier <monnier@iro.umontreal.ca>
2 2
3 * progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode.
4 Provide SMIE-based indentation (not enabled by default yet).
5 (sh-mode-map): Don't bind electric keys.
6 Use electric-pair-mode instead of skeleton-pair.
7 (sh-assignment-regexp): Fit within 80 columns.
8 (sh-indent-supported): Specify actual shell name instead of boolean.
9 (sh--maybe-here-document): New fun, from sh-maybe-here-document.
10 (sh-maybe-here-document): Use it. Make obsolete.
11 (sh-electric-here-document-mode) New minor mode.
12 (sh-mode): Use it. Don't set sh-indent-supported-here here.
13 (sh-smie-sh-grammar, sh-smie--sh-operators, sh-smie--sh-operators-re)
14 (sh-smie--sh-operators-back-re, sh-indent-after-continuation)
15 (sh-smie-rc-grammar, sh-use-smie): New vars.
16 (sh-smie--keyword-p, sh-smie--newline-semi-p, sh-smie--sh-keyword-p)
17 (sh-smie-sh-forward-token, sh-smie--looking-back-at-continuation-p)
18 (sh-smie-sh-backward-token, sh-smie--continuation-start-indent)
19 (sh-smie-sh-rules, sh-smie-rc-rules, sh-smie--sh-keyword-in-p)
20 (sh-smie--rc-after-special-arg-p, sh-smie-rc-backward-token)
21 (sh-smie-sh-rules, sh-smie--rc-newline-semi-p): New functions.
22 (sh-set-shell): Use smie-setup if requested.
23
3 * term.el (term-set-escape-char): Properly set term-escape-char. 24 * term.el (term-set-escape-char): Properly set term-escape-char.
4 See http://stackoverflow.com/questions/10524656. 25 See http://stackoverflow.com/questions/10524656.
5 26
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index f60ce185bc7..a07ecfcb3a4 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -326,7 +326,9 @@ shell it really is."
326 326
327(defcustom sh-imenu-generic-expression 327(defcustom sh-imenu-generic-expression
328 `((sh 328 `((sh
329 . ((nil "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()" 2)))) 329 . ((nil
330 "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()"
331 2))))
330 "Alist of regular expressions for recognizing shell function definitions. 332 "Alist of regular expressions for recognizing shell function definitions.
331See `sh-feature' and `imenu-generic-expression'." 333See `sh-feature' and `imenu-generic-expression'."
332 :type '(alist :key-type (symbol :tag "Shell") 334 :type '(alist :key-type (symbol :tag "Shell")
@@ -460,14 +462,6 @@ This is buffer-local in every such buffer.")
460 (define-key map "\C-c+" 'sh-add) 462 (define-key map "\C-c+" 'sh-add)
461 (define-key map "\C-\M-x" 'sh-execute-region) 463 (define-key map "\C-\M-x" 'sh-execute-region)
462 (define-key map "\C-c\C-x" 'executable-interpret) 464 (define-key map "\C-c\C-x" 'executable-interpret)
463 ;; FIXME: Use post-self-insert-hook.
464 (define-key map "<" 'sh-maybe-here-document)
465 (define-key map "(" 'skeleton-pair-insert-maybe)
466 (define-key map "{" 'skeleton-pair-insert-maybe)
467 (define-key map "[" 'skeleton-pair-insert-maybe)
468 (define-key map "'" 'skeleton-pair-insert-maybe)
469 (define-key map "`" 'skeleton-pair-insert-maybe)
470 (define-key map "\"" 'skeleton-pair-insert-maybe)
471 465
472 (define-key map [remap complete-tag] 'comint-dynamic-complete) 466 (define-key map [remap complete-tag] 'comint-dynamic-complete)
473 (define-key map [remap delete-backward-char] 467 (define-key map [remap delete-backward-char]
@@ -478,10 +472,10 @@ This is buffer-local in every such buffer.")
478 (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map)) 472 (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map))
479 (define-key menu-map [sh-learn-buffer-indent] 473 (define-key menu-map [sh-learn-buffer-indent]
480 '(menu-item "Learn buffer indentation" sh-learn-buffer-indent 474 '(menu-item "Learn buffer indentation" sh-learn-buffer-indent
481 :help "Learn how to indent the buffer the way it currently is.")) 475 :help "Learn how to indent the buffer the way it currently is."))
482 (define-key menu-map [sh-learn-line-indent] 476 (define-key menu-map [sh-learn-line-indent]
483 '(menu-item "Learn line indentation" sh-learn-line-indent 477 '(menu-item "Learn line indentation" sh-learn-line-indent
484 :help "Learn how to indent a line as it currently is indented")) 478 :help "Learn how to indent a line as it currently is indented"))
485 (define-key menu-map [sh-show-indent] 479 (define-key menu-map [sh-show-indent]
486 '(menu-item "Show indentation" sh-show-indent 480 '(menu-item "Show indentation" sh-show-indent
487 :help "Show the how the current line would be indented")) 481 :help "Show the how the current line would be indented"))
@@ -491,13 +485,9 @@ This is buffer-local in every such buffer.")
491 485
492 (define-key menu-map [sh-pair] 486 (define-key menu-map [sh-pair]
493 '(menu-item "Insert braces and quotes in pairs" 487 '(menu-item "Insert braces and quotes in pairs"
494 (lambda () 488 electric-pair-mode
495 (interactive) 489 :button (:toggle . (bound-and-true-p electric-pair-mode))
496 (require 'skeleton) 490 :help "Inserting a brace or quote automatically inserts the matching pair"))
497 (setq skeleton-pair (not skeleton-pair)))
498 :button (:toggle . (and (boundp 'skeleton-pair)
499 skeleton-pair))
500 :help "Inserting a brace or quote automatically inserts the matching pair"))
501 491
502 (define-key menu-map [sh-s0] '("--")) 492 (define-key menu-map [sh-s0] '("--"))
503 ;; Insert 493 ;; Insert
@@ -506,7 +496,7 @@ This is buffer-local in every such buffer.")
506 :help "Insert a function definition")) 496 :help "Insert a function definition"))
507 (define-key menu-map [sh-add] 497 (define-key menu-map [sh-add]
508 '(menu-item "Addition..." sh-add 498 '(menu-item "Addition..." sh-add
509 :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell")) 499 :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell"))
510 (define-key menu-map [sh-until] 500 (define-key menu-map [sh-until]
511 '(menu-item "Until Loop" sh-until 501 '(menu-item "Until Loop" sh-until
512 :help "Insert an until loop")) 502 :help "Insert an until loop"))
@@ -537,16 +527,16 @@ This is buffer-local in every such buffer.")
537 (define-key menu-map [sh-s1] '("--")) 527 (define-key menu-map [sh-s1] '("--"))
538 (define-key menu-map [sh-exec] 528 (define-key menu-map [sh-exec]
539 '(menu-item "Execute region" sh-execute-region 529 '(menu-item "Execute region" sh-execute-region
540 :help "Pass optional header and region to a subshell for noninteractive execution")) 530 :help "Pass optional header and region to a subshell for noninteractive execution"))
541 (define-key menu-map [sh-exec-interpret] 531 (define-key menu-map [sh-exec-interpret]
542 '(menu-item "Execute script..." executable-interpret 532 '(menu-item "Execute script..." executable-interpret
543 :help "Run script with user-specified args, and collect output in a buffer")) 533 :help "Run script with user-specified args, and collect output in a buffer"))
544 (define-key menu-map [sh-set-shell] 534 (define-key menu-map [sh-set-shell]
545 '(menu-item "Set shell type..." sh-set-shell 535 '(menu-item "Set shell type..." sh-set-shell
546 :help "Set this buffer's shell to SHELL (a string)")) 536 :help "Set this buffer's shell to SHELL (a string)"))
547 (define-key menu-map [sh-backslash-region] 537 (define-key menu-map [sh-backslash-region]
548 '(menu-item "Backslash region" sh-backslash-region 538 '(menu-item "Backslash region" sh-backslash-region
549 :help "Insert, align, or delete end-of-line backslashes on the lines in the region.")) 539 :help "Insert, align, or delete end-of-line backslashes on the lines in the region."))
550 map) 540 map)
551 "Keymap used in Shell-Script mode.") 541 "Keymap used in Shell-Script mode.")
552 542
@@ -564,9 +554,10 @@ This is buffer-local in every such buffer.")
564 :group 'sh-script) 554 :group 'sh-script)
565 555
566(defcustom sh-assignment-regexp 556(defcustom sh-assignment-regexp
567 '((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=") 557 `((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
568 ;; actually spaces are only supported in let/(( ... )) 558 ;; actually spaces are only supported in let/(( ... ))
569 (ksh88 . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=") 559 (ksh88 . ,(concat "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?"
560 "[ \t]*\\(?:[-+*/%&|~^]\\|<<\\|>>\\)?="))
570 (bash . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?\\+?=") 561 (bash . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?\\+?=")
571 (rc . "\\<\\([[:alnum:]_*]+\\)[ \t]*=") 562 (rc . "\\<\\([[:alnum:]_*]+\\)[ \t]*=")
572 (sh . "\\<\\([[:alnum:]_]+\\)=")) 563 (sh . "\\<\\([[:alnum:]_]+\\)="))
@@ -1379,10 +1370,10 @@ punctuation characters like '-'."
1379 1370
1380 1371
1381(defconst sh-indent-supported 1372(defconst sh-indent-supported
1382 '((sh . t) 1373 '((sh . sh)
1383 (csh . nil) 1374 (csh . nil)
1384 (rc . t)) 1375 (rc . rc))
1385 "Shell types that shell indenting can do something with.") 1376 "Indentation rule set to use for each shell type.")
1386 1377
1387(defvar sh-indent-supported-here nil 1378(defvar sh-indent-supported-here nil
1388 "Non-nil if we support indentation for the current buffer's shell type.") 1379 "Non-nil if we support indentation for the current buffer's shell type.")
@@ -1464,9 +1455,8 @@ buffer indents as it currently is indented.
1464\\[sh-set-shell] Set this buffer's shell, and maybe its magic number. 1455\\[sh-set-shell] Set this buffer's shell, and maybe its magic number.
1465\\[sh-execute-region] Have optional header and region be executed in a subshell. 1456\\[sh-execute-region] Have optional header and region be executed in a subshell.
1466 1457
1467\\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document. 1458`sh-electric-here-document-mode' controls whether insertion of two
1468\{, (, [, ', \", ` 1459unquoted < insert a here document.
1469 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
1470 1460
1471If you generally program a shell different from your login shell you can 1461If you generally program a shell different from your login shell you can
1472set `sh-shell-file' accordingly. If your shell's file name doesn't correctly 1462set `sh-shell-file' accordingly. If your shell's file name doesn't correctly
@@ -1503,13 +1493,13 @@ with your script for an edit-interpret-debug cycle."
1503 #'sh-syntax-propertize-function) 1493 #'sh-syntax-propertize-function)
1504 (add-hook 'syntax-propertize-extend-region-functions 1494 (add-hook 'syntax-propertize-extend-region-functions
1505 #'syntax-propertize-multiline 'append 'local) 1495 #'syntax-propertize-multiline 'append 'local)
1496 (sh-electric-here-document-mode 1)
1506 (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`))) 1497 (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`)))
1507 (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p) 1498 (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p)
1508 (set (make-local-variable 'skeleton-further-elements) 1499 (set (make-local-variable 'skeleton-further-elements)
1509 '((< '(- (min sh-indentation (current-column)))))) 1500 '((< '(- (min sh-indentation (current-column))))))
1510 (set (make-local-variable 'skeleton-filter-function) 'sh-feature) 1501 (set (make-local-variable 'skeleton-filter-function) 'sh-feature)
1511 (set (make-local-variable 'skeleton-newline-indent-rigidly) t) 1502 (set (make-local-variable 'skeleton-newline-indent-rigidly) t)
1512 (set (make-local-variable 'sh-indent-supported-here) nil)
1513 (set (make-local-variable 'defun-prompt-regexp) 1503 (set (make-local-variable 'defun-prompt-regexp)
1514 (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)")) 1504 (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
1515 ;; Parse or insert magic number for exec, and set all variables depending 1505 ;; Parse or insert magic number for exec, and set all variables depending
@@ -1519,23 +1509,15 @@ with your script for an edit-interpret-debug cycle."
1519 (goto-char (point-min)) 1509 (goto-char (point-min))
1520 (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")) 1510 (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)"))
1521 (match-string 2)) 1511 (match-string 2))
1522 ((not buffer-file-name) 1512 ((not buffer-file-name) sh-shell-file)
1523 sh-shell-file)
1524 ;; Checks that use `buffer-file-name' follow. 1513 ;; Checks that use `buffer-file-name' follow.
1525 ((string-match "\\.m?spec\\'" buffer-file-name) 1514 ((string-match "\\.m?spec\\'" buffer-file-name) "rpm")
1526 "rpm") 1515 ((string-match "[.]sh\\>" buffer-file-name) "sh")
1527 ((string-match "[.]sh\\>" buffer-file-name) 1516 ((string-match "[.]bash\\>" buffer-file-name) "bash")
1528 "sh") 1517 ((string-match "[.]ksh\\>" buffer-file-name) "ksh")
1529 ((string-match "[.]bash\\>" buffer-file-name) 1518 ((string-match "[.]csh\\>" buffer-file-name) "csh")
1530 "bash") 1519 ((equal (file-name-nondirectory buffer-file-name) ".profile") "sh")
1531 ((string-match "[.]ksh\\>" buffer-file-name) 1520 (t sh-shell-file))
1532 "ksh")
1533 ((string-match "[.]csh\\>" buffer-file-name)
1534 "csh")
1535 ((equal (file-name-nondirectory buffer-file-name) ".profile")
1536 "sh")
1537 (t
1538 sh-shell-file))
1539 nil nil)) 1521 nil nil))
1540 1522
1541;;;###autoload 1523;;;###autoload
@@ -1578,6 +1560,426 @@ This adds rules for comments and assignments."
1578 "Function to get better fontification including keywords and builtins." 1560 "Function to get better fontification including keywords and builtins."
1579 (sh-font-lock-keywords-1 t)) 1561 (sh-font-lock-keywords-1 t))
1580 1562
1563;;; Indentation and navigation with SMIE.
1564
1565(require 'smie)
1566
1567;; The SMIE code should generally be preferred, but it currently does not obey
1568;; the various indentation custom-vars, and it misses some important features
1569;; of the old code, mostly: sh-learn-line/buffer-indent, sh-show-indent,
1570;; sh-name/save/load-style.
1571(defvar sh-use-smie nil
1572 "Whether to use the SMIE code for navigation and indentation.")
1573
1574(defun sh-smie--keyword-p (tok)
1575 "Non-nil if TOK (at which we're looking) really is a keyword."
1576 (let ((prev (funcall smie-backward-token-function)))
1577 (if (zerop (length prev))
1578 (looking-back "\\s(" (1- (point)))
1579 (assoc prev smie-grammar))))
1580
1581(defun sh-smie--newline-semi-p (&optional tok)
1582 "Return non-nil if a newline should be treated as a semi-colon.
1583Here we assume that a newline should be treated as a semi-colon unless it
1584comes right after a special keyword.
1585This function does not pay attention to line-continuations.
1586If TOK is nil, point should be before the newline; otherwise, TOK is the token
1587before the newline and in that case point should be just before the token."
1588 (save-excursion
1589 (unless tok
1590 (setq tok (funcall smie-backward-token-function)))
1591 (if (and (zerop (length tok))
1592 (looking-back "\\s(" (1- (point))))
1593 nil
1594 (not (numberp (nth 2 (assoc tok smie-grammar)))))))
1595
1596;;;; SMIE support for `sh'.
1597
1598(defconst sh-smie-sh-grammar
1599 (smie-prec2->grammar
1600 (smie-bnf->prec2
1601 '((exp) ;A constant, or a $var, or a sequence of them...
1602 (cmd ("case" exp "in" branches "esac")
1603 ("if" cmd "then" cmd "fi")
1604 ("if" cmd "then" cmd "else" cmd "fi")
1605 ("if" cmd "then" cmd "elif" cmd "then" cmd "fi")
1606 ("if" cmd "then" cmd "elif" cmd "then" cmd "else" cmd "fi")
1607 ("if" cmd "then" cmd "elif" cmd "then" cmd
1608 "elif" cmd "then" cmd "else" cmd "fi")
1609 ("while" cmd "do" cmd "done")
1610 ("until" cmd "do" cmd "done")
1611 ("for" exp "in" cmd "do" cmd "done")
1612 ("for" exp "do" cmd "done")
1613 ("select" exp "in" cmd "do" cmd "done") ;bash&zsh&ksh88.
1614 ("repeat" exp "do" cmd "done") ;zsh.
1615 (exp "always" exp) ;zsh.
1616 (cmd "|" cmd) (cmd "|&" cmd)
1617 (cmd "&&" cmd) (cmd "||" cmd)
1618 (cmd ";" cmd) (cmd "&" cmd))
1619 (pattern (pattern "|" pattern))
1620 (branches (branches ";;" branches)
1621 (branches ";&" branches) (branches ";;&" branches) ;bash.
1622 (pattern "case-)" cmd)))
1623 '((assoc ";;" ";&" ";;&"))
1624 '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
1625
1626(defconst sh-smie--sh-operators
1627 (delq nil (mapcar (lambda (x)
1628 (setq x (car x))
1629 (and (stringp x)
1630 (not (string-match "\\`[a-z]" x))
1631 x))
1632 sh-smie-sh-grammar)))
1633
1634(defconst sh-smie--sh-operators-re (regexp-opt sh-smie--sh-operators))
1635(defconst sh-smie--sh-operators-back-re
1636 (concat "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*"
1637 "\\(" sh-smie--sh-operators-re "\\)"))
1638
1639(defun sh-smie--sh-keyword-in-p ()
1640 "Assuming we're looking at \"in\", return non-nil if it's a keyword.
1641Does not preserve point."
1642 (let ((forward-sexp-function nil)
1643 (words nil) ;We've seen words.
1644 (newline nil) ;We've seen newlines after the words.
1645 (res nil)
1646 prev)
1647 (while (not res)
1648 (setq prev (funcall smie-backward-token-function))
1649 (cond
1650 ((zerop (length prev))
1651 (if newline
1652 (progn (assert words) (setq res 'word))
1653 (setq words t)
1654 (condition-case nil
1655 (forward-sexp -1)
1656 (scan-error (setq res 'unknown)))))
1657 ((equal prev ";")
1658 (if words (setq newline t)
1659 (setq res 'keyword)))
1660 ((member prev '("case" "for" "select")) (setq res 'keyword))
1661 ((assoc prev smie-grammar) (setq res 'word))
1662 (t
1663 (if newline
1664 (progn (assert words) (setq res 'word))
1665 (setq words t)))))
1666 (eq res 'keyword)))
1667
1668(defun sh-smie--sh-keyword-p (tok)
1669 "Non-nil if TOK (at which we're looking) really is a keyword."
1670 (if (equal tok "in")
1671 (sh-smie--sh-keyword-in-p)
1672 (sh-smie--keyword-p tok)))
1673
1674(defun sh-smie-sh-forward-token ()
1675 (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
1676 (save-excursion
1677 (skip-chars-backward " \t")
1678 (not (bolp))))
1679 (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
1680 ;; Right before a here-doc.
1681 (let ((forward-sexp-function nil))
1682 (forward-sexp 1)
1683 ;; Pretend the here-document is a "newline representing a
1684 ;; semi-colon", since the here-doc otherwise covers the newline(s).
1685 ";")
1686 (let ((semi (sh-smie--newline-semi-p)))
1687 (forward-line 1)
1688 (if semi ";"
1689 (sh-smie-sh-forward-token))))
1690 (forward-comment (point-max))
1691 (cond
1692 ((looking-at "\\\\\n") (forward-line 1) (sh-smie-sh-forward-token))
1693 ((looking-at sh-smie--sh-operators-re)
1694 (goto-char (match-end 0))
1695 (let ((tok (match-string-no-properties 0)))
1696 (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
1697 (looking-at "[ \t]*\\(?:#\\|$\\)"))
1698 (forward-line 1))
1699 tok))
1700 (t
1701 (let* ((pos (point))
1702 (tok (smie-default-forward-token)))
1703 (cond
1704 ((equal tok ")") "case-)")
1705 ((and tok (string-match "\\`[a-z]" tok)
1706 (assoc tok smie-grammar)
1707 (not
1708 (save-excursion
1709 (goto-char pos)
1710 (sh-smie--sh-keyword-p tok))))
1711 " word ")
1712 (t tok)))))))
1713
1714(defun sh-smie--looking-back-at-continuation-p ()
1715 (save-excursion
1716 (and (if (eq (char-before) ?\n) (progn (forward-char -1) t) (eolp))
1717 (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
1718 (line-beginning-position)))))
1719
1720(defun sh-smie-sh-backward-token ()
1721 (let ((bol (line-beginning-position))
1722 pos tok)
1723 (forward-comment (- (point)))
1724 (cond
1725 ((and (bolp) (not (bobp))
1726 (equal (syntax-after (1- (point))) (string-to-syntax "|"))
1727 (not (nth 3 (syntax-ppss))))
1728 ;; Right after a here-document.
1729 (let ((forward-sexp-function nil))
1730 (forward-sexp -1)
1731 ;; Pretend the here-document is a "newline representing a
1732 ;; semi-colon", since the here-doc otherwise covers the newline(s).
1733 ";"))
1734 ((< (point) bol)
1735 (cond
1736 ((sh-smie--looking-back-at-continuation-p)
1737 (forward-char -1)
1738 (funcall smie-backward-token-function))
1739 ((sh-smie--newline-semi-p) ";")
1740 (t (funcall smie-backward-token-function))))
1741 ((looking-back sh-smie--sh-operators-back-re
1742 (line-beginning-position) 'greedy)
1743 (goto-char (match-beginning 1))
1744 (match-string-no-properties 1))
1745 (t
1746 (let ((tok (smie-default-backward-token)))
1747 (cond
1748 ((equal tok ")") "case-)")
1749 ((and tok (string-match "\\`[a-z]" tok)
1750 (assoc tok smie-grammar)
1751 (not (save-excursion (sh-smie--sh-keyword-p tok))))
1752 " word ")
1753 (t tok)))))))
1754
1755(defcustom sh-indent-after-continuation t
1756 "If non-nil, try to make sure text is indented after a line continuation."
1757 :type 'boolean)
1758
1759(defun sh-smie--continuation-start-indent ()
1760 "Return the initial indentation of a continued line.
1761May return nil if the line should not be treated as continued."
1762 (save-excursion
1763 (forward-line -1)
1764 (unless (sh-smie--looking-back-at-continuation-p)
1765 (current-indentation))))
1766
1767(defun sh-smie-sh-rules (kind token)
1768 (pcase (cons kind token)
1769 (`(:elem . basic) sh-indentation)
1770 (`(:after . "case-)") (or sh-indentation smie-indent-basic))
1771 ((and `(:before . ,_)
1772 (guard (when sh-indent-after-continuation
1773 (save-excursion
1774 (ignore-errors
1775 (skip-chars-backward " \t")
1776 (sh-smie--looking-back-at-continuation-p))))))
1777 ;; After a line-continuation, make sure the rest is indented.
1778 (let* ((sh-indent-after-continuation nil)
1779 (indent (smie-indent-calculate))
1780 (initial (sh-smie--continuation-start-indent)))
1781 (when (and (numberp indent) (numberp initial)
1782 (<= indent initial))
1783 `(column . ,(+ initial sh-indentation)))))
1784 (`(:before . ,(or `"(" `"{" `"["))
1785 (if (smie-rule-hanging-p) (smie-rule-parent)))
1786 ;; FIXME: Maybe this handling of ;; should be made into
1787 ;; a smie-rule-terminator function that takes the substitute ";" as arg.
1788 (`(:before . ,(or `";;" `";&" `";;&"))
1789 (if (and (smie-rule-bolp) (looking-at ";;?&?[ \t]*\\(#\\|$\\)"))
1790 (cons 'column (smie-indent-keyword ";"))
1791 (smie-rule-separator kind)))
1792 (`(:after . ,(or `";;" `";&" `";;&"))
1793 (with-demoted-errors
1794 (smie-backward-sexp token)
1795 (cons 'column
1796 (if (or (smie-rule-bolp)
1797 (save-excursion
1798 (and (member (funcall smie-backward-token-function)
1799 '("in" ";;"))
1800 (smie-rule-bolp))))
1801 (current-column)
1802 (smie-indent-calculate)))))
1803 (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
1804 ))
1805
1806;; (defconst sh-smie-csh-grammar
1807;; (smie-prec2->grammar
1808;; (smie-bnf->prec2
1809;; '((exp) ;A constant, or a $var, or a sequence of them…
1810;; (elseifcmd (cmd)
1811;; (cmd "else" "else-if" exp "then" elseifcmd))
1812;; (cmd ("switch" branches "endsw")
1813;; ("if" exp)
1814;; ("if" exp "then" cmd "endif")
1815;; ("if" exp "then" cmd "else" cmd "endif")
1816;; ("if" exp "then" elseifcmd "endif")
1817;; ;; ("if" exp "then" cmd "else" cmd "endif")
1818;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd "endif")
1819;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
1820;; ;; "else" cmd "endif")
1821;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
1822;; ;; "else" "if" exp "then" cmd "endif")
1823;; ("while" cmd "end")
1824;; ("foreach" cmd "end")
1825;; (cmd "|" cmd) (cmd "|&" cmd)
1826;; (cmd "&&" cmd) (cmd "||" cmd)
1827;; (cmd ";" cmd) (cmd "&" cmd))
1828;; ;; This is a lie, but (combined with the corresponding disambiguation
1829;; ;; rule) it makes it more clear that `case' and `default' are the key
1830;; ;; separators and the `:' is a secondary tokens.
1831;; (branches (branches "case" branches)
1832;; (branches "default" branches)
1833;; (exp ":" branches)))
1834;; '((assoc "else" "then" "endif"))
1835;; '((assoc "case" "default") (nonassoc ":"))
1836;; '((assoc ";;" ";&" ";;&"))
1837;; '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
1838
1839;;;; SMIE support for `rc'.
1840
1841(defconst sh-smie-rc-grammar
1842 (smie-prec2->grammar
1843 (smie-bnf->prec2
1844 '((exp) ;A constant, or a $var, or a sequence of them...
1845 (cmd (cmd "case" cmd)
1846 ("if" exp)
1847 ("switch" exp)
1848 ("for" exp) ("while" exp)
1849 (cmd "|" cmd) (cmd "|&" cmd)
1850 (cmd "&&" cmd) (cmd "||" cmd)
1851 (cmd ";" cmd) (cmd "&" cmd))
1852 (pattern (pattern "|" pattern))
1853 (branches (branches ";;" branches)
1854 (branches ";&" branches) (branches ";;&" branches) ;bash.
1855 (pattern "case-)" cmd)))
1856 '((assoc ";;" ";&" ";;&"))
1857 '((assoc "case") (assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
1858
1859(defun sh-smie--rc-after-special-arg-p ()
1860 "Check if we're after the first arg of an if/while/for/... construct.
1861Returns the construct's token and moves point before it, if so."
1862 (forward-comment (- (point)))
1863 (when (looking-back ")\\|\\_<not" (- (point) 3))
1864 (ignore-errors
1865 (let ((forward-sexp-function nil))
1866 (forward-sexp -1)
1867 (car (member (funcall smie-backward-token-function)
1868 '("if" "for" "switch" "while")))))))
1869
1870(defun sh-smie--rc-newline-semi-p ()
1871 "Return non-nil if a newline should be treated as a semi-colon.
1872Point should be before the newline."
1873 (save-excursion
1874 (let ((tok (funcall smie-backward-token-function)))
1875 (if (or (when (equal tok "not") (forward-word 1) t)
1876 (and (zerop (length tok)) (eq (char-before) ?\))))
1877 (not (sh-smie--rc-after-special-arg-p))
1878 (sh-smie--newline-semi-p tok)))))
1879
1880(defun sh-smie-rc-forward-token ()
1881 ;; FIXME: Code duplication with sh-smie-sh-forward-token.
1882 (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
1883 (save-excursion
1884 (skip-chars-backward " \t")
1885 (not (bolp))))
1886 (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
1887 ;; Right before a here-doc.
1888 (let ((forward-sexp-function nil))
1889 (forward-sexp 1)
1890 ;; Pretend the here-document is a "newline representing a
1891 ;; semi-colon", since the here-doc otherwise covers the newline(s).
1892 ";")
1893 (let ((semi (sh-smie--rc-newline-semi-p)))
1894 (forward-line 1)
1895 (if semi ";"
1896 (sh-smie-rc-forward-token))))
1897 (forward-comment (point-max))
1898 (cond
1899 ((looking-at "\\\\\n") (forward-line 1) (sh-smie-rc-forward-token))
1900 ;; ((looking-at sh-smie--rc-operators-re)
1901 ;; (goto-char (match-end 0))
1902 ;; (let ((tok (match-string-no-properties 0)))
1903 ;; (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
1904 ;; (looking-at "[ \t]*\\(?:#\\|$\\)"))
1905 ;; (forward-line 1))
1906 ;; tok))
1907 (t
1908 (let* ((pos (point))
1909 (tok (smie-default-forward-token)))
1910 (cond
1911 ;; ((equal tok ")") "case-)")
1912 ((and tok (string-match "\\`[a-z]" tok)
1913 (assoc tok smie-grammar)
1914 (not
1915 (save-excursion
1916 (goto-char pos)
1917 (sh-smie--keyword-p tok))))
1918 " word ")
1919 (t tok)))))))
1920
1921(defun sh-smie-rc-backward-token ()
1922 ;; FIXME: Code duplication with sh-smie-sh-backward-token.
1923 (let ((bol (line-beginning-position))
1924 pos tok)
1925 (forward-comment (- (point)))
1926 (cond
1927 ((and (bolp) (not (bobp))
1928 (equal (syntax-after (1- (point))) (string-to-syntax "|"))
1929 (not (nth 3 (syntax-ppss))))
1930 ;; Right after a here-document.
1931 (let ((forward-sexp-function nil))
1932 (forward-sexp -1)
1933 ;; Pretend the here-document is a "newline representing a
1934 ;; semi-colon", since the here-doc otherwise covers the newline(s).
1935 ";"))
1936 ((< (point) bol) ;We skipped over a newline.
1937 (cond
1938 ;; A continued line.
1939 ((and (eolp)
1940 (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
1941 (line-beginning-position)))
1942 (forward-char -1)
1943 (funcall smie-backward-token-function))
1944 ((sh-smie--rc-newline-semi-p) ";")
1945 (t (funcall smie-backward-token-function))))
1946 ;; ((looking-back sh-smie--sh-operators-back-re
1947 ;; (line-beginning-position) 'greedy)
1948 ;; (goto-char (match-beginning 1))
1949 ;; (match-string-no-properties 1))
1950 (t
1951 (let ((tok (smie-default-backward-token)))
1952 (cond
1953 ;; ((equal tok ")") "case-)")
1954 ((and tok (string-match "\\`[a-z]" tok)
1955 (assoc tok smie-grammar)
1956 (not (save-excursion (sh-smie--keyword-p tok))))
1957 " word ")
1958 (t tok)))))))
1959
1960(defun sh-smie-rc-rules (kind token)
1961 (pcase (cons kind token)
1962 (`(:elem . basic) sh-indentation)
1963 ;; (`(:after . "case") (or sh-indentation smie-indent-basic))
1964 (`(:after . ";") (if (smie-rule-parent-p "case")
1965 (smie-rule-parent sh-indentation)))
1966 (`(:before . "{")
1967 (save-excursion
1968 (when (sh-smie--rc-after-special-arg-p)
1969 `(column . ,(current-column)))))
1970 (`(:before . ,(or `"(" `"{" `"["))
1971 (if (smie-rule-hanging-p) (smie-rule-parent)))
1972 ;; FIXME: SMIE parses "if (exp) cmd" as "(if ((exp) cmd))" so "cmd" is
1973 ;; treated as an arg to (exp) by default, which indents it all wrong.
1974 ;; To handle it right, we should extend smie-indent-exps so that the
1975 ;; preceding keyword can give special rules. Currently the only special
1976 ;; rule we have is the :list-intro hack, which we use here to align "cmd"
1977 ;; with "(exp)", which is rarely the right thing to do, but is better
1978 ;; than nothing.
1979 (`(:list-intro . ,(or `"for" `"if" `"while")) t)
1980 ))
1981
1982;;; End of SMIE code.
1581 1983
1582(defvar sh-regexp-for-done nil 1984(defvar sh-regexp-for-done nil
1583 "A buffer-local regexp to match opening keyword for done.") 1985 "A buffer-local regexp to match opening keyword for done.")
@@ -1677,19 +2079,28 @@ Calls the value of `sh-set-shell-hook' if set."
1677 (set-syntax-table sh-mode-syntax-table))) 2079 (set-syntax-table sh-mode-syntax-table)))
1678 (dolist (var (sh-feature sh-variables)) 2080 (dolist (var (sh-feature sh-variables))
1679 (sh-remember-variable var)) 2081 (sh-remember-variable var))
1680 (if (setq sh-indent-supported-here (sh-feature sh-indent-supported)) 2082 (if (set (make-local-variable 'sh-indent-supported-here)
2083 (sh-feature sh-indent-supported))
1681 (progn 2084 (progn
1682 (message "Setting up indent for shell type %s" sh-shell) 2085 (message "Setting up indent for shell type %s" sh-shell)
1683 (set (make-local-variable 'parse-sexp-lookup-properties) t) 2086 (if sh-use-smie
1684 (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw)) 2087 (let ((mksym (lambda (name)
1685 (let ((regexp (sh-feature sh-kws-for-done))) 2088 (intern (format "sh-smie-%s-%s"
1686 (if regexp 2089 sh-indent-supported-here name)))))
1687 (set (make-local-variable 'sh-regexp-for-done) 2090 (smie-setup (symbol-value (funcall mksym "grammar"))
1688 (sh-mkword-regexpr (regexp-opt regexp t))))) 2091 (funcall mksym "rules")
1689 (message "setting up indent stuff") 2092 :forward-token (funcall mksym "forward-token")
1690 ;; sh-mode has already made indent-line-function local 2093 :backward-token (funcall mksym "backward-token")))
1691 ;; but do it in case this is called before that. 2094 (set (make-local-variable 'parse-sexp-lookup-properties) t)
1692 (set (make-local-variable 'indent-line-function) 'sh-indent-line) 2095 (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
2096 (let ((regexp (sh-feature sh-kws-for-done)))
2097 (if regexp
2098 (set (make-local-variable 'sh-regexp-for-done)
2099 (sh-mkword-regexpr (regexp-opt regexp t)))))
2100 (message "setting up indent stuff")
2101 ;; sh-mode has already made indent-line-function local
2102 ;; but do it in case this is called before that.
2103 (set (make-local-variable 'indent-line-function) 'sh-indent-line))
1693 (if sh-make-vars-local 2104 (if sh-make-vars-local
1694 (sh-make-vars-local)) 2105 (sh-make-vars-local))
1695 (message "Indentation setup for shell type %s" sh-shell)) 2106 (message "Indentation setup for shell type %s" sh-shell))
@@ -3237,8 +3648,9 @@ overwritten if
3237(defun sh-save-styles-to-buffer (buff) 3648(defun sh-save-styles-to-buffer (buff)
3238 "Save all current styles in elisp to buffer BUFF. 3649 "Save all current styles in elisp to buffer BUFF.
3239This is always added to the end of the buffer." 3650This is always added to the end of the buffer."
3240 (interactive (list 3651 (interactive
3241 (read-from-minibuffer "Buffer to save styles in? " "*scratch*"))) 3652 (list
3653 (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
3242 (with-current-buffer (get-buffer-create buff) 3654 (with-current-buffer (get-buffer-create buff)
3243 (goto-char (point-max)) 3655 (goto-char (point-max))
3244 (insert "\n") 3656 (insert "\n")
@@ -3656,8 +4068,12 @@ option followed by a colon `:' if the option accepts an argument."
3656The document is bounded by `sh-here-document-word'." 4068The document is bounded by `sh-here-document-word'."
3657 (interactive "*P") 4069 (interactive "*P")
3658 (self-insert-command (prefix-numeric-value arg)) 4070 (self-insert-command (prefix-numeric-value arg))
3659 (or arg 4071 (or arg (sh--maybe-here-document)))
3660 (not (looking-back "[^<]<<")) 4072(make-obsolete 'sh--maybe-here-document
4073 'sh-electric-here-document-mode "24.2")
4074
4075(defun sh--maybe-here-document ()
4076 (or (not (looking-back "[^<]<<"))
3661 (save-excursion 4077 (save-excursion
3662 (backward-char 2) 4078 (backward-char 2)
3663 (sh-quoted-p)) 4079 (sh-quoted-p))
@@ -3678,6 +4094,12 @@ The document is bounded by `sh-here-document-word'."
3678 (insert ?\n tabs (replace-regexp-in-string 4094 (insert ?\n tabs (replace-regexp-in-string
3679 "\\`-?[ \t]*" "" delim)))))) 4095 "\\`-?[ \t]*" "" delim))))))
3680 4096
4097(define-minor-mode sh-electric-here-document-mode
4098 "Make << insert a here document skeleton."
4099 nil nil nil
4100 (if sh-electric-here-document-mode
4101 (add-hook 'post-self-insert-hook #'sh--maybe-here-document nil t)
4102 (remove-hook 'post-self-insert-hook #'sh--maybe-here-document t)))
3681 4103
3682;; various other commands 4104;; various other commands
3683 4105
@@ -3696,12 +4118,14 @@ The document is bounded by `sh-here-document-word'."
3696 4118
3697 4119
3698(defun sh-beginning-of-command () 4120(defun sh-beginning-of-command ()
4121 ;; FIXME: Redefine using SMIE.
3699 "Move point to successive beginnings of commands." 4122 "Move point to successive beginnings of commands."
3700 (interactive) 4123 (interactive)
3701 (if (re-search-backward sh-beginning-of-command nil t) 4124 (if (re-search-backward sh-beginning-of-command nil t)
3702 (goto-char (match-beginning 2)))) 4125 (goto-char (match-beginning 2))))
3703 4126
3704(defun sh-end-of-command () 4127(defun sh-end-of-command ()
4128 ;; FIXME: Redefine using SMIE.
3705 "Move point to successive ends of commands." 4129 "Move point to successive ends of commands."
3706 (interactive) 4130 (interactive)
3707 (if (re-search-forward sh-end-of-command nil t) 4131 (if (re-search-forward sh-end-of-command nil t)