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:
parent
7cb92b5398
commit
87d39a30b1
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
=-=
|
||||
|
Loading…
Reference in New Issue
Block a user