1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-23 07:19:15 +00:00

Fix c-ts-mode indentation

Sign, ok, there's another edge case: else if statements.  Because
"else if" is usually implemented as just another if statement nested
in the else branch, this creates additional levels that indentation
needs to ignore.

I converted c-ts-common-indent-block-type-regexp +
c-ts-common-indent-bracketless-type-regexp into a new, more flexible
variable, c-ts-common-indent-type-regexp-alist, to avoid adding yet
more variables in order to recognize else and if statements.

* lisp/progmodes/c-ts-common.el:
(c-ts-common-indent-type-regexp-alist): New variable.
(c-ts-common-indent-block-type-regexp)
(c-ts-common-indent-bracketless-type-regexp): Remove variables.
(c-ts-common--node-is): New function.
(c-ts-common-statement-offset): Use the new variable, and add the
"else if" special case.  Also merge the code of
c-ts-mode--fix-bracketless-indent, because now the code is much more
succinct.
(c-ts-mode--fix-bracketless-indent): Merge into
c-ts-common-statement-offset.

* lisp/progmodes/c-ts-mode.el:
(c-ts-base-mode): Setup c-ts-common-indent-type-regexp-alist.

* test/lisp/progmodes/c-ts-mode-resources/indent.erts: New test.
This commit is contained in:
Yuan Fu 2023-02-05 19:32:24 -08:00
parent 7cb92b5398
commit 87d39a30b1
No known key found for this signature in database
GPG Key ID: 56E19BC57664A442
3 changed files with 88 additions and 91 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
=-=