diff options
Diffstat (limited to 'lisp/progmodes/c-ts-mode.el')
-rw-r--r-- | lisp/progmodes/c-ts-mode.el | 203 |
1 files changed, 166 insertions, 37 deletions
diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 1c55c7fbdde..e93a0fec707 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -79,6 +79,7 @@ (declare-function treesit-node-type "treesit.c") (declare-function treesit-node-prev-sibling "treesit.c") (declare-function treesit-node-first-child-for-pos "treesit.c") +(declare-function treesit-node-next-sibling "treesit.c") ;;; Custom variables @@ -192,6 +193,10 @@ To set the default indent style globally, use (c-ts-mode--get-indent-style (if (derived-mode-p 'c-ts-mode) 'c 'cpp)))))) +(defvar c-ts-mode-emacs-devel nil + "If the value is t, enable Emacs source-specific features. +This needs to be set before enabling `c-ts-mode'.") + ;;; Syntax table (defvar c-ts-mode--syntax-table @@ -322,7 +327,7 @@ PARENT is the same as other anchor functions." ;; nil. parent (lambda (node) (and node - (not (string-match "preproc" (treesit-node-type node))) + (not (string-search "preproc" (treesit-node-type node))) (progn (goto-char (treesit-node-start node)) (looking-back (rx bol (* whitespace)) @@ -386,7 +391,7 @@ MODE is either `c' or `cpp'." ((parent-is "function_definition") parent-bol 0) ((parent-is "conditional_expression") first-sibling 0) ((parent-is "assignment_expression") parent-bol c-ts-mode-indent-offset) - ((parent-is "concatenated_string") parent-bol c-ts-mode-indent-offset) + ((parent-is "concatenated_string") first-sibling 0) ((parent-is "comma_expression") first-sibling 0) ((parent-is "init_declarator") parent-bol c-ts-mode-indent-offset) ((parent-is "parenthesized_expression") first-sibling 1) @@ -434,6 +439,8 @@ MODE is either `c' or `cpp'." ((parent-is "while_statement") standalone-parent c-ts-mode-indent-offset) ((parent-is "do_statement") standalone-parent c-ts-mode-indent-offset) + ((parent-is "case_statement") standalone-parent c-ts-mode-indent-offset) + ,@(when (eq mode 'cpp) `(((node-is "field_initializer_list") parent-bol ,(* c-ts-mode-indent-offset 2))))))) `((gnu @@ -800,7 +807,14 @@ Return nil if NODE is not a defun node or doesn't have a name." ((or "struct_specifier" "enum_specifier" "union_specifier" "class_specifier" "namespace_definition") - (treesit-node-child-by-field-name node "name"))) + (treesit-node-child-by-field-name node "name")) + ;; DEFUNs in Emacs source. + ("expression_statement" + (let* ((call-exp-1 (treesit-node-child node 0)) + (call-exp-2 (treesit-node-child call-exp-1 0)) + (arg-list (treesit-node-child call-exp-2 1)) + (name (treesit-node-child arg-list 1 t))) + name))) t)) ;;; Defun navigation @@ -808,28 +822,29 @@ Return nil if NODE is not a defun node or doesn't have a name." (defun c-ts-mode--defun-valid-p (node) "Return non-nil if NODE is a valid defun node. Ie, NODE is not nested." - (not (or (and (member (treesit-node-type node) - '("struct_specifier" - "enum_specifier" - "union_specifier" - "declaration")) - ;; If NODE's type is one of the above, make sure it is - ;; top-level. - (treesit-node-top-level - node (rx (or "function_definition" - "type_definition" - "struct_specifier" + (or (c-ts-mode--emacs-defun-p node) + (not (or (and (member (treesit-node-type node) + '("struct_specifier" "enum_specifier" "union_specifier" - "declaration")))) + "declaration")) + ;; If NODE's type is one of the above, make sure it is + ;; top-level. + (treesit-node-top-level + node (rx (or "function_definition" + "type_definition" + "struct_specifier" + "enum_specifier" + "union_specifier" + "declaration")))) - (and (equal (treesit-node-type node) "declaration") - ;; If NODE is a declaration, make sure it is not a - ;; function declaration. - (equal (treesit-node-type - (treesit-node-child-by-field-name - node "declarator")) - "function_declarator"))))) + (and (equal (treesit-node-type node) "declaration") + ;; If NODE is a declaration, make sure it is not a + ;; function declaration. + (equal (treesit-node-type + (treesit-node-child-by-field-name + node "declarator")) + "function_declarator")))))) (defun c-ts-mode--defun-for-class-in-imenu-p (node) "Check if NODE is a valid entry for the Class subindex. @@ -857,17 +872,85 @@ the semicolon. This function skips the semicolon." (goto-char (match-end 0))) (treesit-default-defun-skipper)) +(defun c-ts-base--before-indent (args) + (pcase-let ((`(,node ,parent ,bol) args)) + (when (null node) + (let ((smallest-node (treesit-node-at (point)))) + ;; "Virtual" closer curly added by the + ;; parser's error recovery. + (when (and (equal (treesit-node-type smallest-node) "}") + (equal (treesit-node-end smallest-node) + (treesit-node-start smallest-node))) + (setq parent (treesit-node-parent smallest-node))))) + (list node parent bol))) + +(defun c-ts-mode--emacs-defun-p (node) + "Return non-nil if NODE is a DEFUN in Emacs source files." + (and (equal (treesit-node-type node) "expression_statement") + (equal (treesit-node-text + (treesit-node-child-by-field-name + (treesit-node-child + (treesit-node-child node 0) 0) + "function") + t) + "DEFUN"))) + +(defun c-ts-mode--emacs-defun-at-point (&optional range) + "Return the current defun node. + +This function recognizes DEFUNs in Emacs source files. + +Note that for the case of a DEFUN, it is made of two separate +nodes, one for the declaration and one for the body, this +function returns the declaration node. + +If RANGE is non-nil, return (BEG . END) where BEG end END +encloses the whole defun. This solves the problem of only +returning the declaration part for DEFUN." + (or (when-let ((node (treesit-defun-at-point))) + (if range + (cons (treesit-node-start node) + (treesit-node-end node)) + node)) + (and c-ts-mode-emacs-devel + (let ((candidate-1 ; For when point is in the DEFUN statement. + (treesit-node-prev-sibling + (treesit-node-top-level + (treesit-node-at (point)) + "compound_statement"))) + (candidate-2 ; For when point is in the body. + (treesit-node-top-level + (treesit-node-at (point)) + "expression_statement"))) + (when-let + ((node (or (and (c-ts-mode--emacs-defun-p candidate-1) + candidate-1) + (and (c-ts-mode--emacs-defun-p candidate-2) + candidate-2)))) + (if range + (cons (treesit-node-start node) + (treesit-node-end + (treesit-node-next-sibling node))) + node)))))) + (defun c-ts-mode-indent-defun () "Indent the current top-level declaration syntactically. `treesit-defun-type-regexp' defines what constructs to indent." (interactive "*") (when-let ((orig-point (point-marker)) - (node (treesit-defun-at-point))) - (indent-region (treesit-node-start node) - (treesit-node-end node)) + (range (c-ts-mode--emacs-defun-at-point t))) + (indent-region (car range) (cdr range)) (goto-char orig-point))) +(defun c-ts-mode--emacs-current-defun-name () + "Return the name of the current defun. +This is used for `add-log-current-defun-function'. This +recognizes DEFUN in Emacs sources, in addition to normal function +definitions." + (or (treesit-add-log-current-defun) + (c-ts-mode--defun-name (c-ts-mode--emacs-defun-at-point)))) + ;;; Modes (defvar-keymap c-ts-base-mode-map @@ -919,20 +1002,23 @@ the semicolon. This function skips the semicolon." "goto_statement" "case_statement"))) + ;; IMO it makes more sense to define what's NOT sexp, since sexp by + ;; spirit, especially when used for movement, is like "expression" + ;; or "syntax unit". --yuan (setq-local treesit-sexp-type-regexp - (regexp-opt '("preproc" - "declarator" - "qualifier" - "type" - "parameter" - "expression" - "literal" - "string"))) + ;; It more useful to include semicolons as sexp so that + ;; users can move to the end of a statement. + (rx (not (or "{" "}" "[" "]" "(" ")" ",")))) ;; Nodes like struct/enum/union_specifier can appear in ;; function_definitions, so we need to find the top-level node. (setq-local treesit-defun-prefer-top-level t) + ;; When the code is in incomplete state, try to make a better guess + ;; about which node to indent against. + (add-function :filter-args (local 'treesit-indent-function) + #'c-ts-base--before-indent) + ;; Indent. (when (eq c-ts-mode-indent-style 'linux) (setq-local indent-tabs-mode t)) @@ -1008,7 +1094,11 @@ in your configuration." (setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'c)) ;; Navigation. (setq-local treesit-defun-tactic 'top-level) - (treesit-major-mode-setup))) + (treesit-major-mode-setup) + + (when c-ts-mode-emacs-devel + (setq-local add-log-current-defun-function + #'c-ts-mode--emacs-current-defun-name)))) ;;;###autoload (define-derived-mode c++-ts-mode c-ts-base-mode "C++" @@ -1025,7 +1115,11 @@ To use tree-sitter C/C++ modes by default, evaluate (add-to-list \\='major-mode-remap-alist \\='(c-or-c++-mode . c-or-c++-ts-mode)) -in your configuration." +in your configuration. + +Since this mode uses a parser, unbalanced brackets might cause +some breakage in indentation/fontification. Therefore, it's +recommended to enable `electric-pair-mode' with this mode." :group 'c++ :after-hook (c-ts-mode-set-modeline) @@ -1046,8 +1140,43 @@ in your configuration." ;; Font-lock. (setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'cpp)) - - (treesit-major-mode-setup))) + (treesit-major-mode-setup) + (when c-ts-mode-emacs-devel + (setq-local add-log-current-defun-function + #'c-ts-mode--emacs-current-defun-name)))) + +(easy-menu-define c-ts-mode-menu (list c-ts-mode-map c++-ts-mode-map) + "Menu for `c-ts-mode' and `c++-ts-mode'." + '("C/C++" + ["Comment Out Region" comment-region + :enable mark-active + :help "Comment out the region between the mark and point"] + ["Uncomment Region" (comment-region (region-beginning) + (region-end) '(4)) + :enable mark-active + :help "Uncomment the region between the mark and point"] + ["Indent Top-level Expression" c-ts-mode-indent-defun + :help "Indent/reindent top-level function, class, etc."] + ["Indent Line or Region" indent-for-tab-command + :help "Indent current line or region, or insert a tab"] + ["Forward Expression" forward-sexp + :help "Move forward across one balanced expression"] + ["Backward Expression" backward-sexp + :help "Move back across one balanced expression"] + "--" + ("Style..." + ["Set Indentation Style..." c-ts-mode-set-style + :help "Set C/C++ indentation style for current buffer"] + ["Show Current Indentation Style" (message "Indentation Style: %s" + c-ts-mode-indent-style) + :help "Show the name of the C/C++ indentation style for current buffer"] + ["Set Comment Style" c-ts-mode-toggle-comment-style + :help "Toglle C/C++ comment style between block and line comments"]) + "--" + ("Toggle..." + ["SubWord Mode" subword-mode + :style toggle :selected subword-mode + :help "Toggle sub-word movement and editing mode"]))) ;; We could alternatively use parsers, but if this works well, I don't ;; see the need to change. This is copied verbatim from cc-guess.el. |