summaryrefslogtreecommitdiff
path: root/lisp/progmodes/c-ts-common.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/progmodes/c-ts-common.el')
-rw-r--r--lisp/progmodes/c-ts-common.el145
1 files changed, 142 insertions, 3 deletions
diff --git a/lisp/progmodes/c-ts-common.el b/lisp/progmodes/c-ts-common.el
index 6671d4be5b6..85db39aaeae 100644
--- a/lisp/progmodes/c-ts-common.el
+++ b/lisp/progmodes/c-ts-common.el
@@ -2,7 +2,7 @@
;; Copyright (C) 2023 Free Software Foundation, Inc.
-;; Author : 付禹安 (Yuan Fu) <casouri@gmail.com>
+;; Maintainer : 付禹安 (Yuan Fu) <casouri@gmail.com>
;; Keywords : c c++ java javascript rust languages tree-sitter
;; This file is part of GNU Emacs.
@@ -22,7 +22,10 @@
;;; Commentary:
;;
-;; For C-like language major modes:
+;; This file contains functions that can be shared by C-like language
+;; major modes, like indenting and filling "/* */" block comments.
+;;
+;; For indenting and filling comments:
;;
;; - Use `c-ts-common-comment-setup' to setup comment variables and
;; filling.
@@ -30,6 +33,14 @@
;; - Use simple-indent matcher `c-ts-common-looking-at-star' and
;; anchor `c-ts-common-comment-start-after-first-star' for indenting
;; block comments. See `c-ts-mode--indent-styles' for example.
+;;
+;; For indenting statements:
+;;
+;; - Set `c-ts-common-indent-offset',
+;; `c-ts-common-indent-block-type-regexp', and
+;; `c-ts-common-indent-bracketless-type-regexp', then use simple-indent
+;; offset `c-ts-common-statement-offset' in
+;; `treesit-simple-indent-rules'.
;;; Code:
@@ -39,6 +50,10 @@
(declare-function treesit-node-start "treesit.c")
(declare-function treesit-node-end "treesit.c")
(declare-function treesit-node-type "treesit.c")
+(declare-function treesit-node-parent "treesit.c")
+(declare-function treesit-node-prev-sibling "treesit.c")
+
+;;; Comment indentation and filling
(defun c-ts-common-looking-at-star (_n _p bol &rest _)
"A tree-sitter simple indent matcher.
@@ -180,7 +195,8 @@ comment."
(when end-marker
(goto-char end-marker)
(delete-region (point) (+ end-len (point)))
- (insert (make-string end-len ?\s))))))
+ (insert (make-string end-len ?\s)))
+ (goto-char orig-point))))
(defun c-ts-common-comment-setup ()
"Set up local variables for C-like comment.
@@ -242,6 +258,129 @@ Set up:
(setq-local paragraph-separate paragraph-start)
(setq-local fill-paragraph-function #'c-ts-common--fill-paragraph))
+;;; Statement indent
+
+(defvar c-ts-common-indent-offset nil
+ "Indent offset used by `c-ts-common' indent functions.
+
+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-type-regexp-alist nil
+ "An alist of node type regexps.
+
+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.
+
+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.
+
+If the language doesn't have a particular type, it is fine to
+omit it.")
+
+(defun c-ts-common--node-is (node &rest types)
+ "Return non-nil if NODE is any one of the TYPES.
+
+TYPES can be any of `if', `else', `while', `do', `for', and
+`block'.
+
+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))
+ (treesit-node-field-name node)
+ (string-match-p (cdr regexp)
+ (treesit-node-field-name
+ node)))
+ (string-match-p regexp (treesit-node-type node))))
+ (throw 'ret t))))
+ nil))
+
+(defun c-ts-common-statement-offset (node parent &rest _)
+ "Return an indent offset for a statement inside a block.
+
+Assumes the anchor is (point-min), i.e., the 0th column.
+
+This function basically counts the number of block nodes (i.e.,
+brackets) (defined by `c-ts-common-indent-block-type-regexp')
+between NODE and the root node (not counting NODE itself), and
+multiply that by `c-ts-common-indent-offset'.
+
+To support GNU style, on each block level, this function also
+checks whether the opening bracket { is on its own line, if so,
+it adds an extra level, except for the top-level.
+
+It also has special handling for bracketless statements and
+else-if statements, which see.
+
+PARENT is NODE's parent, BOL is the beginning of non-whitespace
+characters on the current line."
+ (let ((level 0))
+ ;; 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 (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.
+ (when (null node)
+ (setq node t))
+ ;; Go up the tree and compute indent level.
+ (while (if (eq node t)
+ (setq node parent)
+ 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)
+ ;; But if the "if" is on it's own line, still
+ ;; indent a level.
+ (not (save-excursion
+ (goto-char (treesit-node-start node))
+ (looking-back (rx bol (* whitespace))
+ (line-beginning-position)))))
+ (cl-decf level)))
+ ;; Go up the tree.
+ (setq node (treesit-node-parent node)))
+ (* level (symbol-value c-ts-common-indent-offset))))
+
(provide 'c-ts-common)
;;; c-ts-common.el ends here