aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2024-11-29 20:43:09 -0800
committerYuan Fu2024-12-01 17:53:22 -0800
commite37cd4fa597beaec3b491edb1b15ea0c19e72be4 (patch)
treec9e7f33364e95c1a4597760833f3862212509cec
parent4afdb7e80febd56f4024bad0aff4356198f6ce53 (diff)
downloademacs-e37cd4fa597beaec3b491edb1b15ea0c19e72be4.tar.gz
emacs-e37cd4fa597beaec3b491edb1b15ea0c19e72be4.zip
Add baseline tree-sitter indent rule for C-like languages
I found a really good baseline indent rule that handles a wide range of situations very well. Now major modes can just start with this rule and add exceptions on top. This is worth mentioning in the manual, but that'll be a large change, and doesn't have to be included in this commit. * lisp/progmodes/c-ts-common.el: (c-ts-common-list-indent-style): New variable. (c-ts-common--standalone-parent): (c-ts-common--prev-standalone-sibling): (c-ts-common-parent-ignore-preproc): (c-ts-common-baseline-indent-rule): New function. * lisp/treesit.el (treesit--indent-prev-line-node): New function. (treesit-simple-indent-presets): Add new preset.
-rw-r--r--lisp/progmodes/c-ts-common.el163
-rw-r--r--lisp/treesit.el14
2 files changed, 177 insertions, 0 deletions
diff --git a/lisp/progmodes/c-ts-common.el b/lisp/progmodes/c-ts-common.el
index d05b5248cc2..ab5c01bad2d 100644
--- a/lisp/progmodes/c-ts-common.el
+++ b/lisp/progmodes/c-ts-common.el
@@ -532,6 +532,169 @@ characters on the current line."
532 (setq node (treesit-node-parent node))) 532 (setq node (treesit-node-parent node)))
533 (* level (symbol-value c-ts-common-indent-offset)))) 533 (* level (symbol-value c-ts-common-indent-offset))))
534 534
535;;; Baseline indent rule
536
537(defvar c-ts-common-list-indent-style 'align
538 "Intructs `c-ts-common-baseline-indent-rule' how to indent lists.
539
540If the value is `align', indent lists like this:
541
542const a = [
543 1, 2, 3
544 4, 5, 6,
545 ];
546
547If the value is `simple', indent lists like this:
548
549const a = [
550 1, 2, 3,
551 4, 5, 6,
552];")
553
554(defun c-ts-common--standalone-parent (parent)
555 "Find the first parent that starts on a new line.
556Start searching from PARENT, so if PARENT satisfies the condition, it'll
557be returned. Return the starting position of the parent, return nil if
558no parent satisfies the condition."
559 (save-excursion
560 (catch 'term
561 (while parent
562 (goto-char (treesit-node-start parent))
563 (when (looking-back (rx bol (* whitespace))
564 (line-beginning-position))
565 (throw 'term (point)))
566 (setq parent (treesit-node-parent parent))))))
567
568(defun c-ts-common--prev-standalone-sibling (node)
569 "Return the previous sibling of NODE that starts on a new line.
570Return nil if no sibling satisfies the condition."
571 (save-excursion
572 (setq node (treesit-node-prev-sibling node 'named))
573 (goto-char (treesit-node-start node))
574 (while (and node
575 (goto-char (treesit-node-start node))
576 (not (looking-back (rx bol (* whitespace))
577 (pos-bol))))
578 (setq node (treesit-node-prev-sibling node 'named)))
579 node))
580
581(defun c-ts-common-parent-ignore-preproc (node)
582 "Return the parent of NODE, skipping preproc nodes."
583 (let ((parent (treesit-node-parent node))
584 (pred (if (treesit-thing-defined-p
585 'preproc (or (and node (treesit-node-language node))
586 (treesit-parser-language
587 treesit-primary-parser)))
588 'preproc
589 "preproc")))
590 (while (and parent (treesit-node-match-p parent pred))
591 (setq parent (treesit-node-parent parent)))
592 parent))
593
594(defun c-ts-common-baseline-indent-rule (node parent bol &rest _)
595 "Baseline indent rule for C-like languages.
596
597NODE PARENT, BOL are like in other simple indent rules.
598
599This rule works as follows:
600
601Let PREV-NODE be the largest node that starts on previous line,
602basically the NODE we get if we were indenting previous line.
603
6040. Closing brace aligns with first parent that starts on a new line.
605
6061. If PREV-NODE and NODE are siblings, align this line to previous
607line (PREV-NODE as the anchor, and offset is 0).
608
6092. If PARENT is a list, ie, (...) [...], align with NODE's first
610sibling. For the first sibling and the closing paren or bracket, indent
611according to `c-ts-common-list-indent-style'. This rule also handles
612initializer lists like {...}, but initializer lists doesn't respect
613`c-ts-common-list-indent-style'--they always indent in the `simple'
614style.
615
6163. Otherwise, go up the parse tree from NODE and look for a parent that
617starts on a new line. Use that parent as the anchor and indent one
618level. But if the node is a top-level construct (ignoring preprocessor
619nodes), don't indent it.
620
621This rule works for a wide range of scenarios including complex
622situations. Major modes should use this as the fallback rule, and add
623exception rules before it to cover the cases it doesn't apply.
624
625This rule tries to be smart and ignore proprocessor node in some
626situations. By default, any node that has \"proproc\" in its type are
627considered a preprocessor node. If that heuristic is inaccurate, define
628a `preproc' thing in `treesit-thing-settings', and this rule will use
629the thing definition instead."
630 (let ((prev-line-node (treesit--indent-prev-line-node bol))
631 (offset (symbol-value c-ts-common-indent-offset)))
632 (cond
633 ;; Condition 0.
634 ((and (treesit-node-match-p node "}")
635 (treesit-node-match-p (treesit-node-child parent 0) "{"))
636 (cons (c-ts-common--standalone-parent parent)
637 0))
638 ;; Condition 1.
639 ((and (treesit-node-eq (treesit-node-parent prev-line-node)
640 parent)
641 (not (treesit-node-match-p node (rx (or ")" "]")))))
642 (cons (treesit-node-start prev-line-node)
643 0))
644 ;; Condition 2.
645 ((treesit-node-match-p (treesit-node-child parent 0)
646 (rx (or "(" "[")))
647 (let ((first-sibling (treesit-node-child parent 0 'named)))
648 (cond
649 ;; Closing delimeters.
650 ((treesit-node-match-p node (rx (or ")" "]")))
651 (if (eq c-ts-common-list-indent-style 'align)
652 (cons (treesit-node-start (treesit-node-child parent 0))
653 0)
654 (cons (c-ts-common--standalone-parent parent)
655 0)))
656 ;; First sibling.
657 ((treesit-node-eq node first-sibling)
658 (if (eq c-ts-common-list-indent-style 'align)
659 (cons (treesit-node-start (treesit-node-child parent 0))
660 1)
661 (cons (c-ts-common--standalone-parent parent)
662 offset)))
663 ;; Not first sibling
664 (t (cons (treesit-node-start
665 (or (c-ts-common--prev-standalone-sibling node)
666 first-sibling))
667 0)))))
668 ;; Condition 2 for initializer list, only apply to
669 ;; second line. Eg,
670 ;;
671 ;; return { 1, 2, 3,
672 ;; 4, 5, 6, --> Handled by this condition.
673 ;; 7, 8, 9 }; --> Handled by condition 1.
674 ((and (treesit-node-match-p (treesit-node-child parent 0) "{")
675 (treesit-node-prev-sibling node 'named))
676 ;; If first sibling is a comment, indent like code; otherwise
677 ;; align to first sibling.
678 (if (treesit-node-match-p
679 (treesit-node-child parent 0 'named)
680 c-ts-common--comment-regexp 'ignore-missing)
681 (cons (c-ts-common--standalone-parent parent)
682 offset)
683 (cons (treesit-node-start
684 (treesit-node-child parent 0 'named))
685 0)))
686 ;; Before we fallback to condition 3, make sure we don't indent
687 ;; top-level stuff.
688 ((treesit-node-eq (treesit-parser-root-node
689 (treesit-node-parser parent))
690 (c-ts-common-parent-ignore-preproc node))
691 (cons (pos-bol) 0))
692 ;; Condition 3.
693 (t (cons (c-ts-common--standalone-parent parent)
694 offset)))))
695
696
697
535(provide 'c-ts-common) 698(provide 'c-ts-common)
536 699
537;;; c-ts-common.el ends here 700;;; c-ts-common.el ends here
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 3539942f19a..2acb46ab105 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1589,6 +1589,14 @@ should take the same argument as MATCHER or ANCHOR. If it matches,
1589return a cons (ANCHOR-POS . OFFSET), where ANCHOR-POS is a position and 1589return a cons (ANCHOR-POS . OFFSET), where ANCHOR-POS is a position and
1590OFFSET is the indent offset; if it doesn't match, return nil.") 1590OFFSET is the indent offset; if it doesn't match, return nil.")
1591 1591
1592(defun treesit--indent-prev-line-node (pos)
1593 "Return the largest node on the previous line of POS."
1594 (save-excursion
1595 (goto-char pos)
1596 (when (eq (forward-line -1) 0)
1597 (back-to-indentation)
1598 (treesit--indent-largest-node-at (point)))))
1599
1592(defvar treesit-simple-indent-presets 1600(defvar treesit-simple-indent-presets
1593 (list (cons 'match 1601 (list (cons 'match
1594 (lambda 1602 (lambda
@@ -1639,6 +1647,12 @@ OFFSET is the indent offset; if it doesn't match, return nil.")
1639 (lambda (node &rest _) 1647 (lambda (node &rest _)
1640 (string-match-p 1648 (string-match-p
1641 type (or (treesit-node-type node) ""))))) 1649 type (or (treesit-node-type node) "")))))
1650 ;; FIXME: Add to manual.
1651 (cons 'prev-line-is (lambda (type)
1652 (lambda (_n _p bol &rest _)
1653 (treesit-node-match-p
1654 (treesit--indent-prev-line-node bol)
1655 type))))
1642 (cons 'field-is (lambda (name) 1656 (cons 'field-is (lambda (name)
1643 (lambda (node &rest _) 1657 (lambda (node &rest _)
1644 (string-match-p 1658 (string-match-p