changeset 108894:ca3bfaa18e56

Make (after keyword) indent-rules more flexible. * lisp/emacs-lisp/smie.el (smie-indent-offset-after) (smie-indent-forward-token, smie-indent-backward-token): New functions. (smie-indent-after-keyword): Use them. (smie-indent-fixindent): Only applies to the indentation of the BOL. (smie-indent-keyword): Tweak the black magic. (smie-indent-comment-continue): Strip comment-continue before use. (smie-indent-functions): Indent comments before keywords.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Sun, 06 Jun 2010 22:10:19 -0400
parents cc0a50e33241
children c4f9f87f04a2
files lisp/ChangeLog lisp/emacs-lisp/smie.el
diffstat 2 files changed, 156 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Sun Jun 06 12:52:13 2010 +0300
+++ b/lisp/ChangeLog	Sun Jun 06 22:10:19 2010 -0400
@@ -1,3 +1,13 @@
+2010-06-07  Stefan Monnier  <monnier@iro.umontreal.ca>
+
+	* emacs-lisp/smie.el (smie-indent-offset-after)
+	(smie-indent-forward-token, smie-indent-backward-token): New functions.
+	(smie-indent-after-keyword): Use them.
+	(smie-indent-fixindent): Only applies to the indentation of the BOL.
+	(smie-indent-keyword): Tweak the black magic.
+	(smie-indent-comment-continue): Strip comment-continue before use.
+	(smie-indent-functions): Indent comments before keywords.
+
 2010-06-06  Juri Linkov  <juri@jurta.org>
 
 	* isearch.el (isearch-lazy-highlight-search): Fix looping
--- a/lisp/emacs-lisp/smie.el	Sun Jun 06 12:52:13 2010 +0300
+++ b/lisp/emacs-lisp/smie.el	Sun Jun 06 22:10:19 2010 -0400
@@ -294,7 +294,12 @@
 (defvar smie-op-levels 'unset
   "List of token parsing info.
 Each element is of the form (TOKEN LEFT-LEVEL RIGHT-LEVEL).
-Parsing is done using an operator precedence parser.")
+Parsing is done using an operator precedence parser.
+LEFT-LEVEL and RIGHT-LEVEL can be either numbers or nil, where nil
+means that this operator does not bind on the corresponding side,
+i.e. a LEFT-LEVEL of nil means this is a token that behaves somewhat like
+an open-paren, whereas a RIGHT-LEVEL of nil would correspond to something
+like a close-paren.")
 
 (defvar smie-forward-token-function 'smie-default-forward-token
   "Function to scan forward for the next token.
@@ -495,16 +500,19 @@
   ;; I.e. the indentation after "=" depends on the parent ("structure")
   ;; as well as on the following token ("struct").
   "Rules of the following form.
-\(TOK OFFSET)		how to indent right after TOK.
-\(TOK O1 O2)		how to indent right after TOK:
-			  O1 is the default;
-			  O2 is used if TOK is \"hanging\".
+\(TOK . OFFSET-RULES)	how to indent right after TOK.
 \((T1 . T2) . OFFSET)	how to indent token T2 w.r.t T1.
 \((t . TOK) . OFFSET)	how to indent TOK with respect to its parent.
 \(list-intro . TOKENS)	declare TOKENS as being followed by what may look like
 			  a funcall but is just a sequence of expressions.
 \(t . OFFSET)		basic indentation step.
 \(args . OFFSET)		indentation of arguments.
+
+OFFSET-RULES is list of elements which can either be an integer (the offset to
+use), or a cons of the form
+\(:hanging . OFFSET-RULES)	if TOK is hanging, use OFFSET-RULES.
+\(:parent PARENT . OFFSET-RULES) if TOK's parent is PARENT, use OFFSET-RULES.
+\(:next TOKEN . OFFSET-RULES)	if TOK is followed by TOKEN, use OFFSET-RULES.
 A nil offset defaults to `smie-indent-basic'.")
 
 (defun smie-indent-hanging-p ()
@@ -526,6 +534,68 @@
       (cdr (assq t smie-indent-rules))
       smie-indent-basic))
 
+(defun smie-indent-offset-after (tokinfo after)
+  ;; Presumes we're right before the token corresponding to `tokinfo'
+  ;; and `after' is the position that we're trying to indent.
+  (let ((rules (cdr tokinfo))
+        parent next offset)
+    (while (consp rules)
+      (let ((rule (pop rules)))
+        (cond
+         ((not (consp rule)) (setq offset rule))
+         ;; Using this `:hanging' is often "wrong", in that the hangingness
+         ;; of a keyword should usually not affect the indentation of the
+         ;; immediately following expression, but rather should affect the
+         ;; virtual indentation of that keyword (which in turn will affect not
+         ;; only indentation of the immediately following expression, but also
+         ;; other dependent expressions).
+         ;; But there are cases where it's useful: you may want to use it to
+         ;; make the indentation inside parentheses different depending on the
+         ;; hangingness of the open-paren, but without affecting the
+         ;; indentation of the paren-close.
+         ((eq (car rule) :hanging)
+          (when (smie-indent-hanging-p)
+            (setq rules (cdr rule))))
+         ((eq (car rule) :next)
+          (unless next
+            (save-excursion
+              (goto-char after)
+              (setq next (funcall smie-forward-token-function))))
+          (when (equal next (cadr rule))
+            (setq rules (cddr rule))))
+         ((eq (car rule) :parent)
+          (unless parent
+            (save-excursion
+              (goto-char after)
+              (setq parent (smie-backward-sexp 'halfsexp))))
+          (when (equal (nth 2 parent) (cadr rule))
+            (setq rules (cddr rule))))
+         (t (error "Unknown rule %s for indentation after %s"
+                   rule (car tokinfo))))))
+    (or offset (smie-indent-offset t))))
+
+(defun smie-indent-forward-token ()
+  "Skip token forward and return it, along with its levels."
+  (let ((tok (funcall smie-forward-token-function)))
+    (cond
+     ((< 0 (length tok)) (assoc tok smie-op-levels))
+     ((looking-at "\\s(")
+      (forward-char 1)
+      (list (buffer-substring (1- (point)) (point)) nil 0)))))
+
+(defun smie-indent-backward-token ()
+  "Skip token backward and return it, along with its levels."
+  (let ((tok (funcall smie-backward-token-function)))
+    (cond
+     ((< 0 (length tok)) (assoc tok smie-op-levels))
+     ;; 4 == Open paren syntax.
+     ((eq 4 (syntax-class (syntax-after (1- (point)))))
+      (forward-char -1)
+      (list (buffer-substring (point) (1+ (point))) nil 0)))))
+
+;; FIXME: The `virtual' arg is fundamentally wrong: the virtual indent
+;; of a position should not depend on the caller, since it leads to situations
+;; where two dependent indentations get indented differently.
 (defun smie-indent-virtual (virtual)
   "Compute the virtual indentation to use for point.
 This is used when we're not trying to indent point but just
@@ -544,7 +614,8 @@
 
 (defun smie-indent-fixindent ()
   ;; Obey the `fixindent' special comment.
-  (when (save-excursion
+  (and (smie-bolp)
+       (save-excursion
           (comment-normalize-vars)
           (re-search-forward (concat comment-start-skip
                                      "fixindent"
@@ -575,38 +646,56 @@
 (defun smie-indent-keyword ()
   ;; Align closing token with the corresponding opening one.
   ;; (e.g. "of" with "case", or "in" with "let").
+  ;; FIXME: This still looks too much like black magic!!
+  ;; FIXME: Rather than a bunch of rules like (PARENT . TOKEN), we
+  ;; want a single rule for TOKEN with different cases for each PARENT.
   (save-excursion
     (let* ((pos (point))
-           (token (funcall smie-forward-token-function))
-           (toklevels (cdr (assoc token smie-op-levels))))
+           (toklevels (smie-indent-forward-token))
+           (token (pop toklevels)))
       (when (car toklevels)
         (let ((res (smie-backward-sexp 'halfsexp)) tmp)
-          ;; If we didn't move at all, that means we didn't really skip
-          ;; what we wanted.
-          (when (< (point) pos)
-            (cond
-             ((eq (car res) (car toklevels))
-              ;; We bumped into a same-level operator. align with it.
-              (goto-char (cadr res))
-              ;; Don't use (smie-indent-virtual :not-hanging) here, because we
-              ;; want to jump back over a sequence of same-level ops such as
-              ;;    a -> b -> c
-              ;;    -> d
-              ;; So as to align with the earliest appropriate place.
-              (smie-indent-virtual :bolp))
-             ((equal token (save-excursion
-                             (funcall smie-backward-token-function)))
-              ;; in cases such as "fn x => fn y => fn z =>",
-              ;; jump back to the very first fn.
-              ;; FIXME: should we only do that for special tokens like "=>"?
-              (smie-indent-virtual :bolp))
-             ((setq tmp (assoc (cons (caddr res) token)
-                               smie-indent-rules))
-              (goto-char (cadr res))
-              (+ (cdr tmp) (smie-indent-virtual :not-hanging)))
-             (t
-              (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0)
-                 (current-column))))))))))
+          (cond
+           ((not (or (< (point) pos)
+                     (and (cadr res) (< (cadr res) pos))))
+            ;; If we didn't move at all, that means we didn't really skip
+            ;; what we wanted.
+            nil)
+           ((eq (car res) (car toklevels))
+            ;; We bumped into a same-level operator. align with it.
+            (goto-char (cadr res))
+            ;; Don't use (smie-indent-virtual :not-hanging) here, because we
+            ;; want to jump back over a sequence of same-level ops such as
+            ;;    a -> b -> c
+            ;;    -> d
+            ;; So as to align with the earliest appropriate place.
+            (smie-indent-virtual :bolp))
+           ((equal token (save-excursion
+                           (funcall smie-backward-token-function)))
+            ;; in cases such as "fn x => fn y => fn z =>",
+            ;; jump back to the very first fn.
+            ;; FIXME: should we only do that for special tokens like "=>"?
+            (smie-indent-virtual :bolp))
+           ((setq tmp (assoc (cons (caddr res) token)
+                             smie-indent-rules))
+            (goto-char (cadr res))
+            (+ (cdr tmp) (smie-indent-virtual :not-hanging)))
+           ;; FIXME: The rules ((t . TOK) . OFFSET) either indent
+           ;; relative to "before the parent" or "after the parent",
+           ;; depending on details of the grammar.
+           ((null (car res))
+            (assert (eq (point) (cadr res)))
+            (goto-char (cadr res))
+            (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0)
+               (smie-indent-virtual :not-hanging)))
+           ((smie-bolp)
+            (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0)
+               ;; FIXME: This is odd.  Can't we make it use
+               ;; smie-indent-(calculate|virtual) somehow?
+               (smie-indent-after-keyword)))
+           (t
+            (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0)
+               (current-column)))))))))
 
 (defun smie-indent-comment ()
   ;; Indentation of a comment.
@@ -618,53 +707,44 @@
 
 (defun smie-indent-comment-continue ()
   ;; indentation of comment-continue lines.
-  (and (< 0 (length comment-continue))
-       (looking-at (regexp-quote comment-continue)) (nth 4 (syntax-ppss))
+  (let ((continue (comment-string-strip comment-continue t t)))
+    (and (< 0 (length continue))
+         (looking-at (regexp-quote continue)) (nth 4 (syntax-ppss))
        (let ((ppss (syntax-ppss)))
          (save-excursion
            (forward-line -1)
            (if (<= (point) (nth 8 ppss))
                (progn (goto-char (1+ (nth 8 ppss))) (current-column))
              (skip-chars-forward " \t")
-             (if (looking-at (regexp-quote comment-continue))
-                 (current-column)))))))
+               (if (looking-at (regexp-quote continue))
+                   (current-column))))))))
 
 (defun smie-indent-after-keyword ()
   ;; Indentation right after a special keyword.
   (save-excursion
-    (let* ((tok (funcall smie-backward-token-function))
-           (tokinfo (assoc tok smie-indent-rules))
-           (toklevel (if (and (zerop (length tok))
-                              ;; 4 == Open paren syntax.
-                              (eq (syntax-class (syntax-after (1- (point))))
-                                  4))
-                         (progn (forward-char -1)
-                                (setq tok (buffer-substring
-                                           (point) (1+ (point))))
-                                (setq tokinfo (assoc tok smie-indent-rules))
-                                (list tok nil 0))
-                       (assoc tok smie-op-levels))))
+    (let* ((pos (point))
+           (toklevel (smie-indent-backward-token))
+           (tok (car toklevel))
+           (tokinfo (assoc tok smie-indent-rules)))
       (if (and toklevel (null (cadr toklevel)) (null tokinfo))
-          (setq tokinfo (list (car toklevel) nil nil)))
-      (if (and tokinfo (null toklevel))
-          (error "Token %S has indent rule but has no parsing info" tok))
+          (setq tokinfo (list (car toklevel))))
+      ;; (if (and tokinfo (null toklevel))
+      ;;     (error "Token %S has indent rule but has no parsing info" tok))
       (when toklevel
-        (let ((default-offset
+        (let ((offset
+               (cond
+                (tokinfo (smie-indent-offset-after tokinfo pos))
                 ;; The default indentation after a keyword/operator
                 ;; is 0 for infix and t for prefix.
                 ;; Using the BNF syntax, we could come up with
                 ;; better defaults, but we only have the
                 ;; precedence levels here.
-                (if (or tokinfo (null (cadr toklevel)))
-                    (smie-indent-offset t) 0)))
-          ;; For indentation after "(let", we end up accumulating the
-          ;; offset of "(" and the offset of "let", so we use `min'
-          ;; to try and get it right either way.
-          (min
-           (+ (smie-indent-virtual :bolp)
-              (or (caddr tokinfo) (cadr tokinfo) default-offset))
-           (+ (current-column)
-              (or (cadr tokinfo) default-offset))))))))
+                ((null (cadr toklevel)) (smie-indent-offset t))
+                (t 0))))
+          ;; For indentation after "(let" in SML-mode, we end up accumulating
+          ;; the offset of "(" and the offset of "let", so we use `min' to try
+          ;; and get it right either way.
+          (+ (min (smie-indent-virtual :bolp) (current-column)) offset))))))
 
 (defun smie-indent-exps ()
   ;; Indentation of sequences of simple expressions without
@@ -719,8 +799,8 @@
            (current-column)))))))
 
 (defvar smie-indent-functions
-  '(smie-indent-fixindent smie-indent-bob smie-indent-close smie-indent-keyword
-    smie-indent-comment smie-indent-comment-continue smie-indent-after-keyword
+  '(smie-indent-fixindent smie-indent-bob smie-indent-close smie-indent-comment
+    smie-indent-comment-continue smie-indent-keyword smie-indent-after-keyword
     smie-indent-exps)
   "Functions to compute the indentation.
 Each function is called with no argument, shouldn't move point, and should