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
3 changed files with 88 additions and 91 deletions
|
@ -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'
|
particular major mode. This cannot be nil for `c-ts-common'
|
||||||
statement indent functions to work.")
|
statement indent functions to work.")
|
||||||
|
|
||||||
(defvar c-ts-common-indent-block-type-regexp nil
|
(defvar c-ts-common-indent-type-regexp-alist nil
|
||||||
"Regexp matching types of block nodes (i.e., {} blocks).
|
"An alist of of node type regexps.
|
||||||
|
|
||||||
This cannot be nil for `c-ts-common' statement indent functions
|
Each key in the alist is one of `if', `else', `do', `while',
|
||||||
to work.")
|
`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
|
Some types, specifically `else', is usually not identified by a
|
||||||
"A regexp matching types of bracketless constructs.
|
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
|
If the language doesn't have a particular type, it is fine to
|
||||||
these statements, the body can omit the bracket, which requires
|
omit it.")
|
||||||
special handling from our bracket-counting indent algorithm.
|
|
||||||
|
|
||||||
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"
|
TYPES can be any of `if', `else', `while', `do', `for', and
|
||||||
"Regexp used to select an if statement in a C like language.
|
`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
|
(defun c-ts-common-statement-offset (node parent &rest _)
|
||||||
"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 _)
|
|
||||||
"This anchor is used for children of a statement inside a block.
|
"This anchor is used for children of a statement inside a block.
|
||||||
|
|
||||||
This function basically counts the number of block nodes (i.e.,
|
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
|
;; If NODE is a opening/closing bracket on its own line, take off
|
||||||
;; one level because the code below assumes NODE is a statement
|
;; one level because the code below assumes NODE is a statement
|
||||||
;; _inside_ a {} block.
|
;; _inside_ a {} block.
|
||||||
(when (and node
|
(when (c-ts-common--node-is node 'block 'close-bracket)
|
||||||
(or (string-match-p c-ts-common-indent-block-type-regexp
|
|
||||||
(treesit-node-type node))
|
|
||||||
(save-excursion (goto-char bol) (looking-at-p "}"))))
|
|
||||||
(cl-decf level))
|
(cl-decf level))
|
||||||
;; If point is on an empty line, NODE would be nil, but we pretend
|
;; If point is on an empty line, NODE would be nil, but we pretend
|
||||||
;; there is a statement node.
|
;; there is a statement node.
|
||||||
|
@ -324,69 +340,35 @@ characters on the current line."
|
||||||
(while (if (eq node t)
|
(while (if (eq node t)
|
||||||
(setq node parent)
|
(setq node parent)
|
||||||
node)
|
node)
|
||||||
;; Subtract one indent level if the language nests
|
(let ((parent (treesit-node-parent node)))
|
||||||
;; if-statements and node is if_statement.
|
;; Increment level for every bracket (with exception).
|
||||||
(setq level (c-ts-common--fix-nestable-if-statement level node))
|
(when (c-ts-common--node-is node 'block)
|
||||||
(when (string-match-p c-ts-common-indent-block-type-regexp
|
(cl-incf level)
|
||||||
(treesit-node-type node))
|
(save-excursion
|
||||||
(cl-incf level)
|
(goto-char (treesit-node-start node))
|
||||||
(save-excursion
|
;; Add an extra level if the opening bracket is on its own
|
||||||
(goto-char (treesit-node-start node))
|
;; line, except (1) it's at top-level, or (2) it's immediate
|
||||||
;; Add an extra level if the opening bracket is on its own
|
;; parent is another block.
|
||||||
;; line, except (1) it's at top-level, or (2) it's immediate
|
(cond ((bolp) nil) ; Case (1).
|
||||||
;; parent is another block.
|
((c-ts-common--node-is parent 'block) ; Case (2).
|
||||||
(cond ((bolp) nil) ; Case (1).
|
nil)
|
||||||
((let ((parent-type (treesit-node-type
|
;; Add a level.
|
||||||
(treesit-node-parent node))))
|
((looking-back (rx bol (* whitespace))
|
||||||
;; Case (2).
|
(line-beginning-position))
|
||||||
(and parent-type
|
(cl-incf level)))))
|
||||||
(string-match-p
|
;; Fix bracketless statements.
|
||||||
c-ts-common-indent-block-type-regexp
|
(when (and (c-ts-common--node-is parent
|
||||||
parent-type)))
|
'if 'do 'while 'for)
|
||||||
nil)
|
(not (c-ts-common--node-is node 'block)))
|
||||||
;; Add a level.
|
(cl-incf level))
|
||||||
((looking-back (rx bol (* whitespace))
|
;; Flatten "else if" statements.
|
||||||
(line-beginning-position))
|
(when (and (c-ts-common--node-is node 'else)
|
||||||
(cl-incf level)))))
|
(c-ts-common--node-is node 'if))
|
||||||
(setq level (c-ts-mode--fix-bracketless-indent level node))
|
(cl-decf level)))
|
||||||
;; Go up the tree.
|
;; Go up the tree.
|
||||||
(setq node (treesit-node-parent node)))
|
(setq node (treesit-node-parent node)))
|
||||||
(* level (symbol-value c-ts-common-indent-offset))))
|
(* 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)
|
(provide 'c-ts-common)
|
||||||
|
|
||||||
;;; c-ts-common.el ends here
|
;;; 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)
|
(when (eq c-ts-mode-indent-style 'linux)
|
||||||
(setq-local indent-tabs-mode t))
|
(setq-local indent-tabs-mode t))
|
||||||
(setq-local c-ts-common-indent-offset 'c-ts-mode-indent-offset)
|
(setq-local c-ts-common-indent-offset 'c-ts-mode-indent-offset)
|
||||||
(setq-local c-ts-common-indent-block-type-regexp
|
(setq-local c-ts-common-indent-type-regexp-alist
|
||||||
(rx (or "compound_statement"
|
`((block . ,(rx (or "compound_statement"
|
||||||
"field_declaration_list"
|
"field_declaration_list"
|
||||||
"enumerator_list")))
|
"enumerator_list")))
|
||||||
(setq-local c-ts-common-indent-bracketless-type-regexp
|
(if . "if_statement")
|
||||||
(rx (or "if_statement" "do_statement"
|
(else . ("if_statement" . "alternative"))
|
||||||
"for_statement" "while_statement")))
|
(do . "do_statement")
|
||||||
|
(while . "while_statement")
|
||||||
|
(for . "for_statement")
|
||||||
|
(close-bracket . "}")))
|
||||||
;; Comment
|
;; Comment
|
||||||
(c-ts-common-comment-setup)
|
(c-ts-common-comment-setup)
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,19 @@ do
|
||||||
while (true)
|
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)
|
Name: Multiline Block Comments 1 (bug#60270)
|
||||||
|
|
||||||
=-=
|
=-=
|
||||||
|
|
Loading…
Add table
Reference in a new issue