emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] /srv/bzr/emacs/emacs-24 r108195: * lisp/progmodes/sh-scrip


From: Stefan Monnier
Subject: [Emacs-diffs] /srv/bzr/emacs/emacs-24 r108195: * lisp/progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode.
Date: Fri, 02 Nov 2012 02:00:42 -0000
User-agent: Bazaar (2.5.0)

------------------------------------------------------------
revno: 108195
committer: Stefan Monnier <address@hidden>
branch nick: trunk
timestamp: Fri 2012-05-11 10:24:50 -0400
message:
  * 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.
modified:
  etc/NEWS
  lisp/ChangeLog
  lisp/progmodes/sh-script.el
=== modified file 'etc/NEWS'
--- a/etc/NEWS  2012-05-10 06:27:12 +0000
+++ b/etc/NEWS  2012-05-11 14:24:50 +0000
@@ -90,6 +90,11 @@
 
 * Changes in Specialized Modes and Packages in Emacs 24.2
 
+** `sh-script'
+*** Pairing of parens/quotes uses electric-pair-mode instead of skeleton-pair.
+*** `sh-electric-here-document-mode' now controls auto-insertion of here-docs.
+*** `sh-use-smie' lets you choose a new indentation and navigation code.
+
 ** reStructuredText mode
 
 *** Major merge with upstream development.

=== modified file 'lisp/ChangeLog'
--- a/lisp/ChangeLog    2012-05-11 13:13:09 +0000
+++ b/lisp/ChangeLog    2012-05-11 14:24:50 +0000
@@ -1,5 +1,26 @@
 2012-05-11  Stefan Monnier  <address@hidden>
 
+       * 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.
+
        * term.el (term-set-escape-char): Properly set term-escape-char.
        See http://stackoverflow.com/questions/10524656.
 

=== modified file 'lisp/progmodes/sh-script.el'
--- a/lisp/progmodes/sh-script.el       2012-04-09 13:05:48 +0000
+++ b/lisp/progmodes/sh-script.el       2012-05-11 14:24:50 +0000
@@ -326,7 +326,9 @@
 
 (defcustom sh-imenu-generic-expression
   `((sh
-     . ((nil 
"^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()" 2))))
+     . ((nil
+         "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()"
+         2))))
   "Alist of regular expressions for recognizing shell function definitions.
 See `sh-feature' and `imenu-generic-expression'."
   :type '(alist :key-type (symbol :tag "Shell")
@@ -460,14 +462,6 @@
     (define-key map "\C-c+" 'sh-add)
     (define-key map "\C-\M-x" 'sh-execute-region)
     (define-key map "\C-c\C-x" 'executable-interpret)
-    ;; FIXME: Use post-self-insert-hook.
-    (define-key map "<" 'sh-maybe-here-document)
-    (define-key map "(" 'skeleton-pair-insert-maybe)
-    (define-key map "{" 'skeleton-pair-insert-maybe)
-    (define-key map "[" 'skeleton-pair-insert-maybe)
-    (define-key map "'" 'skeleton-pair-insert-maybe)
-    (define-key map "`" 'skeleton-pair-insert-maybe)
-    (define-key map "\"" 'skeleton-pair-insert-maybe)
 
     (define-key map [remap complete-tag] 'comint-dynamic-complete)
     (define-key map [remap delete-backward-char]
@@ -478,10 +472,10 @@
     (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map))
     (define-key menu-map [sh-learn-buffer-indent]
       '(menu-item "Learn buffer indentation" sh-learn-buffer-indent
-                 :help "Learn how to indent the buffer the way it currently 
is."))
+        :help "Learn how to indent the buffer the way it currently is."))
     (define-key menu-map [sh-learn-line-indent]
       '(menu-item "Learn line indentation" sh-learn-line-indent
-                 :help "Learn how to indent a line as it currently is 
indented"))
+        :help "Learn how to indent a line as it currently is indented"))
     (define-key menu-map [sh-show-indent]
       '(menu-item "Show indentation" sh-show-indent
                  :help "Show the how the current line would be indented"))
@@ -491,13 +485,9 @@
 
     (define-key menu-map [sh-pair]
       '(menu-item "Insert braces and quotes in pairs"
-                 (lambda ()
-                   (interactive)
-                   (require 'skeleton)
-                   (setq skeleton-pair (not skeleton-pair)))
-                 :button (:toggle . (and (boundp 'skeleton-pair)
-                                         skeleton-pair))
-                 :help "Inserting a brace or quote automatically inserts the 
matching pair"))
+        electric-pair-mode
+        :button (:toggle . (bound-and-true-p electric-pair-mode))
+        :help "Inserting a brace or quote automatically inserts the matching 
pair"))
 
     (define-key menu-map [sh-s0] '("--"))
     ;; Insert
@@ -506,7 +496,7 @@
                  :help "Insert a function definition"))
     (define-key menu-map [sh-add]
       '(menu-item "Addition..." sh-add
-                 :help "Insert an addition of VAR and prefix DELTA for Bourne 
(type) shell"))
+        :help "Insert an addition of VAR and prefix DELTA for Bourne (type) 
shell"))
     (define-key menu-map [sh-until]
       '(menu-item "Until Loop" sh-until
                  :help "Insert an until loop"))
@@ -537,16 +527,16 @@
     (define-key menu-map [sh-s1] '("--"))
     (define-key menu-map [sh-exec]
       '(menu-item "Execute region" sh-execute-region
-                 :help "Pass optional header and region to a subshell for 
noninteractive execution"))
+        :help "Pass optional header and region to a subshell for 
noninteractive execution"))
     (define-key menu-map [sh-exec-interpret]
       '(menu-item "Execute script..." executable-interpret
-                 :help "Run script with user-specified args, and collect 
output in a buffer"))
+        :help "Run script with user-specified args, and collect output in a 
buffer"))
     (define-key menu-map [sh-set-shell]
       '(menu-item "Set shell type..." sh-set-shell
                  :help "Set this buffer's shell to SHELL (a string)"))
     (define-key menu-map [sh-backslash-region]
       '(menu-item "Backslash region" sh-backslash-region
-                 :help "Insert, align, or delete end-of-line backslashes on 
the lines in the region."))
+        :help "Insert, align, or delete end-of-line backslashes on the lines 
in the region."))
     map)
   "Keymap used in Shell-Script mode.")
 
@@ -564,9 +554,10 @@
   :group 'sh-script)
 
 (defcustom sh-assignment-regexp
-  '((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
+  `((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
     ;; actually spaces are only supported in let/(( ... ))
-    (ksh88 . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ 
\t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=")
+    (ksh88 . ,(concat "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?"
+                      "[ \t]*\\(?:[-+*/%&|~^]\\|<<\\|>>\\)?="))
     (bash . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?\\+?=")
     (rc . "\\<\\([[:alnum:]_*]+\\)[ \t]*=")
     (sh . "\\<\\([[:alnum:]_]+\\)="))
@@ -1379,10 +1370,10 @@
 
 
 (defconst sh-indent-supported
-  '((sh . t)
+  '((sh . sh)
     (csh . nil)
-    (rc . t))
-  "Shell types that shell indenting can do something with.")
+    (rc . rc))
+  "Indentation rule set to use for each shell type.")
 
 (defvar sh-indent-supported-here nil
   "Non-nil if we support indentation for the current buffer's shell type.")
@@ -1464,9 +1455,8 @@
 \\[sh-set-shell]        Set this buffer's shell, and maybe its magic number.
 \\[sh-execute-region]   Have optional header and region be executed in a 
subshell.
 
-\\[sh-maybe-here-document]      Without prefix, following an unquoted < 
inserts here document.
-\{, (, [, ', \", `
-       Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
+`sh-electric-here-document-mode' controls whether insertion of two
+unquoted < insert a here document.
 
 If you generally program a shell different from your login shell you can
 set `sh-shell-file' accordingly.  If your shell's file name doesn't correctly
@@ -1503,13 +1493,13 @@
        #'sh-syntax-propertize-function)
   (add-hook 'syntax-propertize-extend-region-functions
             #'syntax-propertize-multiline 'append 'local)
+  (sh-electric-here-document-mode 1)
   (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`)))
   (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p)
   (set (make-local-variable 'skeleton-further-elements)
        '((< '(- (min sh-indentation (current-column))))))
   (set (make-local-variable 'skeleton-filter-function) 'sh-feature)
   (set (make-local-variable 'skeleton-newline-indent-rigidly) t)
-  (set (make-local-variable 'sh-indent-supported-here) nil)
   (set (make-local-variable 'defun-prompt-regexp)
        (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
   ;; Parse or insert magic number for exec, and set all variables depending
@@ -1519,23 +1509,15 @@
             (goto-char (point-min))
             (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ 
\t\n]+\\)"))
           (match-string 2))
-         ((not buffer-file-name)
-          sh-shell-file)
+         ((not buffer-file-name) sh-shell-file)
          ;; Checks that use `buffer-file-name' follow.
-         ((string-match "\\.m?spec\\'" buffer-file-name)
-          "rpm")
-         ((string-match "[.]sh\\>" buffer-file-name)
-          "sh")
-         ((string-match "[.]bash\\>" buffer-file-name)
-          "bash")
-         ((string-match "[.]ksh\\>" buffer-file-name)
-          "ksh")
-         ((string-match "[.]csh\\>" buffer-file-name)
-          "csh")
-        ((equal (file-name-nondirectory buffer-file-name) ".profile")
-          "sh")
-         (t
-          sh-shell-file))
+         ((string-match "\\.m?spec\\'" buffer-file-name) "rpm")
+         ((string-match "[.]sh\\>"     buffer-file-name) "sh")
+         ((string-match "[.]bash\\>"   buffer-file-name) "bash")
+         ((string-match "[.]ksh\\>"    buffer-file-name) "ksh")
+         ((string-match "[.]csh\\>"    buffer-file-name) "csh")
+        ((equal (file-name-nondirectory buffer-file-name) ".profile") "sh")
+         (t sh-shell-file))
    nil nil))
 
 ;;;###autoload
@@ -1578,6 +1560,426 @@
   "Function to get better fontification including keywords and builtins."
   (sh-font-lock-keywords-1 t))
 
+;;; Indentation and navigation with SMIE.
+
+(require 'smie)
+
+;; The SMIE code should generally be preferred, but it currently does not obey
+;; the various indentation custom-vars, and it misses some important features
+;; of the old code, mostly: sh-learn-line/buffer-indent, sh-show-indent,
+;; sh-name/save/load-style.
+(defvar sh-use-smie nil
+  "Whether to use the SMIE code for navigation and indentation.")
+
+(defun sh-smie--keyword-p (tok)
+  "Non-nil if TOK (at which we're looking) really is a keyword."
+  (let ((prev (funcall smie-backward-token-function)))
+    (if (zerop (length prev))
+        (looking-back "\\s(" (1- (point)))
+      (assoc prev smie-grammar))))
+
+(defun sh-smie--newline-semi-p (&optional tok)
+  "Return non-nil if a newline should be treated as a semi-colon.
+Here we assume that a newline should be treated as a semi-colon unless it
+comes right after a special keyword.
+This function does not pay attention to line-continuations.
+If TOK is nil, point should be before the newline; otherwise, TOK is the token
+before the newline and in that case point should be just before the token."
+  (save-excursion
+    (unless tok
+      (setq tok (funcall smie-backward-token-function)))
+    (if (and (zerop (length tok))
+             (looking-back "\\s(" (1- (point))))
+        nil
+      (not (numberp (nth 2 (assoc tok smie-grammar)))))))
+
+;;;; SMIE support for `sh'.
+
+(defconst sh-smie-sh-grammar
+  (smie-prec2->grammar
+   (smie-bnf->prec2
+    '((exp)                    ;A constant, or a $var, or a sequence of them...
+      (cmd ("case" exp "in" branches "esac")
+           ("if" cmd "then" cmd "fi")
+           ("if" cmd "then" cmd "else" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd "else" cmd "fi")
+           ("if" cmd "then" cmd "elif" cmd "then" cmd
+                                "elif" cmd "then" cmd "else" cmd "fi")
+           ("while" cmd "do" cmd "done")
+           ("until" cmd "do" cmd "done")
+           ("for" exp "in" cmd "do" cmd "done")
+           ("for" exp "do" cmd "done")
+           ("select" exp "in" cmd "do" cmd "done")   ;bash&zsh&ksh88.
+           ("repeat" exp "do" cmd "done")            ;zsh.
+           (exp "always" exp)                        ;zsh.
+           (cmd "|" cmd) (cmd "|&" cmd)
+           (cmd "&&" cmd) (cmd "||" cmd)
+           (cmd ";" cmd) (cmd "&" cmd))
+      (pattern (pattern "|" pattern))
+      (branches (branches ";;" branches)
+                (branches ";&" branches) (branches ";;&" branches) ;bash.
+                (pattern "case-)" cmd)))
+    '((assoc ";;" ";&" ";;&"))
+    '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+(defconst sh-smie--sh-operators
+  (delq nil (mapcar (lambda (x)
+                      (setq x (car x))
+                      (and (stringp x)
+                           (not (string-match "\\`[a-z]" x))
+                           x))
+                    sh-smie-sh-grammar)))
+
+(defconst sh-smie--sh-operators-re (regexp-opt sh-smie--sh-operators))
+(defconst sh-smie--sh-operators-back-re
+  (concat "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*"
+          "\\(" sh-smie--sh-operators-re "\\)"))
+
+(defun sh-smie--sh-keyword-in-p ()
+  "Assuming we're looking at \"in\", return non-nil if it's a keyword.
+Does not preserve point."
+  (let ((forward-sexp-function nil)
+        (words nil)                     ;We've seen words.
+        (newline nil)                   ;We've seen newlines after the words.
+        (res nil)
+        prev)
+    (while (not res)
+      (setq prev (funcall smie-backward-token-function))
+      (cond
+       ((zerop (length prev))
+        (if newline
+            (progn (assert words) (setq res 'word))
+          (setq words t)
+          (condition-case nil
+              (forward-sexp -1)
+            (scan-error (setq res 'unknown)))))
+       ((equal prev ";")
+        (if words (setq newline t)
+          (setq res 'keyword)))
+       ((member prev '("case" "for" "select")) (setq res 'keyword))
+       ((assoc prev smie-grammar) (setq res 'word))
+       (t
+        (if newline
+            (progn (assert words) (setq res 'word))
+          (setq words t)))))
+    (eq res 'keyword)))
+
+(defun sh-smie--sh-keyword-p (tok)
+  "Non-nil if TOK (at which we're looking) really is a keyword."
+  (if (equal tok "in")
+      (sh-smie--sh-keyword-in-p)
+    (sh-smie--keyword-p tok)))
+
+(defun sh-smie-sh-forward-token ()
+  (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
+           (save-excursion
+             (skip-chars-backward " \t")
+             (not (bolp))))
+      (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
+          ;; Right before a here-doc.
+          (let ((forward-sexp-function nil))
+            (forward-sexp 1)
+            ;; Pretend the here-document is a "newline representing a
+            ;; semi-colon", since the here-doc otherwise covers the newline(s).
+            ";")
+        (let ((semi (sh-smie--newline-semi-p)))
+          (forward-line 1)
+          (if semi ";"
+            (sh-smie-sh-forward-token))))
+    (forward-comment (point-max))
+    (cond
+     ((looking-at "\\\\\n") (forward-line 1) (sh-smie-sh-forward-token))
+     ((looking-at sh-smie--sh-operators-re)
+      (goto-char (match-end 0))
+      (let ((tok (match-string-no-properties 0)))
+        (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
+                 (looking-at "[ \t]*\\(?:#\\|$\\)"))
+            (forward-line 1))
+        tok))
+     (t
+      (let* ((pos (point))
+             (tok (smie-default-forward-token)))
+        (cond
+         ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not
+                (save-excursion
+                  (goto-char pos)
+                  (sh-smie--sh-keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie--looking-back-at-continuation-p ()
+  (save-excursion
+    (and (if (eq (char-before) ?\n) (progn (forward-char -1) t) (eolp))
+         (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
+                       (line-beginning-position)))))
+
+(defun sh-smie-sh-backward-token ()
+  (let ((bol (line-beginning-position))
+        pos tok)
+    (forward-comment (- (point)))
+    (cond
+     ((and (bolp) (not (bobp))
+           (equal (syntax-after (1- (point))) (string-to-syntax "|"))
+           (not (nth 3 (syntax-ppss))))
+      ;; Right after a here-document.
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        ;; Pretend the here-document is a "newline representing a
+        ;; semi-colon", since the here-doc otherwise covers the newline(s).
+        ";"))
+     ((< (point) bol)
+      (cond
+       ((sh-smie--looking-back-at-continuation-p)
+        (forward-char -1)
+        (funcall smie-backward-token-function))
+       ((sh-smie--newline-semi-p) ";")
+       (t (funcall smie-backward-token-function))))
+     ((looking-back sh-smie--sh-operators-back-re
+                    (line-beginning-position) 'greedy)
+      (goto-char (match-beginning 1))
+      (match-string-no-properties 1))
+     (t
+      (let ((tok (smie-default-backward-token)))
+        (cond
+         ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not (save-excursion (sh-smie--sh-keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defcustom sh-indent-after-continuation t
+  "If non-nil, try to make sure text is indented after a line continuation."
+  :type 'boolean)
+
+(defun sh-smie--continuation-start-indent ()
+  "Return the initial indentation of a continued line.
+May return nil if the line should not be treated as continued."
+  (save-excursion
+    (forward-line -1)
+    (unless (sh-smie--looking-back-at-continuation-p)
+      (current-indentation))))
+
+(defun sh-smie-sh-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) sh-indentation)
+    (`(:after . "case-)") (or sh-indentation smie-indent-basic))
+    ((and `(:before . ,_)
+          (guard (when sh-indent-after-continuation
+                   (save-excursion
+                     (ignore-errors
+                       (skip-chars-backward " \t")
+                       (sh-smie--looking-back-at-continuation-p))))))
+     ;; After a line-continuation, make sure the rest is indented.
+     (let* ((sh-indent-after-continuation nil)
+            (indent (smie-indent-calculate))
+            (initial (sh-smie--continuation-start-indent)))
+       (when (and (numberp indent) (numberp initial)
+                  (<= indent initial))
+         `(column . ,(+ initial sh-indentation)))))
+    (`(:before . ,(or `"(" `"{" `"["))
+     (if (smie-rule-hanging-p) (smie-rule-parent)))
+    ;; FIXME: Maybe this handling of ;; should be made into
+    ;; a smie-rule-terminator function that takes the substitute ";" as arg.
+    (`(:before . ,(or `";;" `";&" `";;&"))
+     (if (and (smie-rule-bolp) (looking-at ";;?&?[ \t]*\\(#\\|$\\)"))
+         (cons 'column (smie-indent-keyword ";"))
+       (smie-rule-separator kind)))
+    (`(:after . ,(or `";;" `";&" `";;&"))
+     (with-demoted-errors
+       (smie-backward-sexp token)
+       (cons 'column
+             (if (or (smie-rule-bolp)
+                     (save-excursion
+                       (and (member (funcall smie-backward-token-function)
+                                    '("in" ";;"))
+                            (smie-rule-bolp))))
+                 (current-column)
+               (smie-indent-calculate)))))
+    (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
+    ))
+
+;; (defconst sh-smie-csh-grammar
+;;   (smie-prec2->grammar
+;;    (smie-bnf->prec2
+;;     '((exp)                   ;A constant, or a $var, or a sequence of them…
+;;       (elseifcmd (cmd)
+;;                  (cmd "else" "else-if" exp "then" elseifcmd))
+;;       (cmd ("switch" branches "endsw")
+;;            ("if" exp)
+;;            ("if" exp "then" cmd "endif")
+;;            ("if" exp "then" cmd "else" cmd "endif")
+;;            ("if" exp "then" elseifcmd "endif")
+;;            ;; ("if" exp "then" cmd "else" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
+;;            ;;                      "else" cmd "endif")
+;;            ;; ("if" exp "then" cmd "else" "if" exp "then" cmd
+;;            ;;                      "else" "if" exp "then" cmd "endif")
+;;            ("while" cmd "end")
+;;            ("foreach" cmd "end")
+;;            (cmd "|" cmd) (cmd "|&" cmd)
+;;            (cmd "&&" cmd) (cmd "||" cmd)
+;;            (cmd ";" cmd) (cmd "&" cmd))
+;;       ;; This is a lie, but (combined with the corresponding disambiguation
+;;       ;; rule) it makes it more clear that `case' and `default' are the key
+;;       ;; separators and the `:' is a secondary tokens.
+;;       (branches (branches "case" branches)
+;;                 (branches "default" branches)
+;;                 (exp ":" branches)))
+;;     '((assoc "else" "then" "endif"))
+;;     '((assoc "case" "default") (nonassoc ":"))
+;;     '((assoc ";;" ";&" ";;&"))
+;;     '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+;;;; SMIE support for `rc'.
+
+(defconst sh-smie-rc-grammar
+  (smie-prec2->grammar
+   (smie-bnf->prec2
+    '((exp)                    ;A constant, or a $var, or a sequence of them...
+      (cmd (cmd "case" cmd)
+           ("if" exp)
+           ("switch" exp)
+           ("for" exp) ("while" exp)
+           (cmd "|" cmd) (cmd "|&" cmd)
+           (cmd "&&" cmd) (cmd "||" cmd)
+           (cmd ";" cmd) (cmd "&" cmd))
+      (pattern (pattern "|" pattern))
+      (branches (branches ";;" branches)
+                (branches ";&" branches) (branches ";;&" branches) ;bash.
+                (pattern "case-)" cmd)))
+    '((assoc ";;" ";&" ";;&"))
+    '((assoc "case") (assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&")))))
+
+(defun sh-smie--rc-after-special-arg-p ()
+  "Check if we're after the first arg of an if/while/for/... construct.
+Returns the construct's token and moves point before it, if so."
+  (forward-comment (- (point)))
+  (when (looking-back ")\\|\\_<not" (- (point) 3))
+    (ignore-errors
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        (car (member (funcall smie-backward-token-function)
+                     '("if" "for" "switch" "while")))))))
+
+(defun sh-smie--rc-newline-semi-p ()
+  "Return non-nil if a newline should be treated as a semi-colon.
+Point should be before the newline."
+  (save-excursion
+    (let ((tok (funcall smie-backward-token-function)))
+      (if (or (when (equal tok "not") (forward-word 1) t)
+              (and (zerop (length tok)) (eq (char-before) ?\))))
+          (not (sh-smie--rc-after-special-arg-p))
+        (sh-smie--newline-semi-p tok)))))
+
+(defun sh-smie-rc-forward-token ()
+  ;; FIXME: Code duplication with sh-smie-sh-forward-token.
+  (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
+           (save-excursion
+             (skip-chars-backward " \t")
+             (not (bolp))))
+      (if (and (match-end 1) (not (nth 3 (syntax-ppss))))
+          ;; Right before a here-doc.
+          (let ((forward-sexp-function nil))
+            (forward-sexp 1)
+            ;; Pretend the here-document is a "newline representing a
+            ;; semi-colon", since the here-doc otherwise covers the newline(s).
+            ";")
+        (let ((semi (sh-smie--rc-newline-semi-p)))
+          (forward-line 1)
+          (if semi ";"
+            (sh-smie-rc-forward-token))))
+    (forward-comment (point-max))
+    (cond
+     ((looking-at "\\\\\n") (forward-line 1) (sh-smie-rc-forward-token))
+     ;; ((looking-at sh-smie--rc-operators-re)
+     ;;  (goto-char (match-end 0))
+     ;;  (let ((tok (match-string-no-properties 0)))
+     ;;    (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|))
+     ;;             (looking-at "[ \t]*\\(?:#\\|$\\)"))
+     ;;        (forward-line 1))
+     ;;    tok))
+     (t
+      (let* ((pos (point))
+             (tok (smie-default-forward-token)))
+        (cond
+         ;; ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not
+                (save-excursion
+                  (goto-char pos)
+                  (sh-smie--keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie-rc-backward-token ()
+  ;; FIXME: Code duplication with sh-smie-sh-backward-token.
+  (let ((bol (line-beginning-position))
+        pos tok)
+    (forward-comment (- (point)))
+    (cond
+     ((and (bolp) (not (bobp))
+           (equal (syntax-after (1- (point))) (string-to-syntax "|"))
+           (not (nth 3 (syntax-ppss))))
+      ;; Right after a here-document.
+      (let ((forward-sexp-function nil))
+        (forward-sexp -1)
+        ;; Pretend the here-document is a "newline representing a
+        ;; semi-colon", since the here-doc otherwise covers the newline(s).
+        ";"))
+     ((< (point) bol)                   ;We skipped over a newline.
+      (cond
+       ;; A continued line.
+       ((and (eolp)
+             (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\"
+                           (line-beginning-position)))
+        (forward-char -1)
+        (funcall smie-backward-token-function))
+       ((sh-smie--rc-newline-semi-p) ";")
+       (t (funcall smie-backward-token-function))))
+     ;; ((looking-back sh-smie--sh-operators-back-re
+     ;;                (line-beginning-position) 'greedy)
+     ;;  (goto-char (match-beginning 1))
+     ;;  (match-string-no-properties 1))
+     (t
+      (let ((tok (smie-default-backward-token)))
+        (cond
+         ;; ((equal tok ")") "case-)")
+         ((and tok (string-match "\\`[a-z]" tok)
+               (assoc tok smie-grammar)
+               (not (save-excursion (sh-smie--keyword-p tok))))
+          " word ")
+         (t tok)))))))
+
+(defun sh-smie-rc-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) sh-indentation)
+    ;; (`(:after . "case") (or sh-indentation smie-indent-basic))
+    (`(:after . ";") (if (smie-rule-parent-p "case")
+                         (smie-rule-parent sh-indentation)))
+    (`(:before . "{")
+     (save-excursion
+       (when (sh-smie--rc-after-special-arg-p)
+         `(column . ,(current-column)))))
+    (`(:before . ,(or `"(" `"{" `"["))
+     (if (smie-rule-hanging-p) (smie-rule-parent)))
+    ;; FIXME: SMIE parses "if (exp) cmd" as "(if ((exp) cmd))" so "cmd" is
+    ;; treated as an arg to (exp) by default, which indents it all wrong.
+    ;; To handle it right, we should extend smie-indent-exps so that the
+    ;; preceding keyword can give special rules.  Currently the only special
+    ;; rule we have is the :list-intro hack, which we use here to align "cmd"
+    ;; with "(exp)", which is rarely the right thing to do, but is better
+    ;; than nothing.
+    (`(:list-intro . ,(or `"for" `"if" `"while")) t)
+    ))
+
+;;; End of SMIE code.
 
 (defvar sh-regexp-for-done nil
   "A buffer-local regexp to match opening keyword for done.")
@@ -1677,19 +2079,28 @@
       (set-syntax-table sh-mode-syntax-table)))
   (dolist (var (sh-feature sh-variables))
     (sh-remember-variable var))
-  (if (setq sh-indent-supported-here (sh-feature sh-indent-supported))
+  (if (set (make-local-variable 'sh-indent-supported-here)
+           (sh-feature sh-indent-supported))
       (progn
        (message "Setting up indent for shell type %s" sh-shell)
-       (set (make-local-variable 'parse-sexp-lookup-properties) t)
-       (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
-       (let ((regexp (sh-feature sh-kws-for-done)))
-         (if regexp
-             (set (make-local-variable 'sh-regexp-for-done)
-                  (sh-mkword-regexpr (regexp-opt regexp t)))))
-       (message "setting up indent stuff")
-       ;; sh-mode has already made indent-line-function local
-       ;; but do it in case this is called before that.
-       (set (make-local-variable 'indent-line-function) 'sh-indent-line)
+        (if sh-use-smie
+            (let ((mksym (lambda (name)
+                           (intern (format "sh-smie-%s-%s"
+                                           sh-indent-supported-here name)))))
+              (smie-setup (symbol-value (funcall mksym "grammar"))
+                          (funcall mksym "rules")
+                          :forward-token  (funcall mksym "forward-token")
+                          :backward-token (funcall mksym "backward-token")))
+          (set (make-local-variable 'parse-sexp-lookup-properties) t)
+          (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
+          (let ((regexp (sh-feature sh-kws-for-done)))
+            (if regexp
+                (set (make-local-variable 'sh-regexp-for-done)
+                     (sh-mkword-regexpr (regexp-opt regexp t)))))
+          (message "setting up indent stuff")
+          ;; sh-mode has already made indent-line-function local
+          ;; but do it in case this is called before that.
+          (set (make-local-variable 'indent-line-function) 'sh-indent-line))
        (if sh-make-vars-local
            (sh-make-vars-local))
        (message "Indentation setup for shell type %s" sh-shell))
@@ -3237,8 +3648,9 @@
 (defun sh-save-styles-to-buffer (buff)
   "Save all current styles in elisp to buffer BUFF.
 This is always added to the end of the buffer."
-  (interactive (list
-               (read-from-minibuffer "Buffer to save styles in? " 
"*scratch*")))
+  (interactive
+   (list
+    (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
   (with-current-buffer (get-buffer-create buff)
     (goto-char (point-max))
     (insert "\n")
@@ -3656,8 +4068,12 @@
 The document is bounded by `sh-here-document-word'."
   (interactive "*P")
   (self-insert-command (prefix-numeric-value arg))
-  (or arg
-      (not (looking-back "[^<]<<"))
+  (or arg (sh--maybe-here-document)))
+(make-obsolete 'sh--maybe-here-document
+               'sh-electric-here-document-mode "24.2")
+
+(defun sh--maybe-here-document ()
+  (or (not (looking-back "[^<]<<"))
       (save-excursion
        (backward-char 2)
        (sh-quoted-p))
@@ -3678,6 +4094,12 @@
           (insert ?\n tabs (replace-regexp-in-string
                             "\\`-?[ \t]*" "" delim))))))
 
+(define-minor-mode sh-electric-here-document-mode
+  "Make << insert a here document skeleton."
+  nil nil nil
+  (if sh-electric-here-document-mode
+      (add-hook 'post-self-insert-hook #'sh--maybe-here-document nil t)
+    (remove-hook 'post-self-insert-hook #'sh--maybe-here-document t)))
 
 ;; various other commands
 
@@ -3696,12 +4118,14 @@
 
 
 (defun sh-beginning-of-command ()
+  ;; FIXME: Redefine using SMIE.
   "Move point to successive beginnings of commands."
   (interactive)
   (if (re-search-backward sh-beginning-of-command nil t)
       (goto-char (match-beginning 2))))
 
 (defun sh-end-of-command ()
+  ;; FIXME: Redefine using SMIE.
   "Move point to successive ends of commands."
   (interactive)
   (if (re-search-forward sh-end-of-command nil t)


reply via email to

[Prev in Thread] Current Thread [Next in Thread]