1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-26 07:11:34 -08:00

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.
This commit is contained in:
Stefan Monnier 2010-06-06 22:10:19 -04:00
parent 27dd3c1158
commit c2ea581076
2 changed files with 156 additions and 66 deletions

View file

@ -294,7 +294,12 @@ PREC2 is a table as returned by `smie-precs-precedence-table' or
(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 @@ Possible return values:
;; 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 @@ A nil offset defaults to `smie-indent-basic'.")
(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 @@ VIRTUAL can take two different values:
(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 @@ VIRTUAL can take two different values:
(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 @@ VIRTUAL can take two different values:
(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 @@ VIRTUAL can take two different values:
(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