]> git.eshelyaron.com Git - emacs.git/commitdiff
Use new baseline indent rule in c-ts-mode
authorYuan Fu <casouri@gmail.com>
Sun, 1 Dec 2024 08:15:08 +0000 (00:15 -0800)
committerEshel Yaron <me@eshelyaron.com>
Wed, 4 Dec 2024 17:04:14 +0000 (18:04 +0100)
Rework the indent rules in c-ts-mode using
c-ts-common-baseline-indent-rule.  Also reworked internal
functions for setting indent styles to make it simpler and (more
importantly) more readable.  Now there's a single function,
c-ts-mode--simple-indent-rules, that returns a value that can go
straight to treesit-simple-indent-rules.

* lisp/progmodes/c-ts-mode.el:
(c-ts-mode--indent-style-setter): Simplify.
(c-ts-mode--get-indent-style): Remove function.
(c-ts-mode--prompt-for-style): Use a hard-coded list of styles.
This is a tiny downgrade from the computed lists but is
acceptable.
(c-ts-mode-set-style): Use c-ts-mode--simple-indent-rules.
(c-ts-mode--standalone-grandparent): Docstring change.
(c-ts-mode--for-loop-indent-rule): New function.
(c-ts-mode--preproc-indent-rules): New variable.  Copied from
old indent rules and added some new rule.
(c-ts-mode--macro-heuristic-rules):
(c-ts-mode--simple-indent-rules):
(c-ts-mode--parenthesized-expression-indent-rule):
(c-ts-mode--label-indent-rules): New functions.
(c-ts-mode):
(c++-ts-mode): Use c-ts-mode--simple-indent-rules.

(cherry picked from commit 63d69bd154987bcc0434e0f85e09bf5dfa07b827)

lisp/progmodes/c-ts-mode.el

index 710160ea472c0655f1311579fc944161e9b12d00..3059898f6d57098a0c2c27d747ff302d66b30d9e 100644 (file)
@@ -86,7 +86,6 @@
 (declare-function treesit-node-first-child-for-pos "treesit.c")
 (declare-function treesit-node-next-sibling "treesit.c")
 (declare-function treesit-node-eq "treesit.c")
-(declare-function treesit-node-match-p "treesit.c")
 (declare-function treesit-query-compile "treesit.c")
 
 ;;; Custom variables
@@ -282,13 +281,13 @@ is actually the parent of point at the moment of indentation."
         0
       c-ts-mode-indent-offset)))
 
-(defun c-ts-mode--prev-sibling (node parent bol &rest _)
+(defun c-ts-mode--anchor-prev-sibling (node parent bol &rest _)
   "Return the start of the previous named sibling of NODE.
 
-This anchor handles the special case where the previous sibling is a
-labeled_statement or preproc directive; in that case, return the child
-of the labeled statement instead.  (Actually, recursively go down until
-the node isn't a labeled_statement or preproc.)  E.g.,
+This anchor handles the special case where the previous sibling
+is a labeled_statement; in that case, return the child of the
+labeled statement instead.  (Actually, recursively go down until
+the node isn't a labeled_statement.)  E.g.,
 
 label:
   int x = 1;
@@ -341,8 +340,8 @@ characters of the current line."
     ;; prev-sibling doesn't have a child.
     (treesit-node-start prev-sibling)))
 
-(defun c-ts-mode--standalone-parent (_n parent &rest _)
-  "Like the standalone-parent anchor but skips preproc nodes and labels.
+(defun c-ts-mode--standalone-parent-skip-preproc (_n parent &rest _)
+  "Like the standalone-parent anchor but skips preproc nodes.
 PARENT is the parent of the current node."
   (save-excursion
     (treesit-node-start
@@ -351,14 +350,49 @@ PARENT is the parent of the current node."
       ;; nil.
       parent (lambda (node)
                (and node
-                    (not (treesit-node-match-p
-                          node (rx (or "preproc" "labeled_statement"))))
+                    (not (string-search "preproc" (treesit-node-type node)))
                     (progn
                       (goto-char (treesit-node-start node))
                       (looking-back (rx bol (* whitespace))
                                     (line-beginning-position)))))
       t))))
 
+(defun c-ts-mode--standalone-grandparent (_node parent bol &rest args)
+  "Like the standalone-parent anchor but pass it the grandparent.
+NODE, PARENT, BOL, ARGS are the same as in other anchors."
+  (apply (alist-get 'standalone-parent treesit-simple-indent-presets)
+         parent (treesit-node-parent parent) bol args))
+
+(defun c-ts-mode--else-heuristic (node parent bol &rest _)
+  "Heuristic matcher for when \"else\" is followed by a closing bracket.
+PARENT is NODE's parent, BOL is the beginning of non-whitespace
+characters of the current line."
+  (and (null node)
+       (save-excursion
+         (forward-line -1)
+         (looking-at (rx (* whitespace) "else" (* whitespace) eol)))
+       (let ((next-node (treesit-node-first-child-for-pos parent bol)))
+         (equal (treesit-node-type next-node) "}"))))
+
+(defun c-ts-mode--first-sibling (node parent &rest _)
+  "Matches when NODE is the \"first sibling\".
+\"First sibling\" is defined as: the first child node of PARENT
+such that it's on its own line.  NODE is the node to match and
+PARENT is its parent."
+  (let ((prev-sibling (treesit-node-prev-sibling node t)))
+    (or (null prev-sibling)
+        (save-excursion
+          (goto-char (treesit-node-start prev-sibling))
+          (<= (line-beginning-position)
+              (treesit-node-start parent)
+              (line-end-position))))))
+
+(defun c-ts-mode--parent-is-not-top-compound (_n parent &rest _)
+  "Matches when PARENT is not the top level compound statement.
+The top-level compound is the {} that immediately follows the function
+signature."
+  (not (equal "function_definition" (treesit-node-type (treesit-node-parent parent)))))
+
 (defun c-ts-mode--for-loop-indent-rule (node parent &rest _)
   "Indentation rule for the for-loop.
 
@@ -383,24 +417,24 @@ NODE and PARENT as usual."
   `(((node-is "preproc") column-0 0)
     ((node-is "#endif") column-0 0)
     ((match "preproc_call" "compound_statement") column-0 0)
-    ((prev-line-is "#endif") c-ts-mode--prev-sibling 0)
+    ((prev-line-is "#endif") c-ts-mode--anchor-prev-sibling 0)
     ;; Top-level things under a preproc directive.  Note that
     ;; "preproc" matches more than one type: it matches
     ;; preproc_if, preproc_elif, etc.
     ((n-p-gp nil "preproc" "translation_unit") column-0 0)
     ;; Indent rule for an empty line after a preproc directive.
     ((and no-node (parent-is ,(rx (or "\n" "preproc"))))
-     c-ts-mode--standalone-parent c-ts-mode--preproc-offset)
+     c-ts-mode--standalone-parent-skip-preproc c-ts-mode--preproc-offset)
     ;; Statement under a preproc directive, the first statement
     ;; indents against parent, the rest statements indent to
     ;; their prev-sibling.
     ((match nil ,(rx "preproc_" (or "if" "elif")) nil 3 3)
-     c-ts-mode--standalone-parent c-ts-mode-indent-offset)
+     c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
     ((match nil "preproc_ifdef" nil 2 2)
-     c-ts-mode--standalone-parent c-ts-mode-indent-offset)
+     c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
     ((match nil "preproc_else" nil 1 1)
-     c-ts-mode--standalone-parent c-ts-mode-indent-offset)
-    ((parent-is "preproc") c-ts-mode--prev-sibling 0))
+     c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
+    ((parent-is "preproc") c-ts-mode--anchor-prev-sibling 0))
   "Indent rules for preprocessors.")
 
 (defun c-ts-mode--macro-heuristic-rules (node parent &rest _)
@@ -485,6 +519,11 @@ MODE can be `c' or `c++'.  STYLE can be `gnu', `k&r', `linux', `bsd'."
             1)
            ((parent-is "comment") prev-adaptive-prefix 0)
 
+           ;; Labels.
+           ((node-is "labeled_statement") standalone-parent 0)
+           ((parent-is "labeled_statement")
+            c-ts-mode--standalone-grandparent c-ts-mode-indent-offset)
+
            ;; Preproc directives
            ((node-is "preproc_arg") no-indent)
            ((node-is "preproc") column-0 0)
@@ -515,6 +554,155 @@ MODE can be `c' or `c++'.  STYLE can be `gnu', `k&r', `linux', `bsd'."
       ('c `((c . ,rules)))
       ('c++ `((cpp . ,rules))))))
 
+(defun c-ts-mode--indent-styles (mode)
+  "Indent rules supported by `c-ts-mode'.
+MODE is either `c' or `cpp'."
+  (let ((common
+         `((c-ts-mode--for-each-tail-body-matcher prev-line c-ts-mode-indent-offset)
+           ;; If the user types "else" and hits RET, they expect point
+           ;; on the empty line to be indented; this rule does that.
+           ;; This heuristic is intentionally very specific because
+           ;; more general heuristic is very error-prone, see
+           ;; discussion in bug#67417.
+           (c-ts-mode--else-heuristic prev-line c-ts-mode-indent-offset)
+
+           ((parent-is "translation_unit") column-0 0)
+           ((query "(ERROR (ERROR)) @indent") column-0 0)
+           ((node-is ")") parent 1)
+           ((node-is "]") parent-bol 0)
+           ((node-is "else") parent-bol 0)
+           ((node-is "case") parent-bol 0)
+           ((node-is "preproc_arg") no-indent)
+           ;; `c-ts-common-looking-at-star' has to come before
+           ;; `c-ts-common-comment-2nd-line-matcher'.
+           ((and (parent-is "comment") c-ts-common-looking-at-star)
+            c-ts-common-comment-start-after-first-star -1)
+           (c-ts-common-comment-2nd-line-matcher
+            c-ts-common-comment-2nd-line-anchor
+            1)
+           ((parent-is "comment") prev-adaptive-prefix 0)
+
+           ;; Labels.
+           ((node-is "labeled_statement") standalone-parent 0)
+           ((parent-is "labeled_statement")
+            c-ts-mode--standalone-grandparent c-ts-mode-indent-offset)
+
+           ;; Preproc directives
+           ((node-is "preproc") column-0 0)
+           ((node-is "#endif") column-0 0)
+           ((match "preproc_call" "compound_statement") column-0 0)
+
+           ;; Top-level things under a preproc directive.  Note that
+           ;; "preproc" matches more than one type: it matches
+           ;; preproc_if, preproc_elif, etc.
+           ((n-p-gp nil "preproc" "translation_unit") column-0 0)
+           ;; Indent rule for an empty line after a preproc directive.
+           ((and no-node (parent-is ,(rx (or "\n" "preproc"))))
+            c-ts-mode--standalone-parent-skip-preproc c-ts-mode--preproc-offset)
+           ;; Statement under a preproc directive, the first statement
+           ;; indents against parent, the rest statements indent to
+           ;; their prev-sibling.
+           ((match nil ,(rx "preproc_" (or "if" "elif")) nil 3 3)
+            c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
+           ((match nil "preproc_ifdef" nil 2 2)
+            c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
+           ((match nil "preproc_else" nil 1 1)
+            c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset)
+           ((parent-is "preproc") c-ts-mode--anchor-prev-sibling 0)
+
+           ((parent-is "function_definition") parent-bol 0)
+           ((parent-is "pointer_declarator") parent-bol 0)
+           ((parent-is ,(rx bos "declaration" eos)) parent-bol 0)
+           ((parent-is "conditional_expression") first-sibling 0)
+           ((parent-is "assignment_expression") parent-bol c-ts-mode-indent-offset)
+           ((parent-is "concatenated_string") first-sibling 0)
+           ((parent-is "comma_expression") first-sibling 0)
+           ((parent-is "init_declarator") parent-bol c-ts-mode-indent-offset)
+           ((parent-is "parenthesized_expression") first-sibling 1)
+           ((parent-is "argument_list") first-sibling 1)
+           ((parent-is "parameter_list") first-sibling 1)
+           ((parent-is "binary_expression") parent 0)
+           ((query "(for_statement initializer: (_) @indent)") parent-bol 5)
+           ((query "(for_statement condition: (_) @indent)") parent-bol 5)
+           ((query "(for_statement update: (_) @indent)") parent-bol 5)
+           ((query "(call_expression arguments: (_) @indent)") parent c-ts-mode-indent-offset)
+           ((parent-is "call_expression") parent 0)
+           ;; Closing bracket.  This should be before initializer_list
+           ;; (and probably others) rule because that rule (and other
+           ;; similar rules) will match the closing bracket.  (Bug#61398)
+           ((and (node-is "}") c-ts-mode--parent-is-not-top-compound) parent-bol 0)
+           ((node-is "}") standalone-parent 0)
+           ,@(when (eq mode 'cpp)
+               '(((node-is "access_specifier") parent-bol 0)
+                 ;; Indent the body of namespace definitions.
+                 ((parent-is "declaration_list") parent-bol c-ts-mode-indent-offset)
+                 ((parent-is "template_declaration") parent-bol 0)))
+
+
+           ;; int[5] a = { 0, 0, 0, 0 };
+           ((match nil "initializer_list" nil 1 1) parent-bol c-ts-mode-indent-offset)
+           ((parent-is "initializer_list") c-ts-mode--anchor-prev-sibling 0)
+           ;; Statement in enum.
+           ((match nil "enumerator_list" nil 1 1) standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "enumerator_list") c-ts-mode--anchor-prev-sibling 0)
+           ;; Statement in struct and union.
+           ((match nil "field_declaration_list" nil 1 1) standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "field_declaration_list") c-ts-mode--anchor-prev-sibling 0)
+
+           ;; Statement in {} blocks.
+           ((and (parent-is "compound_statement") c-ts-mode--parent-is-not-top-compound)
+            parent-bol c-ts-mode-indent-offset)
+           ((or (and (parent-is "compound_statement")
+                     ;; If the previous sibling(s) are not on their
+                     ;; own line, indent as if this node is the first
+                     ;; sibling (Bug#67357)
+                     c-ts-mode--first-sibling)
+                (match null "compound_statement"))
+            standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "compound_statement") c-ts-mode--anchor-prev-sibling 0)
+           ;; Opening bracket.
+           ((node-is "compound_statement") standalone-parent c-ts-mode-indent-offset)
+           ;; Bug#61291.
+           ((match "expression_statement" nil "body") standalone-parent c-ts-mode-indent-offset)
+           ;; These rules are for cases where the body is bracketless.
+           ;; Tested by the "Bracketless Simple Statement" test.
+           ((parent-is "if_statement") standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "else_clause") standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "for_statement") standalone-parent c-ts-mode-indent-offset)
+           ((match "while" "do_statement") parent-bol 0) ; (do_statement "while")
+           ((parent-is "while_statement") standalone-parent c-ts-mode-indent-offset)
+           ((parent-is "do_statement") standalone-parent c-ts-mode-indent-offset)
+
+           ((parent-is "case_statement") standalone-parent c-ts-mode-indent-offset)
+
+           ,@(when (eq mode 'cpp)
+               `(((node-is "field_initializer_list") parent-bol ,(* c-ts-mode-indent-offset 2)))))))
+    `((gnu
+       ;; Prepend rules to set highest priority
+       ((match "while" "do_statement") parent 0)
+       (c-ts-mode--top-level-label-matcher column-0 1)
+       ,@common)
+      (k&r ,@common)
+      (linux
+       ;; Reference:
+       ;; https://www.kernel.org/doc/html/latest/process/coding-style.html,
+       ;; and script/Lindent in Linux kernel repository.
+       ((node-is "labeled_statement") column-0 0)
+       ,@common)
+      (bsd
+       ((node-is "}") parent-bol 0)
+       ((node-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
+       ((parent-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
+       ((parent-is "compound_statement") parent-bol c-ts-mode-indent-offset)
+       ((match "compound_statement" "if_statement") standalone-parent 0)
+       ((match "compound_statement" "else_clause") standalone-parent 0)
+       ((match "compound_statement" "for_statement") standalone-parent 0)
+       ((match "compound_statement" "while_statement") standalone-parent 0)
+       ((match "compound_statement" "switch_statement") standalone-parent 0)
+       ((match "compound_statement" "case_statement") standalone-parent 0)
+       ((match "compound_statement" "do_statement") standalone-parent 0)
+       ,@common))))
+
 (defun c-ts-mode--parenthesized-expression-indent-rule (_node parent &rest _)
   "Indent rule that indents aprenthesized expression.
 
@@ -544,9 +732,9 @@ NODE, PARENT are the same as other indent rules."
              (treesit-node-child parent 0 'named))
             0))))
 
-(defun c-ts-mode--label-indent-rules (node parent bol &rest args)
+(defun c-ts-mode--label-indent-rules (node parent &rest _)
   "Handles indentation around labels.
-NODE, PARENT, BOL, ARGS are as usual."
+NODE, PARENT are as usual."
   (cond
    ;; Matches the top-level labels for GNU-style.
    ((and (eq c-ts-mode-indent-style 'gnu)
@@ -554,23 +742,25 @@ NODE, PARENT, BOL, ARGS are as usual."
          (treesit-node-match-p (treesit-node-parent parent)
                                "function_definition"))
     (cons (pos-bol) 1))
-   ;; Indent the label itself.
-   ((treesit-node-match-p node "labeled_statement")
-    (cons (c-ts-mode--standalone-parent node parent bol args)
-          0))
-   ;; Indent the statement below the label.
-   ((treesit-node-match-p parent "labeled_statement")
-    (cons (c-ts-mode--standalone-parent node parent bol args)
-          c-ts-mode-indent-offset))
    ;; If previous sibling is a labeled_statement, align to it's
    ;; children, which is the previous statement.
    ((and (not (treesit-node-match-p node "}"))
          (treesit-node-match-p (treesit-node-prev-sibling node)
                                "labeled_statement"))
-    (cons (c-ts-mode--prev-sibling node parent bol args)
+    (cons (treesit-node-start
+           (treesit-node-child
+            (treesit-node-prev-sibling node) 1 'named))
           0))
    (t nil)))
 
+(defun c-ts-mode--top-level-label-matcher (node parent &rest _)
+  "A matcher that matches a top-level label.
+NODE should be a labeled_statement.  PARENT is its parent."
+  (and (equal (treesit-node-type node)
+              "labeled_statement")
+       (equal "function_definition"
+              (treesit-node-type (treesit-node-parent parent)))))
+
 ;;; Font-lock
 
 (defvar c-ts-mode--feature-list
@@ -801,17 +991,17 @@ MODE is either `c' or `cpp'."
    :feature 'delimiter
    '((["," ":" ";"]) @font-lock-delimiter-face)
 
-   :language mode
-   :feature 'emacs-devel
-   :override t
-   `(((call_expression
-       (call_expression function: (identifier) @fn)
-       @c-ts-mode--fontify-DEFUN)
-      (:match "\\`DEFUN\\'" @fn))
-
-     ((function_definition type: (_) @for-each-tail)
-      @c-ts-mode--fontify-for-each-tail
-      (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail)))))
+:language mode
+:feature 'emacs-devel
+:override t
+`(((call_expression
+    (call_expression function: (identifier) @fn)
+    @c-ts-mode--fontify-DEFUN)
+   (:match "\\`DEFUN\\'" @fn))
+
+  ((function_definition type: (_) @for-each-tail)
+   @c-ts-mode--fontify-for-each-tail
+   (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail)))))
 
 ;;; Font-lock helpers