Mercurial > emacs
comparison lisp/emacs-lisp/smie.el @ 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 | c3cbf94d10f1 |
children | 68586a267c40 |
comparison
equal
deleted
inserted
replaced
108893:cc0a50e33241 | 108894:ca3bfaa18e56 |
---|---|
292 ;;; Parsing using a precedence level table. | 292 ;;; Parsing using a precedence level table. |
293 | 293 |
294 (defvar smie-op-levels 'unset | 294 (defvar smie-op-levels 'unset |
295 "List of token parsing info. | 295 "List of token parsing info. |
296 Each element is of the form (TOKEN LEFT-LEVEL RIGHT-LEVEL). | 296 Each element is of the form (TOKEN LEFT-LEVEL RIGHT-LEVEL). |
297 Parsing is done using an operator precedence parser.") | 297 Parsing is done using an operator precedence parser. |
298 LEFT-LEVEL and RIGHT-LEVEL can be either numbers or nil, where nil | |
299 means that this operator does not bind on the corresponding side, | |
300 i.e. a LEFT-LEVEL of nil means this is a token that behaves somewhat like | |
301 an open-paren, whereas a RIGHT-LEVEL of nil would correspond to something | |
302 like a close-paren.") | |
298 | 303 |
299 (defvar smie-forward-token-function 'smie-default-forward-token | 304 (defvar smie-forward-token-function 'smie-default-forward-token |
300 "Function to scan forward for the next token. | 305 "Function to scan forward for the next token. |
301 Called with no argument should return a token and move to its end. | 306 Called with no argument should return a token and move to its end. |
302 If no token is found, return nil or the empty string. | 307 If no token is found, return nil or the empty string. |
493 ;; structure Foo = | 498 ;; structure Foo = |
494 ;; struct ... end | 499 ;; struct ... end |
495 ;; I.e. the indentation after "=" depends on the parent ("structure") | 500 ;; I.e. the indentation after "=" depends on the parent ("structure") |
496 ;; as well as on the following token ("struct"). | 501 ;; as well as on the following token ("struct"). |
497 "Rules of the following form. | 502 "Rules of the following form. |
498 \(TOK OFFSET) how to indent right after TOK. | 503 \(TOK . OFFSET-RULES) how to indent right after TOK. |
499 \(TOK O1 O2) how to indent right after TOK: | |
500 O1 is the default; | |
501 O2 is used if TOK is \"hanging\". | |
502 \((T1 . T2) . OFFSET) how to indent token T2 w.r.t T1. | 504 \((T1 . T2) . OFFSET) how to indent token T2 w.r.t T1. |
503 \((t . TOK) . OFFSET) how to indent TOK with respect to its parent. | 505 \((t . TOK) . OFFSET) how to indent TOK with respect to its parent. |
504 \(list-intro . TOKENS) declare TOKENS as being followed by what may look like | 506 \(list-intro . TOKENS) declare TOKENS as being followed by what may look like |
505 a funcall but is just a sequence of expressions. | 507 a funcall but is just a sequence of expressions. |
506 \(t . OFFSET) basic indentation step. | 508 \(t . OFFSET) basic indentation step. |
507 \(args . OFFSET) indentation of arguments. | 509 \(args . OFFSET) indentation of arguments. |
510 | |
511 OFFSET-RULES is list of elements which can either be an integer (the offset to | |
512 use), or a cons of the form | |
513 \(:hanging . OFFSET-RULES) if TOK is hanging, use OFFSET-RULES. | |
514 \(:parent PARENT . OFFSET-RULES) if TOK's parent is PARENT, use OFFSET-RULES. | |
515 \(:next TOKEN . OFFSET-RULES) if TOK is followed by TOKEN, use OFFSET-RULES. | |
508 A nil offset defaults to `smie-indent-basic'.") | 516 A nil offset defaults to `smie-indent-basic'.") |
509 | 517 |
510 (defun smie-indent-hanging-p () | 518 (defun smie-indent-hanging-p () |
511 ;; A hanging keyword is one that's at the end of a line except it's not at | 519 ;; A hanging keyword is one that's at the end of a line except it's not at |
512 ;; the beginning of a line. | 520 ;; the beginning of a line. |
524 (defun smie-indent-offset (elem) | 532 (defun smie-indent-offset (elem) |
525 (or (cdr (assq elem smie-indent-rules)) | 533 (or (cdr (assq elem smie-indent-rules)) |
526 (cdr (assq t smie-indent-rules)) | 534 (cdr (assq t smie-indent-rules)) |
527 smie-indent-basic)) | 535 smie-indent-basic)) |
528 | 536 |
537 (defun smie-indent-offset-after (tokinfo after) | |
538 ;; Presumes we're right before the token corresponding to `tokinfo' | |
539 ;; and `after' is the position that we're trying to indent. | |
540 (let ((rules (cdr tokinfo)) | |
541 parent next offset) | |
542 (while (consp rules) | |
543 (let ((rule (pop rules))) | |
544 (cond | |
545 ((not (consp rule)) (setq offset rule)) | |
546 ;; Using this `:hanging' is often "wrong", in that the hangingness | |
547 ;; of a keyword should usually not affect the indentation of the | |
548 ;; immediately following expression, but rather should affect the | |
549 ;; virtual indentation of that keyword (which in turn will affect not | |
550 ;; only indentation of the immediately following expression, but also | |
551 ;; other dependent expressions). | |
552 ;; But there are cases where it's useful: you may want to use it to | |
553 ;; make the indentation inside parentheses different depending on the | |
554 ;; hangingness of the open-paren, but without affecting the | |
555 ;; indentation of the paren-close. | |
556 ((eq (car rule) :hanging) | |
557 (when (smie-indent-hanging-p) | |
558 (setq rules (cdr rule)))) | |
559 ((eq (car rule) :next) | |
560 (unless next | |
561 (save-excursion | |
562 (goto-char after) | |
563 (setq next (funcall smie-forward-token-function)))) | |
564 (when (equal next (cadr rule)) | |
565 (setq rules (cddr rule)))) | |
566 ((eq (car rule) :parent) | |
567 (unless parent | |
568 (save-excursion | |
569 (goto-char after) | |
570 (setq parent (smie-backward-sexp 'halfsexp)))) | |
571 (when (equal (nth 2 parent) (cadr rule)) | |
572 (setq rules (cddr rule)))) | |
573 (t (error "Unknown rule %s for indentation after %s" | |
574 rule (car tokinfo)))))) | |
575 (or offset (smie-indent-offset t)))) | |
576 | |
577 (defun smie-indent-forward-token () | |
578 "Skip token forward and return it, along with its levels." | |
579 (let ((tok (funcall smie-forward-token-function))) | |
580 (cond | |
581 ((< 0 (length tok)) (assoc tok smie-op-levels)) | |
582 ((looking-at "\\s(") | |
583 (forward-char 1) | |
584 (list (buffer-substring (1- (point)) (point)) nil 0))))) | |
585 | |
586 (defun smie-indent-backward-token () | |
587 "Skip token backward and return it, along with its levels." | |
588 (let ((tok (funcall smie-backward-token-function))) | |
589 (cond | |
590 ((< 0 (length tok)) (assoc tok smie-op-levels)) | |
591 ;; 4 == Open paren syntax. | |
592 ((eq 4 (syntax-class (syntax-after (1- (point))))) | |
593 (forward-char -1) | |
594 (list (buffer-substring (point) (1+ (point))) nil 0))))) | |
595 | |
596 ;; FIXME: The `virtual' arg is fundamentally wrong: the virtual indent | |
597 ;; of a position should not depend on the caller, since it leads to situations | |
598 ;; where two dependent indentations get indented differently. | |
529 (defun smie-indent-virtual (virtual) | 599 (defun smie-indent-virtual (virtual) |
530 "Compute the virtual indentation to use for point. | 600 "Compute the virtual indentation to use for point. |
531 This is used when we're not trying to indent point but just | 601 This is used when we're not trying to indent point but just |
532 need to compute the column at which point should be indented | 602 need to compute the column at which point should be indented |
533 in order to figure out the indentation of some other (further down) point. | 603 in order to figure out the indentation of some other (further down) point. |
542 (current-column) | 612 (current-column) |
543 (smie-indent-calculate))) | 613 (smie-indent-calculate))) |
544 | 614 |
545 (defun smie-indent-fixindent () | 615 (defun smie-indent-fixindent () |
546 ;; Obey the `fixindent' special comment. | 616 ;; Obey the `fixindent' special comment. |
547 (when (save-excursion | 617 (and (smie-bolp) |
618 (save-excursion | |
548 (comment-normalize-vars) | 619 (comment-normalize-vars) |
549 (re-search-forward (concat comment-start-skip | 620 (re-search-forward (concat comment-start-skip |
550 "fixindent" | 621 "fixindent" |
551 comment-end-skip) | 622 comment-end-skip) |
552 ;; 1+ to account for the \n comment termination. | 623 ;; 1+ to account for the \n comment termination. |
573 (scan-error nil))))) | 644 (scan-error nil))))) |
574 | 645 |
575 (defun smie-indent-keyword () | 646 (defun smie-indent-keyword () |
576 ;; Align closing token with the corresponding opening one. | 647 ;; Align closing token with the corresponding opening one. |
577 ;; (e.g. "of" with "case", or "in" with "let"). | 648 ;; (e.g. "of" with "case", or "in" with "let"). |
649 ;; FIXME: This still looks too much like black magic!! | |
650 ;; FIXME: Rather than a bunch of rules like (PARENT . TOKEN), we | |
651 ;; want a single rule for TOKEN with different cases for each PARENT. | |
578 (save-excursion | 652 (save-excursion |
579 (let* ((pos (point)) | 653 (let* ((pos (point)) |
580 (token (funcall smie-forward-token-function)) | 654 (toklevels (smie-indent-forward-token)) |
581 (toklevels (cdr (assoc token smie-op-levels)))) | 655 (token (pop toklevels))) |
582 (when (car toklevels) | 656 (when (car toklevels) |
583 (let ((res (smie-backward-sexp 'halfsexp)) tmp) | 657 (let ((res (smie-backward-sexp 'halfsexp)) tmp) |
584 ;; If we didn't move at all, that means we didn't really skip | 658 (cond |
585 ;; what we wanted. | 659 ((not (or (< (point) pos) |
586 (when (< (point) pos) | 660 (and (cadr res) (< (cadr res) pos)))) |
587 (cond | 661 ;; If we didn't move at all, that means we didn't really skip |
588 ((eq (car res) (car toklevels)) | 662 ;; what we wanted. |
589 ;; We bumped into a same-level operator. align with it. | 663 nil) |
590 (goto-char (cadr res)) | 664 ((eq (car res) (car toklevels)) |
591 ;; Don't use (smie-indent-virtual :not-hanging) here, because we | 665 ;; We bumped into a same-level operator. align with it. |
592 ;; want to jump back over a sequence of same-level ops such as | 666 (goto-char (cadr res)) |
593 ;; a -> b -> c | 667 ;; Don't use (smie-indent-virtual :not-hanging) here, because we |
594 ;; -> d | 668 ;; want to jump back over a sequence of same-level ops such as |
595 ;; So as to align with the earliest appropriate place. | 669 ;; a -> b -> c |
596 (smie-indent-virtual :bolp)) | 670 ;; -> d |
597 ((equal token (save-excursion | 671 ;; So as to align with the earliest appropriate place. |
598 (funcall smie-backward-token-function))) | 672 (smie-indent-virtual :bolp)) |
599 ;; in cases such as "fn x => fn y => fn z =>", | 673 ((equal token (save-excursion |
600 ;; jump back to the very first fn. | 674 (funcall smie-backward-token-function))) |
601 ;; FIXME: should we only do that for special tokens like "=>"? | 675 ;; in cases such as "fn x => fn y => fn z =>", |
602 (smie-indent-virtual :bolp)) | 676 ;; jump back to the very first fn. |
603 ((setq tmp (assoc (cons (caddr res) token) | 677 ;; FIXME: should we only do that for special tokens like "=>"? |
604 smie-indent-rules)) | 678 (smie-indent-virtual :bolp)) |
605 (goto-char (cadr res)) | 679 ((setq tmp (assoc (cons (caddr res) token) |
606 (+ (cdr tmp) (smie-indent-virtual :not-hanging))) | 680 smie-indent-rules)) |
607 (t | 681 (goto-char (cadr res)) |
608 (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0) | 682 (+ (cdr tmp) (smie-indent-virtual :not-hanging))) |
609 (current-column)))))))))) | 683 ;; FIXME: The rules ((t . TOK) . OFFSET) either indent |
684 ;; relative to "before the parent" or "after the parent", | |
685 ;; depending on details of the grammar. | |
686 ((null (car res)) | |
687 (assert (eq (point) (cadr res))) | |
688 (goto-char (cadr res)) | |
689 (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0) | |
690 (smie-indent-virtual :not-hanging))) | |
691 ((smie-bolp) | |
692 (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0) | |
693 ;; FIXME: This is odd. Can't we make it use | |
694 ;; smie-indent-(calculate|virtual) somehow? | |
695 (smie-indent-after-keyword))) | |
696 (t | |
697 (+ (or (cdr (assoc (cons t token) smie-indent-rules)) 0) | |
698 (current-column))))))))) | |
610 | 699 |
611 (defun smie-indent-comment () | 700 (defun smie-indent-comment () |
612 ;; Indentation of a comment. | 701 ;; Indentation of a comment. |
613 (and (looking-at comment-start-skip) | 702 (and (looking-at comment-start-skip) |
614 (save-excursion | 703 (save-excursion |
616 (skip-chars-forward " \t\r\n") | 705 (skip-chars-forward " \t\r\n") |
617 (smie-indent-calculate)))) | 706 (smie-indent-calculate)))) |
618 | 707 |
619 (defun smie-indent-comment-continue () | 708 (defun smie-indent-comment-continue () |
620 ;; indentation of comment-continue lines. | 709 ;; indentation of comment-continue lines. |
621 (and (< 0 (length comment-continue)) | 710 (let ((continue (comment-string-strip comment-continue t t))) |
622 (looking-at (regexp-quote comment-continue)) (nth 4 (syntax-ppss)) | 711 (and (< 0 (length continue)) |
712 (looking-at (regexp-quote continue)) (nth 4 (syntax-ppss)) | |
623 (let ((ppss (syntax-ppss))) | 713 (let ((ppss (syntax-ppss))) |
624 (save-excursion | 714 (save-excursion |
625 (forward-line -1) | 715 (forward-line -1) |
626 (if (<= (point) (nth 8 ppss)) | 716 (if (<= (point) (nth 8 ppss)) |
627 (progn (goto-char (1+ (nth 8 ppss))) (current-column)) | 717 (progn (goto-char (1+ (nth 8 ppss))) (current-column)) |
628 (skip-chars-forward " \t") | 718 (skip-chars-forward " \t") |
629 (if (looking-at (regexp-quote comment-continue)) | 719 (if (looking-at (regexp-quote continue)) |
630 (current-column))))))) | 720 (current-column)))))))) |
631 | 721 |
632 (defun smie-indent-after-keyword () | 722 (defun smie-indent-after-keyword () |
633 ;; Indentation right after a special keyword. | 723 ;; Indentation right after a special keyword. |
634 (save-excursion | 724 (save-excursion |
635 (let* ((tok (funcall smie-backward-token-function)) | 725 (let* ((pos (point)) |
636 (tokinfo (assoc tok smie-indent-rules)) | 726 (toklevel (smie-indent-backward-token)) |
637 (toklevel (if (and (zerop (length tok)) | 727 (tok (car toklevel)) |
638 ;; 4 == Open paren syntax. | 728 (tokinfo (assoc tok smie-indent-rules))) |
639 (eq (syntax-class (syntax-after (1- (point)))) | |
640 4)) | |
641 (progn (forward-char -1) | |
642 (setq tok (buffer-substring | |
643 (point) (1+ (point)))) | |
644 (setq tokinfo (assoc tok smie-indent-rules)) | |
645 (list tok nil 0)) | |
646 (assoc tok smie-op-levels)))) | |
647 (if (and toklevel (null (cadr toklevel)) (null tokinfo)) | 729 (if (and toklevel (null (cadr toklevel)) (null tokinfo)) |
648 (setq tokinfo (list (car toklevel) nil nil))) | 730 (setq tokinfo (list (car toklevel)))) |
649 (if (and tokinfo (null toklevel)) | 731 ;; (if (and tokinfo (null toklevel)) |
650 (error "Token %S has indent rule but has no parsing info" tok)) | 732 ;; (error "Token %S has indent rule but has no parsing info" tok)) |
651 (when toklevel | 733 (when toklevel |
652 (let ((default-offset | 734 (let ((offset |
735 (cond | |
736 (tokinfo (smie-indent-offset-after tokinfo pos)) | |
653 ;; The default indentation after a keyword/operator | 737 ;; The default indentation after a keyword/operator |
654 ;; is 0 for infix and t for prefix. | 738 ;; is 0 for infix and t for prefix. |
655 ;; Using the BNF syntax, we could come up with | 739 ;; Using the BNF syntax, we could come up with |
656 ;; better defaults, but we only have the | 740 ;; better defaults, but we only have the |
657 ;; precedence levels here. | 741 ;; precedence levels here. |
658 (if (or tokinfo (null (cadr toklevel))) | 742 ((null (cadr toklevel)) (smie-indent-offset t)) |
659 (smie-indent-offset t) 0))) | 743 (t 0)))) |
660 ;; For indentation after "(let", we end up accumulating the | 744 ;; For indentation after "(let" in SML-mode, we end up accumulating |
661 ;; offset of "(" and the offset of "let", so we use `min' | 745 ;; the offset of "(" and the offset of "let", so we use `min' to try |
662 ;; to try and get it right either way. | 746 ;; and get it right either way. |
663 (min | 747 (+ (min (smie-indent-virtual :bolp) (current-column)) offset)))))) |
664 (+ (smie-indent-virtual :bolp) | |
665 (or (caddr tokinfo) (cadr tokinfo) default-offset)) | |
666 (+ (current-column) | |
667 (or (cadr tokinfo) default-offset)))))))) | |
668 | 748 |
669 (defun smie-indent-exps () | 749 (defun smie-indent-exps () |
670 ;; Indentation of sequences of simple expressions without | 750 ;; Indentation of sequences of simple expressions without |
671 ;; intervening keywords or operators. E.g. "a b c" or "g (balbla) f". | 751 ;; intervening keywords or operators. E.g. "a b c" or "g (balbla) f". |
672 ;; Can be a list of expressions or a function call. | 752 ;; Can be a list of expressions or a function call. |
717 ;; doesn't seem right since it might then indent args less than | 797 ;; doesn't seem right since it might then indent args less than |
718 ;; the function itself. | 798 ;; the function itself. |
719 (current-column))))))) | 799 (current-column))))))) |
720 | 800 |
721 (defvar smie-indent-functions | 801 (defvar smie-indent-functions |
722 '(smie-indent-fixindent smie-indent-bob smie-indent-close smie-indent-keyword | 802 '(smie-indent-fixindent smie-indent-bob smie-indent-close smie-indent-comment |
723 smie-indent-comment smie-indent-comment-continue smie-indent-after-keyword | 803 smie-indent-comment-continue smie-indent-keyword smie-indent-after-keyword |
724 smie-indent-exps) | 804 smie-indent-exps) |
725 "Functions to compute the indentation. | 805 "Functions to compute the indentation. |
726 Each function is called with no argument, shouldn't move point, and should | 806 Each function is called with no argument, shouldn't move point, and should |
727 return either nil if it has no opinion, or an integer representing the column | 807 return either nil if it has no opinion, or an integer representing the column |
728 to which that point should be aligned, if we were to reindent it.") | 808 to which that point should be aligned, if we were to reindent it.") |