diff options
Diffstat (limited to 'lisp/progmodes/c-ts-common.el')
-rw-r--r-- | lisp/progmodes/c-ts-common.el | 145 |
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 |