2022-11-12 14:33:14 -08:00
|
|
|
;;; c-ts-mode.el --- tree-sitter support for C and C++ -*- lexical-binding: t; -*-
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2023-01-01 05:31:12 -05:00
|
|
|
;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;; Author : Theodor Thornhill <theo@thornhill.no>
|
|
|
|
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
|
|
|
|
;; Created : November 2022
|
|
|
|
;; Keywords : c c++ cpp languages tree-sitter
|
|
|
|
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
2022-12-08 23:56:24 +01:00
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
2022-11-10 17:15:49 +01:00
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
2022-12-08 23:56:24 +01:00
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
2022-11-10 17:15:49 +01:00
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
2022-12-08 23:56:24 +01:00
|
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
;;
|
2023-01-18 15:32:12 -08:00
|
|
|
;; This package provides major modes for C and C++, plus some handy
|
|
|
|
;; functions that are useful generally to major modes for C-like
|
|
|
|
;; languages.
|
|
|
|
;;
|
|
|
|
;; This package provides `c-ts-mode' for C, `c++-ts-mode' for C++, and
|
|
|
|
;; `c-or-c++-ts-mode' which automatically chooses the right mode for
|
|
|
|
;; C/C++ header files.
|
|
|
|
;;
|
2023-01-20 10:28:26 +02:00
|
|
|
;; To use these modes by default, assuming you have the respective
|
|
|
|
;; tree-sitter grammars available, do one of the following:
|
2023-01-18 15:32:12 -08:00
|
|
|
;;
|
2023-01-20 10:28:26 +02:00
|
|
|
;; - If you have both C and C++ grammars installed, add
|
2023-01-18 15:32:12 -08:00
|
|
|
;;
|
2023-01-20 10:28:26 +02:00
|
|
|
;; (require 'c-ts-mode)
|
|
|
|
;;
|
|
|
|
;; to your init file.
|
|
|
|
;;
|
|
|
|
;; - Add one or mode of the following to your init file:
|
|
|
|
;;
|
|
|
|
;; (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))
|
|
|
|
;; (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode))
|
|
|
|
;; (add-to-list 'major-mode-remap-alist '(c-or-c++-mode . c-or-c++-ts-mode))
|
|
|
|
;;
|
|
|
|
;; If you have only C grammar available, use only the first one; if
|
|
|
|
;; you have only the C++ grammar, use only the second one.
|
|
|
|
;;
|
|
|
|
;; - Customize 'auto-mode-alist' to turn one or more of the modes
|
|
|
|
;; automatically. For example:
|
|
|
|
;;
|
|
|
|
;; (add-to-list 'auto-mode-alist
|
|
|
|
;; '("\\(\\.ii\\|\\.\\(CC?\\|HH?\\)\\|\\.[ch]\\(pp\\|xx\\|\\+\\+\\)\\|\\.\\(cc\\|hh\\)\\)\\'"
|
|
|
|
;; . c++-ts-mode))
|
|
|
|
;;
|
|
|
|
;; will turn on the c++-ts-mode for C++ source files.
|
|
|
|
;;
|
|
|
|
;; You can also turn on these modes manually in a buffer. Doing so
|
|
|
|
;; will set up Emacs to use the C/C++ modes defined here for other
|
|
|
|
;; files, provided that you have the corresponding parser grammar
|
|
|
|
;; libraries installed.
|
2023-01-18 15:32:12 -08:00
|
|
|
;;
|
|
|
|
;; - Use variable `c-ts-mode-indent-block-type-regexp' with indent
|
|
|
|
;; offset c-ts-mode--statement-offset for indenting statements.
|
|
|
|
;; Again, see `c-ts-mode--indent-styles' for example.
|
2023-01-21 12:24:55 +01:00
|
|
|
;;
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 'treesit)
|
2023-01-21 12:24:55 +01:00
|
|
|
(require 'c-ts-common)
|
2022-11-27 14:15:57 -08:00
|
|
|
(eval-when-compile (require 'rx))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2022-11-21 19:08:25 +02:00
|
|
|
(declare-function treesit-parser-create "treesit.c")
|
|
|
|
(declare-function treesit-node-parent "treesit.c")
|
|
|
|
(declare-function treesit-node-start "treesit.c")
|
|
|
|
(declare-function treesit-node-end "treesit.c")
|
|
|
|
(declare-function treesit-node-child "treesit.c")
|
|
|
|
(declare-function treesit-node-child-by-field-name "treesit.c")
|
|
|
|
(declare-function treesit-node-type "treesit.c")
|
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Custom variables
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defcustom c-ts-mode-indent-offset 2
|
|
|
|
"Number of spaces for each indentation step in `c-ts-mode'."
|
2022-11-14 08:03:11 +01:00
|
|
|
:version "29.1"
|
2022-11-10 17:15:49 +01:00
|
|
|
:type 'integer
|
|
|
|
:safe 'integerp
|
|
|
|
:group 'c)
|
|
|
|
|
2023-01-25 21:04:00 +01:00
|
|
|
(defun c-ts-mode--indent-style-setter (sym val)
|
2023-01-28 16:20:29 -08:00
|
|
|
"Custom setter for `c-ts-mode-set-style'.
|
|
|
|
Apart from setting the default value of SYM to VAL, also change
|
|
|
|
the value of SYM in `c-ts-mode' and `c++-ts-mode' buffers to VAL."
|
2023-01-25 21:04:00 +01:00
|
|
|
(set-default sym val)
|
|
|
|
(named-let loop ((res nil)
|
|
|
|
(buffers (buffer-list)))
|
|
|
|
(if (null buffers)
|
|
|
|
(mapc (lambda (b)
|
|
|
|
(with-current-buffer b
|
|
|
|
(setq-local treesit-simple-indent-rules
|
|
|
|
(treesit--indent-rules-optimize
|
|
|
|
(c-ts-mode--get-indent-style
|
|
|
|
(if (eq major-mode 'c-ts-mode) 'c 'cpp))))))
|
|
|
|
res)
|
|
|
|
(let ((buffer (car buffers)))
|
|
|
|
(with-current-buffer buffer
|
2023-01-28 16:20:29 -08:00
|
|
|
;; FIXME: Should we use `derived-mode-p' here?
|
2023-01-25 21:04:00 +01:00
|
|
|
(if (or (eq major-mode 'c-ts-mode) (eq major-mode 'c++-ts-mode))
|
|
|
|
(loop (append res (list buffer)) (cdr buffers))
|
|
|
|
(loop res (cdr buffers))))))))
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defcustom c-ts-mode-indent-style 'gnu
|
|
|
|
"Style used for indentation.
|
|
|
|
|
|
|
|
The selected style could be one of GNU, K&R, LINUX or BSD. If
|
|
|
|
one of the supplied styles doesn't suffice a function could be
|
|
|
|
set instead. This function is expected return a list that
|
|
|
|
follows the form of `treesit-simple-indent-rules'."
|
2022-11-14 08:03:11 +01:00
|
|
|
:version "29.1"
|
2023-01-25 21:04:00 +01:00
|
|
|
:type '(choice (symbol :tag "Gnu" gnu)
|
|
|
|
(symbol :tag "K&R" k&r)
|
|
|
|
(symbol :tag "Linux" linux)
|
|
|
|
(symbol :tag "BSD" bsd)
|
2022-11-10 17:15:49 +01:00
|
|
|
(function :tag "A function for user customized style" ignore))
|
2023-01-25 21:04:00 +01:00
|
|
|
:set #'c-ts-mode--indent-style-setter
|
2022-11-10 17:15:49 +01:00
|
|
|
:group 'c)
|
|
|
|
|
2023-01-28 16:25:23 -08:00
|
|
|
(defun c-ts-mode--get-indent-style (mode)
|
|
|
|
"Helper function to set indentation style.
|
|
|
|
MODE is either `c' or `cpp'."
|
|
|
|
(let ((style
|
|
|
|
(if (functionp c-ts-mode-indent-style)
|
|
|
|
(funcall c-ts-mode-indent-style)
|
|
|
|
(alist-get c-ts-mode-indent-style (c-ts-mode--indent-styles mode)))))
|
|
|
|
`((,mode ,@style))))
|
|
|
|
|
2023-01-25 21:04:00 +01:00
|
|
|
(defun c-ts-mode-set-style ()
|
2023-01-28 16:25:23 -08:00
|
|
|
"Set the indent style of C/C++ modes globally.
|
|
|
|
|
|
|
|
This changes the current indent style of every C/C++ buffer and
|
|
|
|
the default C/C++ indent style in this Emacs session."
|
2023-01-25 21:04:00 +01:00
|
|
|
(interactive)
|
2023-01-28 16:25:23 -08:00
|
|
|
;; FIXME: Should we use `derived-mode-p' here?
|
2023-01-25 21:04:00 +01:00
|
|
|
(or (eq major-mode 'c-ts-mode) (eq major-mode 'c++-ts-mode)
|
|
|
|
(error "Buffer %s is not a c-ts-mode (c-ts-mode-set-style)"
|
|
|
|
(buffer-name)))
|
|
|
|
(c-ts-mode--indent-style-setter
|
|
|
|
'c-ts-mode-indent-style
|
2023-01-28 16:25:23 -08:00
|
|
|
;; NOTE: We can probably use the interactive form for this.
|
2023-01-25 21:04:00 +01:00
|
|
|
(intern
|
|
|
|
(completing-read
|
|
|
|
"Select style: "
|
|
|
|
(mapcar #'car (c-ts-mode--indent-styles (if (eq major-mode 'c-ts-mode) 'c 'cpp)))
|
|
|
|
nil t nil nil "gnu"))))
|
|
|
|
|
2022-12-28 15:44:26 -08:00
|
|
|
;;; Syntax table
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defvar c-ts-mode--syntax-table
|
|
|
|
(let ((table (make-syntax-table)))
|
|
|
|
;; Taken from the cc-langs version
|
|
|
|
(modify-syntax-entry ?_ "_" table)
|
|
|
|
(modify-syntax-entry ?\\ "\\" table)
|
|
|
|
(modify-syntax-entry ?+ "." table)
|
|
|
|
(modify-syntax-entry ?- "." table)
|
|
|
|
(modify-syntax-entry ?= "." table)
|
|
|
|
(modify-syntax-entry ?% "." table)
|
|
|
|
(modify-syntax-entry ?< "." table)
|
|
|
|
(modify-syntax-entry ?> "." table)
|
|
|
|
(modify-syntax-entry ?& "." table)
|
|
|
|
(modify-syntax-entry ?| "." table)
|
|
|
|
(modify-syntax-entry ?\' "\"" table)
|
|
|
|
(modify-syntax-entry ?\240 "." table)
|
|
|
|
(modify-syntax-entry ?/ ". 124b" table)
|
|
|
|
(modify-syntax-entry ?* ". 23" table)
|
2022-12-05 13:37:58 +01:00
|
|
|
(modify-syntax-entry ?\n "> b" table)
|
|
|
|
(modify-syntax-entry ?\^m "> b" table)
|
2022-11-10 17:15:49 +01:00
|
|
|
table)
|
|
|
|
"Syntax table for `c-ts-mode'.")
|
|
|
|
|
2022-12-28 15:44:26 -08:00
|
|
|
(defun c-ts-mode--syntax-propertize (beg end)
|
|
|
|
"Apply syntax text property to template delimiters between BEG and END.
|
|
|
|
|
|
|
|
< and > are usually punctuation, e.g., in ->. But when used for
|
|
|
|
templates, they should be considered pairs.
|
|
|
|
|
|
|
|
This function checks for < and > in the changed RANGES and apply
|
|
|
|
appropriate text property to alter the syntax of template
|
|
|
|
delimiters < and >'s."
|
|
|
|
(goto-char beg)
|
|
|
|
(while (re-search-forward (rx (or "<" ">")) end t)
|
|
|
|
(pcase (treesit-node-type
|
|
|
|
(treesit-node-parent
|
|
|
|
(treesit-node-at (match-beginning 0))))
|
|
|
|
("template_argument_list"
|
|
|
|
(put-text-property (match-beginning 0)
|
|
|
|
(match-end 0)
|
|
|
|
'syntax-table
|
|
|
|
(pcase (char-before)
|
|
|
|
(?< '(4 . ?>))
|
|
|
|
(?> '(5 . ?<))))))))
|
2022-12-13 22:28:13 +01:00
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Indent
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defun c-ts-mode--indent-styles (mode)
|
|
|
|
"Indent rules supported by `c-ts-mode'.
|
|
|
|
MODE is either `c' or `cpp'."
|
|
|
|
(let ((common
|
2023-01-20 22:37:47 +01:00
|
|
|
`(((parent-is "translation_unit") point-min 0)
|
2022-11-10 17:15:49 +01:00
|
|
|
((node-is ")") parent 1)
|
|
|
|
((node-is "]") parent-bol 0)
|
|
|
|
((node-is "else") parent-bol 0)
|
|
|
|
((node-is "case") parent-bol 0)
|
|
|
|
((node-is "preproc_arg") no-indent)
|
2023-01-21 12:24:55 +01:00
|
|
|
;; `c-ts-common-looking-at-star' has to come before
|
|
|
|
;; `c-ts-common-comment-2nd-line-matcher'.
|
2023-01-22 10:58:31 +01:00
|
|
|
((and (parent-is "comment") c-ts-common-looking-at-star)
|
2023-01-21 12:24:55 +01:00
|
|
|
c-ts-common-comment-start-after-first-star -1)
|
|
|
|
(c-ts-common-comment-2nd-line-matcher
|
|
|
|
c-ts-common-comment-2nd-line-anchor
|
2023-01-09 01:44:44 -08:00
|
|
|
1)
|
2022-12-23 17:12:32 -08:00
|
|
|
((parent-is "comment") prev-adaptive-prefix 0)
|
2023-01-15 00:21:10 -08:00
|
|
|
|
|
|
|
;; Labels.
|
2022-11-10 17:15:49 +01:00
|
|
|
((node-is "labeled_statement") parent-bol 0)
|
2023-01-15 00:21:10 -08:00
|
|
|
((parent-is "labeled_statement")
|
|
|
|
point-min c-ts-mode--statement-offset)
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
((match "preproc_ifdef" "compound_statement") point-min 0)
|
|
|
|
((match "#endif" "preproc_ifdef") point-min 0)
|
|
|
|
((match "preproc_if" "compound_statement") point-min 0)
|
|
|
|
((match "#endif" "preproc_if") point-min 0)
|
|
|
|
((match "preproc_function_def" "compound_statement") point-min 0)
|
|
|
|
((match "preproc_call" "compound_statement") point-min 0)
|
2023-01-15 00:16:58 -08:00
|
|
|
|
|
|
|
;; {} blocks.
|
|
|
|
((node-is "}") point-min c-ts-mode--close-bracket-offset)
|
2022-12-29 00:58:50 -08:00
|
|
|
((parent-is "compound_statement")
|
2023-01-15 00:16:58 -08:00
|
|
|
point-min c-ts-mode--statement-offset)
|
|
|
|
((parent-is "enumerator_list")
|
|
|
|
point-min c-ts-mode--statement-offset)
|
|
|
|
((parent-is "field_declaration_list")
|
|
|
|
point-min c-ts-mode--statement-offset)
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
((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)
|
2023-01-07 13:04:07 +01:00
|
|
|
((parent-is "concatenated_string") parent-bol c-ts-mode-indent-offset)
|
2022-11-10 17:15:49 +01:00
|
|
|
((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)
|
|
|
|
((parent-is "argument_list") first-sibling 1)
|
|
|
|
((parent-is "parameter_list") first-sibling 1)
|
|
|
|
((parent-is "binary_expression") parent 0)
|
|
|
|
((query "(for_statement initializer: (_) @indent)") parent-bol 5)
|
|
|
|
((query "(for_statement condition: (_) @indent)") parent-bol 5)
|
|
|
|
((query "(for_statement update: (_) @indent)") parent-bol 5)
|
|
|
|
((query "(call_expression arguments: (_) @indent)") parent c-ts-mode-indent-offset)
|
|
|
|
((parent-is "call_expression") parent 0)
|
2022-12-11 15:57:43 +01:00
|
|
|
,@(when (eq mode 'cpp)
|
2023-01-07 15:45:05 -08:00
|
|
|
'(((node-is "access_specifier") parent-bol 0)
|
|
|
|
;; Indent the body of namespace definitions.
|
|
|
|
((parent-is "declaration_list") parent-bol c-ts-mode-indent-offset)))
|
2023-01-15 00:16:58 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
((parent-is "initializer_list") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "if_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "for_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "while_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "switch_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "case_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "do_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
,@(when (eq mode 'cpp)
|
|
|
|
`(((node-is "field_initializer_list") parent-bol ,(* c-ts-mode-indent-offset 2)))))))
|
|
|
|
`((gnu
|
|
|
|
;; Prepend rules to set highest priority
|
|
|
|
((match "while" "do_statement") parent 0)
|
2023-01-14 20:53:10 -08:00
|
|
|
(c-ts-mode--top-level-label-matcher point-min 1)
|
2022-11-10 17:15:49 +01:00
|
|
|
,@common)
|
|
|
|
(k&r ,@common)
|
2023-01-07 18:11:03 -08:00
|
|
|
(linux
|
2023-01-08 16:57:29 -08:00
|
|
|
;; Reference:
|
|
|
|
;; https://www.kernel.org/doc/html/latest/process/coding-style.html,
|
|
|
|
;; and script/Lindent in Linux kernel repository.
|
|
|
|
((node-is "labeled_statement") point-min 0)
|
2023-01-07 18:11:03 -08:00
|
|
|
,@common)
|
2022-11-10 17:15:49 +01:00
|
|
|
(bsd
|
2023-01-22 11:14:00 +01:00
|
|
|
((node-is "}") parent-bol 0)
|
|
|
|
((node-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
|
|
|
|
((parent-is "compound_statement") parent-bol c-ts-mode-indent-offset)
|
2022-11-10 17:15:49 +01:00
|
|
|
((parent-is "if_statement") parent-bol 0)
|
|
|
|
((parent-is "for_statement") parent-bol 0)
|
|
|
|
((parent-is "while_statement") parent-bol 0)
|
|
|
|
((parent-is "switch_statement") parent-bol 0)
|
|
|
|
((parent-is "case_statement") parent-bol 0)
|
|
|
|
((parent-is "do_statement") parent-bol 0)
|
|
|
|
,@common))))
|
|
|
|
|
2023-01-08 16:57:29 -08:00
|
|
|
(defun c-ts-mode--top-level-label-matcher (node &rest _)
|
|
|
|
"A matcher that matches a top-level label.
|
|
|
|
NODE should be a labeled_statement."
|
|
|
|
(let ((func (treesit-parent-until
|
|
|
|
node (lambda (n)
|
|
|
|
(equal (treesit-node-type n)
|
2023-01-15 00:21:10 -08:00
|
|
|
"compound_statement")))))
|
2023-01-08 16:57:29 -08:00
|
|
|
(and (equal (treesit-node-type node)
|
|
|
|
"labeled_statement")
|
2023-01-15 00:21:10 -08:00
|
|
|
(not (treesit-node-top-level func "compound_statement")))))
|
2023-01-08 16:57:29 -08:00
|
|
|
|
2023-01-15 00:16:58 -08:00
|
|
|
(defvar c-ts-mode-indent-block-type-regexp
|
|
|
|
(rx (or "compound_statement"
|
|
|
|
"field_declaration_list"
|
2023-01-20 22:16:25 +01:00
|
|
|
"enumerator_list"))
|
2023-01-15 00:16:58 -08:00
|
|
|
"Regexp matching types of block nodes (i.e., {} blocks).")
|
|
|
|
|
2023-01-25 23:47:27 -08:00
|
|
|
(defvar c-ts-mode--statement-offset-post-processr nil
|
|
|
|
"A functions that makes adjustments to `c-ts-mode--statement-offset'.
|
|
|
|
|
|
|
|
This is a function that takes two arguments, the current indent
|
|
|
|
level and the current node, and returns a new level.
|
|
|
|
|
|
|
|
When `c-ts-mode--statement-offset' runs and go up the parse tree,
|
|
|
|
it increments the indent level when some condition are met in
|
|
|
|
each level. At each level, after (possibly) incrementing the
|
|
|
|
offset, it calls this function, passing it the current indent
|
|
|
|
level and the current node, and use the return value as the new
|
|
|
|
indent level.")
|
|
|
|
|
2023-01-15 00:16:58 -08:00
|
|
|
(defun c-ts-mode--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 (defined
|
|
|
|
by `c-ts-mode--indent-block-type-regexp') between NODE and the
|
|
|
|
root node (not counting NODE itself), and multiply that by
|
|
|
|
`c-ts-mode-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.
|
|
|
|
|
|
|
|
PARENT is NODE's parent."
|
|
|
|
(let ((level 0))
|
|
|
|
;; 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))
|
|
|
|
(while (if (eq node t)
|
|
|
|
(setq node parent)
|
|
|
|
(setq node (treesit-node-parent node)))
|
|
|
|
(when (string-match-p c-ts-mode-indent-block-type-regexp
|
|
|
|
(treesit-node-type node))
|
|
|
|
(cl-incf level)
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (treesit-node-start node))
|
2023-01-19 14:46:17 -08:00
|
|
|
;; Add an extra level if the opening bracket is on its own
|
2023-01-23 02:27:15 +01:00
|
|
|
;; line, except (1) it's at top-level, or (2) it's immediate
|
2023-01-19 14:46:17 -08:00
|
|
|
;; 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-mode-indent-block-type-regexp
|
|
|
|
parent-type)))
|
|
|
|
nil)
|
|
|
|
;; Add a level.
|
2023-01-15 00:16:58 -08:00
|
|
|
((looking-back (rx bol (* whitespace))
|
|
|
|
(line-beginning-position))
|
2023-01-25 23:47:27 -08:00
|
|
|
(cl-incf level)))))
|
|
|
|
(when c-ts-mode--statement-offset-post-processr
|
|
|
|
(setq level (funcall c-ts-mode--statement-offset-post-processr
|
|
|
|
level node))))
|
2023-01-15 00:16:58 -08:00
|
|
|
(* level c-ts-mode-indent-offset)))
|
|
|
|
|
2023-01-25 23:47:27 -08:00
|
|
|
(defun c-ts-mode--fix-bracketless-indent (level node)
|
|
|
|
"Takes LEVEL and NODE and returns adjusted LEVEL.
|
|
|
|
This fixes indentation for cases shown in bug#61026. Basically
|
|
|
|
in C/C++, constructs like if, for, while sometimes don't have
|
|
|
|
bracket."
|
|
|
|
(if (and (not (equal (treesit-node-type node) "compound_statement"))
|
|
|
|
(member (treesit-node-type (treesit-node-parent node))
|
|
|
|
'("if_statement" "while_statement" "do_statement"
|
|
|
|
"for_statement")))
|
|
|
|
(1+ level)
|
|
|
|
level))
|
|
|
|
|
2023-01-15 00:16:58 -08:00
|
|
|
(defun c-ts-mode--close-bracket-offset (node parent &rest _)
|
|
|
|
"Offset for the closing bracket, NODE.
|
|
|
|
It's basically one level less that the statements in the block.
|
|
|
|
PARENT is NODE's parent."
|
|
|
|
(- (c-ts-mode--statement-offset node parent)
|
|
|
|
c-ts-mode-indent-offset))
|
2022-12-29 00:58:50 -08:00
|
|
|
|
2022-12-24 00:15:48 -08:00
|
|
|
;;; Font-lock
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defvar c-ts-mode--preproc-keywords
|
|
|
|
'("#define" "#if" "#ifdef" "#ifndef"
|
|
|
|
"#else" "#elif" "#endif" "#include")
|
|
|
|
"C/C++ keywords for tree-sitter font-locking.")
|
|
|
|
|
|
|
|
(defun c-ts-mode--keywords (mode)
|
|
|
|
"C/C++ keywords for tree-sitter font-locking.
|
|
|
|
MODE is either `c' or `cpp'."
|
|
|
|
(let ((c-keywords
|
|
|
|
'("break" "case" "const" "continue"
|
|
|
|
"default" "do" "else" "enum"
|
2022-11-30 19:58:42 +01:00
|
|
|
"extern" "for" "goto" "if" "inline"
|
2022-12-06 00:15:30 -08:00
|
|
|
"register" "return"
|
|
|
|
"sizeof" "static" "struct"
|
|
|
|
"switch" "typedef" "union"
|
2022-11-10 17:15:49 +01:00
|
|
|
"volatile" "while")))
|
|
|
|
(if (eq mode 'cpp)
|
|
|
|
(append c-keywords
|
|
|
|
'("and" "and_eq" "bitand" "bitor"
|
|
|
|
"catch" "class" "co_await" "co_return"
|
|
|
|
"co_yield" "compl" "concept" "consteval"
|
|
|
|
"constexpr" "constinit" "decltype" "delete"
|
2022-12-01 00:26:04 +01:00
|
|
|
"explicit" "final" "friend"
|
2022-11-10 17:15:49 +01:00
|
|
|
"mutable" "namespace" "new" "noexcept"
|
|
|
|
"not" "not_eq" "operator" "or"
|
|
|
|
"or_eq" "override" "private" "protected"
|
|
|
|
"public" "requires" "template" "throw"
|
|
|
|
"try" "typename" "using" "virtual"
|
|
|
|
"xor" "xor_eq"))
|
|
|
|
(append '("auto") c-keywords))))
|
|
|
|
|
2022-12-06 00:15:30 -08:00
|
|
|
(defvar c-ts-mode--type-keywords
|
|
|
|
'("long" "short" "signed" "unsigned")
|
|
|
|
"Keywords that should be considered as part of a type.")
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(defvar c-ts-mode--operators
|
|
|
|
'("=" "-" "*" "/" "+" "%" "~" "|" "&" "^" "<<" ">>" "->"
|
|
|
|
"." "<" "<=" ">=" ">" "==" "!=" "!" "&&" "||" "-="
|
|
|
|
"+=" "*=" "/=" "%=" "|=" "&=" "^=" ">>=" "<<=" "--" "++")
|
|
|
|
"C/C++ operators for tree-sitter font-locking.")
|
|
|
|
|
|
|
|
(defun c-ts-mode--font-lock-settings (mode)
|
|
|
|
"Tree-sitter font-lock settings.
|
|
|
|
MODE is either `c' or `cpp'."
|
|
|
|
(treesit-font-lock-rules
|
|
|
|
:language mode
|
|
|
|
:feature 'comment
|
|
|
|
`((comment) @font-lock-comment-face
|
2022-11-12 14:33:14 -08:00
|
|
|
(comment) @contextual)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'preprocessor
|
|
|
|
`((preproc_directive) @font-lock-preprocessor-face
|
|
|
|
|
|
|
|
(preproc_def
|
|
|
|
name: (identifier) @font-lock-variable-name-face)
|
|
|
|
|
|
|
|
(preproc_ifdef
|
|
|
|
name: (identifier) @font-lock-variable-name-face)
|
|
|
|
|
|
|
|
(preproc_function_def
|
|
|
|
name: (identifier) @font-lock-function-name-face)
|
|
|
|
|
|
|
|
(preproc_params
|
|
|
|
(identifier) @font-lock-variable-name-face)
|
|
|
|
|
|
|
|
(preproc_defined) @font-lock-preprocessor-face
|
|
|
|
(preproc_defined (identifier) @font-lock-variable-name-face)
|
|
|
|
[,@c-ts-mode--preproc-keywords] @font-lock-preprocessor-face)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'constant
|
|
|
|
`((true) @font-lock-constant-face
|
|
|
|
(false) @font-lock-constant-face
|
|
|
|
(null) @font-lock-constant-face
|
|
|
|
,@(when (eq mode 'cpp)
|
2022-12-09 03:09:31 -08:00
|
|
|
'((nullptr) @font-lock-constant-face)))
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'keyword
|
|
|
|
`([,@(c-ts-mode--keywords mode)] @font-lock-keyword-face
|
|
|
|
,@(when (eq mode 'cpp)
|
2022-12-09 03:09:31 -08:00
|
|
|
'((auto) @font-lock-keyword-face
|
|
|
|
(this) @font-lock-keyword-face)))
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'operator
|
2022-11-16 14:33:48 -08:00
|
|
|
`([,@c-ts-mode--operators] @font-lock-operator-face
|
|
|
|
"!" @font-lock-negation-char-face)
|
|
|
|
|
|
|
|
:language mode
|
2022-11-10 17:15:49 +01:00
|
|
|
:feature 'string
|
|
|
|
`((string_literal) @font-lock-string-face
|
2022-12-09 22:36:03 +01:00
|
|
|
(system_lib_string) @font-lock-string-face
|
|
|
|
,@(when (eq mode 'cpp)
|
|
|
|
'((raw_string_literal) @font-lock-string-face)))
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'literal
|
2022-11-13 22:06:33 -05:00
|
|
|
`((number_literal) @font-lock-number-face
|
2022-11-10 17:15:49 +01:00
|
|
|
(char_literal) @font-lock-constant-face)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'type
|
|
|
|
`((primitive_type) @font-lock-type-face
|
2022-11-16 14:33:48 -08:00
|
|
|
(type_identifier) @font-lock-type-face
|
|
|
|
(sized_type_specifier) @font-lock-type-face
|
2022-11-10 17:15:49 +01:00
|
|
|
,@(when (eq mode 'cpp)
|
|
|
|
'((type_qualifier) @font-lock-type-face
|
|
|
|
|
|
|
|
(qualified_identifier
|
|
|
|
scope: (namespace_identifier) @font-lock-type-face)
|
|
|
|
|
2022-12-06 00:15:30 -08:00
|
|
|
(operator_cast) type: (type_identifier) @font-lock-type-face))
|
|
|
|
[,@c-ts-mode--type-keywords] @font-lock-type-face)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'definition
|
2022-11-16 14:33:48 -08:00
|
|
|
;; Highlights identifiers in declarations.
|
2022-11-10 17:15:49 +01:00
|
|
|
`((declaration
|
2022-11-21 22:37:36 -08:00
|
|
|
declarator: (_) @c-ts-mode--fontify-declarator)
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
(field_declaration
|
2022-11-21 13:17:16 -08:00
|
|
|
declarator: (_) @c-ts-mode--fontify-declarator)
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
(function_definition
|
2022-11-21 13:17:16 -08:00
|
|
|
declarator: (_) @c-ts-mode--fontify-declarator))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2022-11-13 22:06:33 -05:00
|
|
|
;; Should we highlight identifiers in the parameter list?
|
|
|
|
;; (parameter_declaration
|
2022-11-21 13:17:16 -08:00
|
|
|
;; declarator: (_) @c-ts-mode--fontify-declarator))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
:language mode
|
2022-11-16 14:33:48 -08:00
|
|
|
:feature 'assignment
|
|
|
|
;; TODO: Recursively highlight identifiers in parenthesized
|
2023-01-03 13:49:08 +01:00
|
|
|
;; expressions, see `c-ts-mode--fontify-declarator' for
|
2022-11-16 14:33:48 -08:00
|
|
|
;; inspiration.
|
2022-11-10 17:15:49 +01:00
|
|
|
'((assignment_expression
|
|
|
|
left: (identifier) @font-lock-variable-name-face)
|
2022-11-16 14:33:48 -08:00
|
|
|
(assignment_expression
|
|
|
|
left: (field_expression field: (_) @font-lock-property-face))
|
|
|
|
(assignment_expression
|
|
|
|
left: (pointer_expression
|
|
|
|
(identifier) @font-lock-variable-name-face))
|
|
|
|
(assignment_expression
|
|
|
|
left: (subscript_expression
|
2022-11-21 22:37:36 -08:00
|
|
|
(identifier) @font-lock-variable-name-face))
|
|
|
|
(init_declarator declarator: (_) @c-ts-mode--fontify-declarator))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2022-11-16 14:33:48 -08:00
|
|
|
:language mode
|
2022-11-21 22:37:36 -08:00
|
|
|
:feature 'function
|
2022-11-16 14:33:48 -08:00
|
|
|
'((call_expression
|
2022-11-21 22:37:36 -08:00
|
|
|
function: (identifier) @font-lock-function-name-face))
|
|
|
|
|
|
|
|
:language mode
|
|
|
|
:feature 'variable
|
|
|
|
'((identifier) @c-ts-mode--fontify-variable)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
2022-11-16 14:33:48 -08:00
|
|
|
:feature 'label
|
2022-11-21 22:37:36 -08:00
|
|
|
'((labeled_statement
|
2022-11-21 01:27:55 -08:00
|
|
|
label: (statement_identifier) @font-lock-constant-face))
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
:language mode
|
|
|
|
:feature 'error
|
2022-12-06 00:26:51 -08:00
|
|
|
'((ERROR) @c-ts-mode--fontify-error)
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-13 22:06:33 -05:00
|
|
|
:feature 'escape-sequence
|
|
|
|
:language mode
|
|
|
|
:override t
|
|
|
|
'((escape_sequence) @font-lock-escape-face)
|
|
|
|
|
|
|
|
:language mode
|
|
|
|
:feature 'property
|
|
|
|
'((field_identifier) @font-lock-property-face
|
|
|
|
(enumerator
|
|
|
|
name: (identifier) @font-lock-property-face))
|
|
|
|
|
|
|
|
:language mode
|
|
|
|
:feature 'bracket
|
|
|
|
'((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
|
|
|
|
|
|
|
|
:language mode
|
|
|
|
:feature 'delimiter
|
|
|
|
'((["," ":" ";"]) @font-lock-delimiter-face)
|
|
|
|
|
2022-11-15 10:14:47 -08:00
|
|
|
:language mode
|
|
|
|
:feature 'emacs-devel
|
2022-11-21 12:07:20 -08:00
|
|
|
:override t
|
|
|
|
'(((call_expression
|
|
|
|
(call_expression function: (identifier) @fn)
|
|
|
|
@c-ts-mode--fontify-defun)
|
2022-11-15 10:14:47 -08:00
|
|
|
(:match "^DEFUN$" @fn)))))
|
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Font-lock helpers
|
|
|
|
|
2023-01-07 16:03:37 -08:00
|
|
|
(defun c-ts-mode--declarator-identifier (node &optional qualified)
|
|
|
|
"Return the identifier of the declarator node NODE.
|
|
|
|
|
|
|
|
If QUALIFIED is non-nil, include the names space part of the
|
|
|
|
identifier and return a qualified_identifier."
|
2022-11-16 14:33:48 -08:00
|
|
|
(pcase (treesit-node-type node)
|
2022-12-24 18:59:39 -08:00
|
|
|
;; Recurse.
|
2022-11-16 14:33:48 -08:00
|
|
|
((or "attributed_declarator" "parenthesized_declarator")
|
2023-01-07 16:03:37 -08:00
|
|
|
(c-ts-mode--declarator-identifier (treesit-node-child node 0 t)
|
|
|
|
qualified))
|
2022-12-31 01:45:27 +01:00
|
|
|
((or "pointer_declarator" "reference_declarator")
|
2023-01-07 16:03:37 -08:00
|
|
|
(c-ts-mode--declarator-identifier (treesit-node-child node -1)
|
|
|
|
qualified))
|
2022-11-16 14:33:48 -08:00
|
|
|
((or "function_declarator" "array_declarator" "init_declarator")
|
2022-12-24 18:59:39 -08:00
|
|
|
(c-ts-mode--declarator-identifier
|
2023-01-07 16:03:37 -08:00
|
|
|
(treesit-node-child-by-field-name node "declarator")
|
|
|
|
qualified))
|
2023-01-03 22:08:13 +01:00
|
|
|
("qualified_identifier"
|
2023-01-07 16:03:37 -08:00
|
|
|
(if qualified
|
|
|
|
node
|
|
|
|
(c-ts-mode--declarator-identifier
|
|
|
|
(treesit-node-child-by-field-name node "name")
|
|
|
|
qualified)))
|
2022-12-24 18:59:39 -08:00
|
|
|
;; Terminal case.
|
2022-11-16 14:33:48 -08:00
|
|
|
((or "identifier" "field_identifier")
|
2022-12-24 18:59:39 -08:00
|
|
|
node)))
|
|
|
|
|
2022-12-25 12:59:06 +01:00
|
|
|
(defun c-ts-mode--fontify-declarator (node override start end &rest _args)
|
2022-12-24 18:59:39 -08:00
|
|
|
"Fontify a declarator (whatever under the \"declarator\" field).
|
|
|
|
For NODE, OVERRIDE, START, END, and ARGS, see
|
|
|
|
`treesit-font-lock-rules'."
|
|
|
|
(let* ((identifier (c-ts-mode--declarator-identifier node))
|
2023-01-03 22:08:13 +01:00
|
|
|
(qualified-root
|
|
|
|
(treesit-parent-while (treesit-node-parent identifier)
|
|
|
|
(lambda (node)
|
|
|
|
(equal (treesit-node-type node)
|
|
|
|
"qualified_identifier"))))
|
|
|
|
(face (pcase (treesit-node-type (treesit-node-parent
|
|
|
|
(or qualified-root
|
|
|
|
identifier)))
|
2022-12-24 18:59:39 -08:00
|
|
|
("function_declarator" 'font-lock-function-name-face)
|
|
|
|
(_ 'font-lock-variable-name-face))))
|
|
|
|
(treesit-fontify-with-override
|
|
|
|
(treesit-node-start identifier) (treesit-node-end identifier)
|
|
|
|
face override start end)))
|
2022-11-16 14:33:48 -08:00
|
|
|
|
2022-11-21 22:37:36 -08:00
|
|
|
(defun c-ts-mode--fontify-variable (node override start end &rest _)
|
2022-12-06 00:17:04 -08:00
|
|
|
"Fontify an identifier node if it is a variable.
|
|
|
|
Don't fontify if it is a function identifier. For NODE,
|
2022-11-21 22:37:36 -08:00
|
|
|
OVERRIDE, START, END, and ARGS, see `treesit-font-lock-rules'."
|
|
|
|
(when (not (equal (treesit-node-type
|
|
|
|
(treesit-node-parent node))
|
|
|
|
"call_expression"))
|
|
|
|
(treesit-fontify-with-override
|
2022-12-04 00:22:28 -08:00
|
|
|
(treesit-node-start node) (treesit-node-end node)
|
|
|
|
'font-lock-variable-name-face override start end)))
|
2022-11-21 22:37:36 -08:00
|
|
|
|
2022-11-15 10:14:47 -08:00
|
|
|
(defun c-ts-mode--fontify-defun (node override start end &rest _)
|
|
|
|
"Correctly fontify the DEFUN macro.
|
|
|
|
For NODE, OVERRIDE, START, and END, see
|
|
|
|
`treesit-font-lock-rules'. The captured NODE is a
|
|
|
|
call_expression where DEFUN is the function.
|
|
|
|
|
|
|
|
This function corrects the fontification on the colon in
|
|
|
|
\"doc:\", and the parameter list."
|
|
|
|
(let* ((parent (treesit-node-parent node))
|
|
|
|
;; ARG-LIST-1 and 2 are like this:
|
|
|
|
;;
|
|
|
|
;; DEFUN (ARG-LIST-1)
|
|
|
|
;; (ARG-LIST-2)
|
|
|
|
(arg-list-1 (treesit-node-children
|
|
|
|
(treesit-node-child-by-field-name
|
|
|
|
node "arguments")))
|
|
|
|
;; ARG-LIST-2 is the
|
|
|
|
(arg-list-2 (treesit-node-children
|
|
|
|
(treesit-node-child-by-field-name
|
|
|
|
parent "arguments") t)))
|
|
|
|
;; Fix the colon.
|
|
|
|
(dolist (node arg-list-1)
|
|
|
|
(when (equal (treesit-node-text node t) ":")
|
|
|
|
(treesit-fontify-with-override
|
|
|
|
(treesit-node-start node) (treesit-node-end node)
|
2022-12-04 00:22:28 -08:00
|
|
|
'default override start end)))
|
2022-11-15 10:14:47 -08:00
|
|
|
;; Fix the parameter list.
|
|
|
|
(while arg-list-2
|
|
|
|
(let ((type (and arg-list-2 (pop arg-list-2)))
|
|
|
|
(arg (and arg-list-2 (pop arg-list-2))))
|
|
|
|
(when type
|
|
|
|
(treesit-fontify-with-override
|
2022-12-04 00:22:28 -08:00
|
|
|
(treesit-node-start type) (treesit-node-end type)
|
|
|
|
'font-lock-type-face override start end))
|
2022-11-15 10:14:47 -08:00
|
|
|
(when arg
|
|
|
|
(treesit-fontify-with-override
|
2022-12-04 00:22:28 -08:00
|
|
|
(treesit-node-start arg) (treesit-node-end arg)
|
|
|
|
'default override start end))))))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2022-12-06 00:26:51 -08:00
|
|
|
(defun c-ts-mode--fontify-error (node override start end &rest _)
|
2022-11-23 17:35:15 -08:00
|
|
|
"Fontify the error nodes.
|
|
|
|
For NODE, OVERRIDE, START, and END, see
|
|
|
|
`treesit-font-lock-rules'."
|
|
|
|
(let ((parent (treesit-node-parent node))
|
|
|
|
(child (treesit-node-child node 0)))
|
|
|
|
(treesit-fontify-with-override
|
2022-12-04 00:22:28 -08:00
|
|
|
(treesit-node-start node) (treesit-node-end node)
|
2022-11-23 17:35:15 -08:00
|
|
|
(cond
|
|
|
|
;; This matches the case MACRO(struct a, b, c)
|
|
|
|
;; where struct is seen as error.
|
|
|
|
((and (equal (treesit-node-type child) "identifier")
|
|
|
|
(equal (treesit-node-type parent) "argument_list")
|
|
|
|
(member (treesit-node-text child)
|
|
|
|
'("struct" "long" "short" "enum" "union")))
|
|
|
|
'font-lock-keyword-face)
|
|
|
|
(t 'font-lock-warning-face))
|
2022-12-04 00:22:28 -08:00
|
|
|
override start end)))
|
2022-11-23 17:35:15 -08:00
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Imenu
|
|
|
|
|
2022-12-24 16:33:35 -08:00
|
|
|
(defun c-ts-mode--defun-name (node)
|
|
|
|
"Return the name of the defun NODE.
|
2022-12-27 20:57:12 -08:00
|
|
|
Return nil if NODE is not a defun node or doesn't have a name."
|
2022-12-24 16:33:35 -08:00
|
|
|
(treesit-node-text
|
|
|
|
(pcase (treesit-node-type node)
|
2022-12-24 18:59:39 -08:00
|
|
|
((or "function_definition" "declaration")
|
|
|
|
(c-ts-mode--declarator-identifier
|
2023-01-07 16:03:37 -08:00
|
|
|
(treesit-node-child-by-field-name node "declarator")
|
|
|
|
t))
|
2022-12-27 20:57:12 -08:00
|
|
|
((or "struct_specifier" "enum_specifier"
|
2023-01-07 15:45:05 -08:00
|
|
|
"union_specifier" "class_specifier"
|
|
|
|
"namespace_definition")
|
2022-12-24 16:33:35 -08:00
|
|
|
(treesit-node-child-by-field-name node "name")))
|
|
|
|
t))
|
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Defun navigation
|
|
|
|
|
2022-12-15 17:44:07 -08:00
|
|
|
(defun c-ts-mode--defun-valid-p (node)
|
2022-12-26 01:01:41 -08:00
|
|
|
"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"
|
|
|
|
"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")))))
|
2022-12-15 17:44:07 -08:00
|
|
|
|
2023-01-07 17:46:27 -08:00
|
|
|
(defun c-ts-mode--defun-for-class-in-imenu-p (node)
|
|
|
|
"Check if NODE is a valid entry for the Class subindex.
|
|
|
|
|
|
|
|
Basically, if NODE is a class, return non-nil; if NODE is a
|
|
|
|
function but is under a class, return non-nil; if NODE is a
|
|
|
|
top-level function, return nil.
|
|
|
|
|
|
|
|
This is for the Class subindex in
|
|
|
|
`treesit-simple-imenu-settings'."
|
|
|
|
(pcase (treesit-node-type node)
|
|
|
|
;; The Class subindex only has class_specifier and
|
|
|
|
;; function_definition.
|
|
|
|
("class_specifier" t)
|
|
|
|
("function_definition"
|
|
|
|
;; Return t if this function is nested in a class.
|
|
|
|
(treesit-node-top-level node "class_specifier"))))
|
|
|
|
|
2022-12-15 17:44:07 -08:00
|
|
|
(defun c-ts-mode--defun-skipper ()
|
|
|
|
"Custom defun skipper for `c-ts-mode' and friends.
|
|
|
|
Structs in C ends with a semicolon, but the semicolon is not
|
|
|
|
considered part of the struct node, so point would stop before
|
|
|
|
the semicolon. This function skips the semicolon."
|
|
|
|
(when (looking-at (rx (* (or " " "\t")) ";"))
|
|
|
|
(goto-char (match-end 0)))
|
|
|
|
(treesit-default-defun-skipper))
|
|
|
|
|
2022-12-02 16:05:35 +01:00
|
|
|
(defun c-ts-mode-indent-defun ()
|
|
|
|
"Indent the current top-level declaration syntactically.
|
|
|
|
|
|
|
|
`treesit-defun-type-regexp' defines what constructs to indent."
|
|
|
|
(interactive "*")
|
2022-12-20 21:22:30 -08:00
|
|
|
(when-let ((orig-point (point-marker))
|
|
|
|
(node (treesit-defun-at-point)))
|
|
|
|
(indent-region (treesit-node-start node)
|
|
|
|
(treesit-node-end node))
|
2022-12-02 16:05:35 +01:00
|
|
|
(goto-char orig-point)))
|
|
|
|
|
2022-12-24 00:16:45 -08:00
|
|
|
;;; Modes
|
|
|
|
|
2022-12-02 16:05:35 +01:00
|
|
|
(defvar-keymap c-ts-mode-map
|
|
|
|
:doc "Keymap for the C language with tree-sitter"
|
|
|
|
:parent prog-mode-map
|
|
|
|
"C-c C-q" #'c-ts-mode-indent-defun)
|
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
;;;###autoload
|
2022-12-01 20:42:35 -08:00
|
|
|
(define-derived-mode c-ts-base-mode prog-mode "C"
|
2022-12-02 16:05:35 +01:00
|
|
|
"Major mode for editing C, powered by tree-sitter.
|
|
|
|
|
|
|
|
\\{c-ts-mode-map}"
|
2022-11-10 17:15:49 +01:00
|
|
|
:syntax-table c-ts-mode--syntax-table
|
|
|
|
|
|
|
|
;; Navigation.
|
|
|
|
(setq-local treesit-defun-type-regexp
|
2022-12-15 17:44:07 -08:00
|
|
|
(cons (regexp-opt '("function_definition"
|
|
|
|
"type_definition"
|
|
|
|
"struct_specifier"
|
|
|
|
"enum_specifier"
|
|
|
|
"union_specifier"
|
2023-01-07 15:45:05 -08:00
|
|
|
"class_specifier"
|
|
|
|
"namespace_definition"))
|
2022-12-15 17:44:07 -08:00
|
|
|
#'c-ts-mode--defun-valid-p))
|
|
|
|
(setq-local treesit-defun-skipper #'c-ts-mode--defun-skipper)
|
2022-12-24 16:33:35 -08:00
|
|
|
(setq-local treesit-defun-name-function #'c-ts-mode--defun-name)
|
2022-11-30 14:59:06 -08:00
|
|
|
|
|
|
|
;; 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)
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;; Indent.
|
|
|
|
(when (eq c-ts-mode-indent-style 'linux)
|
|
|
|
(setq-local indent-tabs-mode t))
|
2023-01-25 23:47:27 -08:00
|
|
|
(setq-local c-ts-mode--statement-offset-post-processr
|
|
|
|
#'c-ts-mode--fix-bracketless-indent)
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2022-12-25 11:21:50 -08:00
|
|
|
;; Comment
|
2023-01-21 12:24:55 +01:00
|
|
|
(c-ts-common-comment-setup)
|
2022-12-23 17:12:32 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
;; Electric
|
|
|
|
(setq-local electric-indent-chars
|
2022-11-14 08:03:11 +01:00
|
|
|
(append "{}():;," electric-indent-chars))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;; Imenu.
|
2022-12-27 20:57:12 -08:00
|
|
|
(setq-local treesit-simple-imenu-settings
|
|
|
|
(let ((pred #'c-ts-mode--defun-valid-p))
|
|
|
|
`(("Struct" ,(rx bos (or "struct" "enum" "union")
|
|
|
|
"_specifier" eos)
|
|
|
|
,pred nil)
|
|
|
|
("Variable" ,(rx bos "declaration" eos) ,pred nil)
|
|
|
|
("Function" "\\`function_definition\\'" ,pred nil)
|
|
|
|
("Class" ,(rx bos (or "class_specifier"
|
|
|
|
"function_definition")
|
|
|
|
eos)
|
2023-01-07 17:46:27 -08:00
|
|
|
c-ts-mode--defun-for-class-in-imenu-p nil))))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
(setq-local treesit-font-lock-feature-list
|
2022-11-26 15:05:57 -08:00
|
|
|
'(( comment definition)
|
|
|
|
( keyword preprocessor string type)
|
|
|
|
( assignment constant escape-sequence label literal property )
|
|
|
|
( bracket delimiter error function operator variable))))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;;;###autoload
|
2022-12-01 20:42:35 -08:00
|
|
|
(define-derived-mode c-ts-mode c-ts-base-mode "C"
|
2023-01-07 16:32:46 -08:00
|
|
|
"Major mode for editing C, powered by tree-sitter.
|
|
|
|
|
|
|
|
This mode is independent from the classic cc-mode.el based
|
|
|
|
`c-mode', so configuration variables of that mode, like
|
2023-01-18 15:32:12 -08:00
|
|
|
`c-basic-offset', doesn't affect this mode.
|
|
|
|
|
|
|
|
To use tree-sitter C/C++ modes by default, evaluate
|
|
|
|
|
|
|
|
(add-to-list \\='major-mode-remap-alist \\='(c-mode . c-ts-mode))
|
|
|
|
(add-to-list \\='major-mode-remap-alist \\='(c++-mode . c++-ts-mode))
|
|
|
|
(add-to-list \\='major-mode-remap-alist
|
|
|
|
\\='(c-or-c++-mode . c-or-c++-ts-mode))
|
|
|
|
|
|
|
|
in your configuration."
|
2022-11-10 17:15:49 +01:00
|
|
|
:group 'c
|
|
|
|
|
2023-01-07 17:26:26 -08:00
|
|
|
(when (treesit-ready-p 'c)
|
|
|
|
(treesit-parser-create 'c)
|
|
|
|
;; Comments.
|
|
|
|
(setq-local comment-start "/* ")
|
|
|
|
(setq-local comment-end " */")
|
|
|
|
;; Indent.
|
|
|
|
(setq-local treesit-simple-indent-rules
|
2023-01-25 21:04:00 +01:00
|
|
|
(c-ts-mode--get-indent-style 'c))
|
2023-01-07 17:26:26 -08:00
|
|
|
;; Font-lock.
|
|
|
|
(setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'c))
|
|
|
|
(treesit-major-mode-setup)))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
|
|
|
;;;###autoload
|
2022-12-01 20:42:35 -08:00
|
|
|
(define-derived-mode c++-ts-mode c-ts-base-mode "C++"
|
2023-01-07 16:32:46 -08:00
|
|
|
"Major mode for editing C++, powered by tree-sitter.
|
|
|
|
|
|
|
|
This mode is independent from the classic cc-mode.el based
|
|
|
|
`c++-mode', so configuration variables of that mode, like
|
2023-01-18 15:32:12 -08:00
|
|
|
`c-basic-offset', don't affect this mode.
|
|
|
|
|
|
|
|
To use tree-sitter C/C++ modes by default, evaluate
|
|
|
|
|
|
|
|
(add-to-list \\='major-mode-remap-alist \\='(c-mode . c-ts-mode))
|
|
|
|
(add-to-list \\='major-mode-remap-alist \\='(c++-mode . c++-ts-mode))
|
|
|
|
(add-to-list \\='major-mode-remap-alist
|
|
|
|
\\='(c-or-c++-mode . c-or-c++-ts-mode))
|
|
|
|
|
|
|
|
in your configuration."
|
2022-11-10 17:15:49 +01:00
|
|
|
:group 'c++
|
|
|
|
|
2023-01-07 17:26:26 -08:00
|
|
|
(when (treesit-ready-p 'cpp)
|
|
|
|
(treesit-parser-create 'cpp)
|
|
|
|
;; Syntax.
|
|
|
|
(setq-local syntax-propertize-function
|
|
|
|
#'c-ts-mode--syntax-propertize)
|
|
|
|
;; Indent.
|
|
|
|
(setq-local treesit-simple-indent-rules
|
2023-01-25 21:04:00 +01:00
|
|
|
(c-ts-mode--get-indent-style 'cpp))
|
2023-01-07 17:26:26 -08:00
|
|
|
;; Font-lock.
|
|
|
|
(setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'cpp))
|
|
|
|
(treesit-major-mode-setup)))
|
2022-11-10 17:15:49 +01:00
|
|
|
|
2023-01-17 22:30:09 -08:00
|
|
|
;; 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.
|
|
|
|
(defconst c-ts-mode--c-or-c++-regexp
|
|
|
|
(eval-when-compile
|
|
|
|
(let ((id "[a-zA-Z_][a-zA-Z0-9_]*") (ws "[ \t]+") (ws-maybe "[ \t]*")
|
|
|
|
(headers '("string" "string_view" "iostream" "map" "unordered_map"
|
|
|
|
"set" "unordered_set" "vector" "tuple")))
|
|
|
|
(concat "^" ws-maybe "\\(?:"
|
|
|
|
"using" ws "\\(?:namespace" ws
|
|
|
|
"\\|" id "::"
|
|
|
|
"\\|" id ws-maybe "=\\)"
|
|
|
|
"\\|" "\\(?:inline" ws "\\)?namespace"
|
|
|
|
"\\(:?" ws "\\(?:" id "::\\)*" id "\\)?" ws-maybe "{"
|
|
|
|
"\\|" "class" ws id
|
|
|
|
"\\(?:" ws "final" "\\)?" ws-maybe "[:{;\n]"
|
|
|
|
"\\|" "struct" ws id "\\(?:" ws "final" ws-maybe "[:{\n]"
|
|
|
|
"\\|" ws-maybe ":\\)"
|
|
|
|
"\\|" "template" ws-maybe "<.*?>"
|
|
|
|
"\\|" "#include" ws-maybe "<" (regexp-opt headers) ">"
|
|
|
|
"\\)")))
|
|
|
|
"A regexp applied to C header files to check if they are really C++.")
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun c-or-c++-ts-mode ()
|
|
|
|
"Analyze buffer and enable either C or C++ mode.
|
|
|
|
|
|
|
|
Some people and projects use .h extension for C++ header files
|
|
|
|
which is also the one used for C header files. This makes
|
|
|
|
matching on file name insufficient for detecting major mode that
|
|
|
|
should be used.
|
|
|
|
|
|
|
|
This function attempts to use file contents to determine whether
|
|
|
|
the code is C or C++ and based on that chooses whether to enable
|
|
|
|
`c-ts-mode' or `c++-ts-mode'."
|
|
|
|
(interactive)
|
|
|
|
(if (save-excursion
|
|
|
|
(save-restriction
|
|
|
|
(save-match-data ; Why `save-match-data'?
|
|
|
|
(widen)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(re-search-forward c-ts-mode--c-or-c++-regexp nil t))))
|
|
|
|
(c++-ts-mode)
|
|
|
|
(c-ts-mode)))
|
2023-01-20 10:28:26 +02:00
|
|
|
;; The entries for C++ must come first to prevent *.c files be taken
|
|
|
|
;; as C++ on case-insensitive filesystems, since *.C files are C++,
|
|
|
|
;; not C.
|
|
|
|
(if (treesit-ready-p 'cpp)
|
|
|
|
(add-to-list 'auto-mode-alist
|
|
|
|
'("\\(\\.ii\\|\\.\\(CC?\\|HH?\\)\\|\\.[ch]\\(pp\\|xx\\|\\+\\+\\)\\|\\.\\(cc\\|hh\\)\\)\\'"
|
|
|
|
. c++-ts-mode)))
|
|
|
|
|
|
|
|
(if (treesit-ready-p 'c)
|
|
|
|
(add-to-list 'auto-mode-alist
|
|
|
|
'("\\(\\.[chi]\\|\\.lex\\|\\.y\\(acc\\)?\\|\\.x[bp]m\\)\\'"
|
|
|
|
. c-ts-mode)))
|
|
|
|
|
|
|
|
(if (and (treesit-ready-p 'cpp)
|
|
|
|
(treesit-ready-p 'c))
|
|
|
|
(add-to-list 'auto-mode-alist '("\\.h\\'" . c-or-c++-ts-mode)))
|
2023-01-17 22:30:09 -08:00
|
|
|
|
2022-11-10 17:15:49 +01:00
|
|
|
(provide 'c-ts-mode)
|
|
|
|
|
|
|
|
;;; c-ts-mode.el ends here
|