diff --git a/lisp/progmodes/c-ts-common.el b/lisp/progmodes/c-ts-common.el index 0b0a7ff7cd3..8262e6261d4 100644 --- a/lisp/progmodes/c-ts-common.el +++ b/lisp/progmodes/c-ts-common.el @@ -267,33 +267,52 @@ This should be the symbol of the indent offset variable for the particular major mode. This cannot be nil for `c-ts-common' statement indent functions to work.") -(defvar c-ts-common-indent-block-type-regexp nil - "Regexp matching types of block nodes (i.e., {} blocks). +(defvar c-ts-common-indent-type-regexp-alist nil + "An alist of of node type regexps. -This cannot be nil for `c-ts-common' statement indent functions -to work.") +Each key in the alist is one of `if', `else', `do', `while', +`for', `block', `close-bracket'. Each value in the alist +is the regexp matching the type of that kind of node. Most of +these types are self-explanatory, e.g., `if' corresponds to +\"if_statement\" in C. `block' corresponds to the {} block. -(defvar c-ts-common-indent-bracketless-type-regexp nil - "A regexp matching types of bracketless constructs. +Some types, specifically `else', is usually not identified by a +standalone node, but a child under the \"if_statement\", under a +field name like \"alternative\", etc. In that case, use a +cons (TYPE . FIELD-NAME) as the value, where TYPE is the node's +parent's type, and FIELD-NAME is the field name of the node. -These constructs include if, while, do-while, for statements. In -these statements, the body can omit the bracket, which requires -special handling from our bracket-counting indent algorithm. +If the language doesn't have a particular type, it is fine to +omit it.") -This can be nil, meaning such special handling is not needed.") +(defun c-ts-common--node-is (node &rest types) + "Return non-nil if NODE is any one of the TYPES. -(defvar c-ts-common-if-statement-regexp "if_statement" - "Regexp used to select an if statement in a C like language. +TYPES can be any of `if', `else', `while', `do', `for', and +`block'. -This can be set to a different regexp if needed.") +If NODE is nil, return nil." + (declare (indent 2)) + (catch 'ret + (when (null node) + (throw 'ret nil)) + (dolist (type types) + (let ((regexp (alist-get + type c-ts-common-indent-type-regexp-alist)) + (parent (treesit-node-parent node))) + (when (and regexp + (if (consp regexp) + (and parent + (string-match-p (car regexp) + (treesit-node-type parent)) + (string-match-p (cdr regexp) + (treesit-node-field-name + node))) + (string-match-p regexp (treesit-node-type node)))) + (throw 'ret t)))) + nil)) -(defvar c-ts-common-nestable-if-statement-p t - "Does the current parser nest if-else statements? - -t if the current tree-sitter grammar nests the else if -statements, nil otherwise.") - -(defun c-ts-common-statement-offset (node parent bol &rest _) +(defun c-ts-common-statement-offset (node parent &rest _) "This anchor is used for children of a statement inside a block. This function basically counts the number of block nodes (i.e., @@ -311,10 +330,7 @@ characters on the current line." ;; If NODE is a opening/closing bracket on its own line, take off ;; one level because the code below assumes NODE is a statement ;; _inside_ a {} block. - (when (and node - (or (string-match-p c-ts-common-indent-block-type-regexp - (treesit-node-type node)) - (save-excursion (goto-char bol) (looking-at-p "}")))) + (when (c-ts-common--node-is node 'block 'close-bracket) (cl-decf level)) ;; If point is on an empty line, NODE would be nil, but we pretend ;; there is a statement node. @@ -324,69 +340,35 @@ characters on the current line." (while (if (eq node t) (setq node parent) node) - ;; Subtract one indent level if the language nests - ;; if-statements and node is if_statement. - (setq level (c-ts-common--fix-nestable-if-statement level node)) - (when (string-match-p c-ts-common-indent-block-type-regexp - (treesit-node-type node)) - (cl-incf level) - (save-excursion - (goto-char (treesit-node-start node)) - ;; Add an extra level if the opening bracket is on its own - ;; line, except (1) it's at top-level, or (2) it's immediate - ;; parent is another block. - (cond ((bolp) nil) ; Case (1). - ((let ((parent-type (treesit-node-type - (treesit-node-parent node)))) - ;; Case (2). - (and parent-type - (string-match-p - c-ts-common-indent-block-type-regexp - parent-type))) - nil) - ;; Add a level. - ((looking-back (rx bol (* whitespace)) - (line-beginning-position)) - (cl-incf level))))) - (setq level (c-ts-mode--fix-bracketless-indent level node)) + (let ((parent (treesit-node-parent node))) + ;; Increment level for every bracket (with exception). + (when (c-ts-common--node-is node 'block) + (cl-incf level) + (save-excursion + (goto-char (treesit-node-start node)) + ;; Add an extra level if the opening bracket is on its own + ;; line, except (1) it's at top-level, or (2) it's immediate + ;; parent is another block. + (cond ((bolp) nil) ; Case (1). + ((c-ts-common--node-is parent 'block) ; Case (2). + nil) + ;; Add a level. + ((looking-back (rx bol (* whitespace)) + (line-beginning-position)) + (cl-incf level))))) + ;; Fix bracketless statements. + (when (and (c-ts-common--node-is parent + 'if 'do 'while 'for) + (not (c-ts-common--node-is node 'block))) + (cl-incf level)) + ;; Flatten "else if" statements. + (when (and (c-ts-common--node-is node 'else) + (c-ts-common--node-is node 'if)) + (cl-decf level))) ;; Go up the tree. (setq node (treesit-node-parent node))) (* level (symbol-value c-ts-common-indent-offset)))) -(defun c-ts-mode--fix-bracketless-indent (level node) - "Takes LEVEL and NODE and return adjusted LEVEL. -This fixes indentation for cases shown in bug#61026. Basically -in C-like syntax, statements like if, for, while sometimes omit -the bracket in the body." - (let ((block-re c-ts-common-indent-block-type-regexp) - (statement-re - c-ts-common-indent-bracketless-type-regexp) - (node-type (treesit-node-type node)) - (parent-type (treesit-node-type (treesit-node-parent node)))) - (if (and block-re statement-re node-type parent-type - (not (string-match-p block-re node-type)) - (string-match-p statement-re parent-type)) - (1+ level) - level))) - -(defun c-ts-common--fix-nestable-if-statement (level node) - "Takes LEVEL and NODE and return adjusted LEVEL. -Look at the type of NODE, when it is an if-statement node, as -defined by `c-ts-common-if-statement-regexp' and its parent is -also an if-statement node, subtract one level. Otherwise return -the value unchanged. Whether or not if-statements are nestable -is controlled by `c-ts-common-nestable-if-statement-p'." - ;; This fixes indentation for cases shown in bug#61142. - (or (and node - (equal (treesit-node-type (treesit-node-prev-sibling node)) "else") - (treesit-node-parent node) - c-ts-common-nestable-if-statement-p - (equal (treesit-node-type node) c-ts-common-if-statement-regexp) - (equal (treesit-node-type (treesit-node-parent node)) - c-ts-common-if-statement-regexp) - (cl-decf level)) - level)) - (provide 'c-ts-common) ;;; c-ts-common.el ends here diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index f2d5a482009..b898f7d9ee3 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -765,14 +765,16 @@ the semicolon. This function skips the semicolon." (when (eq c-ts-mode-indent-style 'linux) (setq-local indent-tabs-mode t)) (setq-local c-ts-common-indent-offset 'c-ts-mode-indent-offset) - (setq-local c-ts-common-indent-block-type-regexp - (rx (or "compound_statement" - "field_declaration_list" - "enumerator_list"))) - (setq-local c-ts-common-indent-bracketless-type-regexp - (rx (or "if_statement" "do_statement" - "for_statement" "while_statement"))) - + (setq-local c-ts-common-indent-type-regexp-alist + `((block . ,(rx (or "compound_statement" + "field_declaration_list" + "enumerator_list"))) + (if . "if_statement") + (else . ("if_statement" . "alternative")) + (do . "do_statement") + (while . "while_statement") + (for . "for_statement") + (close-bracket . "}"))) ;; Comment (c-ts-common-comment-setup) diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent.erts b/test/lisp/progmodes/c-ts-mode-resources/indent.erts index 8c588f56f9a..21b84c2e7e3 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent.erts @@ -169,6 +169,19 @@ do while (true) =-=-= +Name: Nested If-Else + +=-= +if (true) + return 0; +else if (false) + return 1; +else if (true) + return 2; +else if (false) + return 3; +=-=-= + Name: Multiline Block Comments 1 (bug#60270) =-=