Add tree-sitter modes for C-like languages

* etc/NEWS: Mention the new modes
* lisp/progmodes/c-ts-mode.el: New major mode with tree-sitter support
for C and C++.
* lisp/progmodes/java-ts-mode.el: New major mode with tree-sitter support.
* lisp/progmodes/json-ts-mode.el: New major mode with tree-sitter support.
* lisp/progmodes/css-ts-mode.el: New major mode with tree-sitter support.
This commit is contained in:
Theodor Thornhill 2022-11-10 17:15:49 +01:00 committed by Yuan Fu
parent faf44e2c61
commit 67f9705fc0
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
5 changed files with 1043 additions and 4 deletions

View file

@ -2784,10 +2784,34 @@ when visiting JSON files.
** New mode ts-mode'.
Support is added for TypeScript, based on the new integration with
Tree-Sitter. There's support for font-locking, indentation and
navigation. Tree-Sitter is required for this mode to function, but if
it is not available, we will default to use 'js-mode'.
A major mode based on the Tree-sitter library for editing programs
in the FOO language. It includes support for font-locking,
indentation, and navigation.
** New mode c-ts-mode'.
A major mode based on the Tree-sitter library for editing programs
in the C language. It includes support for font-locking,
indentation, Imenu, which-func, and navigation.
** New mode c++-ts-mode'.
A major mode based on the Tree-sitter library for editing programs
in the C++ language. It includes support for font-locking,
indentation, Imenu, which-func, and navigation.
** New mode java-ts-mode'.
A major mode based on the Tree-sitter library for editing programs
in the Java language. It includes support for font-locking,
indentation, Imenu, which-func, and navigation.
** New mode css-ts-mode'.
A major mode based on the Tree-sitter library for editing programs
in the CSS language. It includes support for font-locking,
indentation, Imenu, which-func, and navigation.
** New mode json-ts-mode'.
A major mode based on the Tree-sitter library for editing programs
in the JSON language. It includes support for font-locking,
indentation, Imenu, which-func, and navigation.
* Incompatible Lisp Changes in Emacs 29.1

445
lisp/progmodes/c-ts-mode.el Normal file
View file

@ -0,0 +1,445 @@
;;; c-ts-mode.el --- tree sitter support for C and C++ -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; 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.
;; This program is free software; you can redistribute it and/or modify
;; 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.
;; This program is distributed in the hope that it will be useful,
;; 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
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'treesit)
(require 'rx)
(defcustom c-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `c-ts-mode'."
:type 'integer
:safe 'integerp
:group 'c)
(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'."
:type '(choice (symbol :tag "Gnu" 'gnu)
(symbol :tag "K&R" 'k&r)
(symbol :tag "Linux" 'linux)
(symbol :tag "BSD" 'bsd)
(function :tag "A function for user customized style" ignore))
:group 'c)
(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)
table)
"Syntax table for `c-ts-mode'.")
(defun c-ts-mode--indent-styles (mode)
"Indent rules supported by `c-ts-mode'.
MODE is either `c' or `cpp'."
(let ((common
`(((parent-is "translation_unit") parent-bol 0)
((node-is ")") parent 1)
((node-is "]") parent-bol 0)
((node-is "}") (and parent parent-bol) 0)
((node-is "else") parent-bol 0)
((node-is "case") parent-bol 0)
((node-is "preproc_arg") no-indent)
((node-is "comment") no-indent)
((parent-is "comment") no-indent)
((node-is "labeled_statement") parent-bol 0)
((parent-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
((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)
((parent-is "compound_statement") (and parent parent-bol) c-ts-mode-indent-offset)
((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 "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)
((parent-is "enumerator_list") parent-bol c-ts-mode-indent-offset)
((parent-is "field_declaration_list") parent-bol c-ts-mode-indent-offset)
((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)
,@common)
(k&r ,@common)
(linux ,@common)
(bsd
((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))))
(defun c-ts-mode--set-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)
(pcase c-ts-mode-indent-style
('gnu (alist-get 'gnu (c-ts-mode--indent-styles mode)))
('k&r (alist-get 'k&r (c-ts-mode--indent-styles mode)))
('bsd (alist-get 'bsd (c-ts-mode--indent-styles mode)))
('linux (alist-get 'linux (c-ts-mode--indent-styles mode)))))))
`((,mode ,@style))))
(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"
"extern" "for" "goto" "if"
"long" "register" "return" "short"
"signed" "sizeof" "static" "struct"
"switch" "typedef" "union" "unsigned"
"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"
"explicit" "final" "friend" "friend"
"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))))
(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
:override t
:feature 'comment
`((comment) @font-lock-comment-face
(comment) @contexual)
:language mode
:override t
: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)
:language mode
:override t
:feature 'constant
`((true) @font-lock-constant-face
(false) @font-lock-constant-face
(null) @font-lock-constant-face
,@(when (eq mode 'cpp)
'((this) @font-lock-constant-face)))
:language mode
:override t
:feature 'keyword
`([,@(c-ts-mode--keywords mode)] @font-lock-keyword-face
,@(when (eq mode 'cpp)
'((auto) @font-lock-keyword-face)))
:language mode
:override t
:feature 'operator
`([,@c-ts-mode--operators] @font-lock-builtin-face)
:language mode
:override t
:feature 'string
`((string_literal) @font-lock-string-face
((string_literal)) @contextual
(system_lib_string) @font-lock-string-face
(escape_sequence) @font-lock-string-face)
:language mode
:override t
:feature 'literal
`((number_literal) @font-lock-constant-face
(char_literal) @font-lock-constant-face)
:language mode
:override t
:feature 'type
`((primitive_type) @font-lock-type-face
,@(when (eq mode 'cpp)
'((type_qualifier) @font-lock-type-face
(qualified_identifier
scope: (namespace_identifier) @font-lock-type-face)
(operator_cast) type: (type_identifier) @font-lock-type-face)))
:language mode
:override t
:feature 'definition
`((declaration
declarator: (identifier) @font-lock-variable-name-face)
(declaration
type: (type_identifier) @font-lock-type-face)
(field_declaration
declarator: (field_identifier) @font-lock-variable-name-face)
(field_declaration
type: (type_identifier) @font-lock-type-face)
(parameter_declaration
type: (type_identifier) @font-lock-type-face)
(function_definition
type: (type_identifier) @font-lock-type-face)
(function_declarator
declarator: (identifier) @font-lock-function-name-face)
(array_declarator
declarator: (identifier) @font-lock-variable-name-face)
(init_declarator
declarator: (identifier) @font-lock-variable-name-face)
(struct_specifier
name: (type_identifier) @font-lock-type-face)
(sized_type_specifier) @font-lock-type-face
(enum_specifier
name: (type_identifier) @font-lock-type-face)
(enumerator
name: (identifier) @font-lock-variable-name-face)
(parameter_declaration
type: (_) @font-lock-type-face
declarator: (identifier) @font-lock-variable-name-face)
(pointer_declarator
declarator: (identifier) @font-lock-variable-name-face)
(pointer_declarator
declarator: (field_identifier) @font-lock-variable-name-face))
:language mode
:override t
:feature 'expression
'((assignment_expression
left: (identifier) @font-lock-variable-name-face)
(call_expression
function: (identifier) @font-lock-function-name-face)
(field_expression
field: (field_identifier) @font-lock-variable-name-face)
(field_expression
argument: (identifier) @font-lock-variable-name-face
field: (field_identifier) @font-lock-variable-name-face)
(pointer_expression
argument: (identifier) @font-lock-variable-name-face))
:language mode
:override t
:feature 'statement
'((expression_statement (identifier) @font-lock-variable-name-face)
(labeled_statement
label: (statement_identifier) @font-lock-type-face))
:language mode
:override t
:feature 'error
'((ERROR) @font-lock-warning-face)))
(defun c-ts-mode--imenu-1 (node)
"Helper for `c-ts-mode--imenu'.
Find string representation for NODE and set marker, then recurse
the subtrees."
(let* ((ts-node (car node))
(subtrees (mapcan #'c-ts-mode--imenu-1 (cdr node)))
(name (when ts-node
(or (treesit-node-text
(or (treesit-node-child-by-field-name
ts-node "declarator")
(treesit-node-child-by-field-name
ts-node "name"))
t)
"Unnamed node")))
(marker (when ts-node
(set-marker (make-marker)
(treesit-node-start ts-node)))))
;; A struct_specifier could be inside a parameter list or another
;; struct definition. In those cases we don't include it.
(cond
((string-match-p
(rx (or "parameter" "field") "_declaration")
(or (treesit-node-type (treesit-node-parent ts-node))
""))
nil)
((null ts-node) subtrees)
(subtrees
`((,name ,(cons name marker) ,@subtrees)))
(t
`((,name . ,marker))))))
(defun c-ts-mode--imenu ()
"Return Imenu alist for the current buffer."
(let* ((node (treesit-buffer-root-node))
(tree (treesit-induce-sparse-tree
node (rx (or "function_definition"
"struct_specifier")))))
(c-ts-mode--imenu-1 tree)))
;;;###autoload
(define-derived-mode c-ts-mode--base-mode prog-mode "C"
"Major mode for editing C, powered by Tree Sitter."
:group 'c
:syntax-table c-ts-mode--syntax-table
;; Comments.
(setq-local comment-start "// ")
(setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
(setq-local comment-end "")
;; Navigation.
(setq-local treesit-defun-type-regexp
(rx (or "specifier"
"definition")))
;; Indent.
(when (eq c-ts-mode-indent-style 'linux)
(setq-local indent-tabs-mode t))
;; Electric
(setq-local electric-indent-chars
(append "{}():;," electric-indent-chars))
;; Imenu.
(setq-local imenu-create-index-function #'c-ts-mode--imenu)
(setq-local which-func-functions nil)
(setq-local treesit-font-lock-feature-list
'((comment preprocessor operator constant string literal keyword)
(type definition expression statement)
(error))))
;;;###autoload
(define-derived-mode c-ts-mode c-ts-mode--base-mode "C"
"Major mode for editing C, powered by Tree Sitter."
:group 'c
(unless (treesit-ready-p nil 'c)
(error "Tree Sitter for C isn't available"))
(treesit-parser-create 'c)
(setq-local treesit-simple-indent-rules
(c-ts-mode--set-indent-style 'c))
;; Font-lock.
(setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'c))
(treesit-major-mode-setup))
;;;###autoload
(define-derived-mode c++-ts-mode c-ts-mode--base-mode "C++"
"Major mode for editing C, powered by Tree Sitter."
:group 'c++
(unless (treesit-ready-p nil 'cpp)
(error "Tree Sitter for C++ isn't available"))
(treesit-parser-create 'cpp)
(setq-local treesit-simple-indent-rules
(c-ts-mode--set-indent-style 'cpp))
;; Font-lock.
(setq-local treesit-font-lock-settings (c-ts-mode--font-lock-settings 'cpp))
(treesit-major-mode-setup))
(provide 'c-ts-mode)
;;; c-ts-mode.el ends here

View file

@ -0,0 +1,131 @@
;;; css-ts-mode.el --- tree sitter support for CSS -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; Author : Theodor Thornhill <theo@thornhill.no>
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
;; Created : November 2022
;; Keywords : css languages tree-sitter
;; This file is part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; 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.
;; This program is distributed in the hope that it will be useful,
;; 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
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'treesit)
(require 'rx)
(require 'css-mode)
(defcustom css-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `ts-mode'."
:type 'integer
:safe 'integerp
:group 'css)
(defvar css-ts-mode--indent-rules
`((css
((node-is "}") parent-bol 0)
((node-is ")") parent-bol 0)
((node-is "]") parent-bol 0)
((parent-is "block") parent-bol css-ts-mode-indent-offset)
((parent-is "arguments") parent-bol css-ts-mode-indent-offset)
((parent-is "declaration") parent-bol css-ts-mode-indent-offset))))
(defvar css-ts-mode--settings
(treesit-font-lock-rules
:language 'css
:feature 'basic
:override t
`((unit) @font-lock-constant-face
(integer_value) @font-lock-builtin-face
(float_value) @font-lock-builtin-face
(plain_value) @font-lock-variable-name-face
(comment) @font-lock-comment-face
(class_selector) @css-selector
(child_selector) @css-selector
(id_selector) @css-selector
(tag_name) @css-selector
(property_name) @css-property
(class_name) @css-selector
(function_name) @font-lock-function-name-face)))
(defun css-ts-mode--imenu-1 (node)
"Helper for `css-ts-mode--imenu'.
Find string representation for NODE and set marker, then recurse
the subtrees."
(let* ((ts-node (car node))
(subtrees (mapcan #'css-ts-mode--imenu-1 (cdr node)))
(name (when ts-node
(if (equal (treesit-node-type ts-node) "tag_name")
(treesit-node-text ts-node)
(treesit-node-text (treesit-node-child ts-node 1) t))))
(marker (when ts-node
(set-marker (make-marker)
(treesit-node-start ts-node)))))
(cond
((null ts-node) subtrees)
(subtrees
`((,name ,(cons name marker) ,@subtrees)))
(t
`((,name . ,marker))))))
(defun css-ts-mode--imenu ()
"Return Imenu alist for the current buffer."
(let* ((node (treesit-buffer-root-node))
(tree (treesit-induce-sparse-tree
node (rx (or "class_selector"
"id_selector"
"tag_name")))))
(css-ts-mode--imenu-1 tree)))
(define-derived-mode css-ts-mode prog-mode "CSS"
"Major mode for editing CSS."
:group 'css
:syntax-table css-mode-syntax-table
(unless (treesit-ready-p nil 'css)
(error "Tree Sitter for CSS isn't available"))
(treesit-parser-create 'css)
;; Comments
(setq-local comment-start "/*")
(setq-local comment-start-skip "/\\*+[ \t]*")
(setq-local comment-end "*/")
(setq-local comment-end-skip "[ \t]*\\*+/")
;; Indent.
(setq-local treesit-simple-indent-rules css-ts-mode--indent-rules)
;; Navigation.
(setq-local treesit-defun-type-regexp "rule_set")
;; Font-lock.
(setq-local treesit-font-lock-settings css-ts-mode--settings)
(setq treesit-font-lock-feature-list '((basic) () ()))
;; Imenu.
(setq-local imenu-create-index-function #'css-ts-mode--imenu)
(setq-local which-func-functions nil) ;; Piggyback on imenu
(treesit-major-mode-setup))
(provide 'css-ts-mode)
;;; css-ts-mode.el ends here

View file

@ -0,0 +1,289 @@
;;; java-ts-mode.el --- tree sitter support for Java -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; Author : Theodor Thornhill <theo@thornhill.no>
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
;; Created : November 2022
;; Keywords : java languages tree-sitter
;; This file is part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; 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.
;; This program is distributed in the hope that it will be useful,
;; 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
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'treesit)
(require 'rx)
(defcustom java-ts-mode-indent-offset 4
"Number of spaces for each indentation step in `java-ts-mode'."
:type 'integer
:safe 'integerp
:group 'java)
(defvar java-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)
table)
"Syntax table for `java-ts-mode'.")
(defvar java-ts-mode--indent-rules
`((java
((parent-is "program") parent-bol 0)
((node-is "}") (and parent parent-bol) 0)
((node-is ")") parent-bol 0)
((node-is "]") parent-bol 0)
((parent-is "class_body") parent-bol java-ts-mode-indent-offset)
((parent-is "interface_body") parent-bol java-ts-mode-indent-offset)
((parent-is "constructor_body") parent-bol java-ts-mode-indent-offset)
((parent-is "enum_body") parent-bol java-ts-mode-indent-offset)
((parent-is "switch_block") parent-bol java-ts-mode-indent-offset)
((parent-is "record_declaration_body") parent-bol java-ts-mode-indent-offset)
((query "(method_declaration (block _ @indent))") parent-bol java-ts-mode-indent-offset)
((query "(method_declaration (block (_) @indent))") parent-bol java-ts-mode-indent-offset)
((parent-is "variable_declarator") parent-bol java-ts-mode-indent-offset)
((parent-is "method_invocation") parent-bol java-ts-mode-indent-offset)
((parent-is "switch_rule") parent-bol java-ts-mode-indent-offset)
((parent-is "ternary_expression") parent-bol java-ts-mode-indent-offset)
((parent-is "element_value_array_initializer") parent-bol java-ts-mode-indent-offset)
((parent-is "function_definition") parent-bol 0)
((parent-is "conditional_expression") first-sibling 0)
((parent-is "assignment_expression") parent-bol 2)
((parent-is "binary_expression") parent 0)
((parent-is "parenthesized_expression") first-sibling 1)
((parent-is "argument_list") parent-bol java-ts-mode-indent-offset)
((parent-is "annotation_argument_list") parent-bol java-ts-mode-indent-offset)
((parent-is "modifiers") parent-bol 0)
((parent-is "formal_parameters") parent-bol java-ts-mode-indent-offset)
((parent-is "formal_parameter") parent-bol 0)
((parent-is "init_declarator") parent-bol java-ts-mode-indent-offset)
((parent-is "if_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "for_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "while_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "switch_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "case_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "labeled_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "do_statement") parent-bol java-ts-mode-indent-offset)
((parent-is "block") (and parent parent-bol) java-ts-mode-indent-offset)))
"Tree-sitter indent rules.")
(defvar java-ts-mode--keywords
'("abstract" "assert" "break" "case" "catch"
"class" "continue" "default" "do" "else"
"enum" "exports" "extends" "final" "finally"
"for" "if" "implements" "import" "instanceof"
"interface" "module" "native" "new" "non-sealed"
"open" "opens" "package" "private" "protected"
"provides" "public" "requires" "return" "sealed"
"static" "strictfp" "switch" "synchronized"
"throw" "throws" "to" "transient" "transitive"
"try" "uses" "volatile" "while" "with" "record")
"C keywords for tree-sitter font-locking.")
(defvar java-ts-mode--operators
'("@" "+" ":" "++" "-" "--" "&" "&&" "|" "||"
"!=" "==" "*" "/" "%" "<" "<=" ">" ">=" "="
"-=" "+=" "*=" "/=" "%=" "->" "^" "^=" "&="
"|=" "~" ">>" ">>>" "<<" "::" "?")
"C operators for tree-sitter font-locking.")
(defvar java-ts-mode--font-lock-settings
(treesit-font-lock-rules
:language 'java
:override t
:feature 'basic
'((identifier) @font-lock-variable-name-face)
:language 'java
:override t
:feature 'comment
`((line_comment) @font-lock-comment-face
(block_comment) @font-lock-comment-face)
:language 'java
:override t
:feature 'constant
`(((identifier) @font-lock-constant-face
(:match "^[A-Z_][A-Z_\\d]*$" @font-lock-constant-face))
(true) @font-lock-constant-face
(false) @font-lock-constant-face)
:language 'java
:override t
:feature 'keyword
`([,@java-ts-mode--keywords] @font-lock-keyword-face
(labeled_statement
(identifier) @font-lock-keyword-face))
:language 'java
:override t
:feature 'operator
`([,@java-ts-mode--operators] @font-lock-builtin-face)
:language 'java
:override t
:feature 'annotation
`((annotation
name: (identifier) @font-lock-constant-face)
(marker_annotation
name: (identifier) @font-lock-constant-face))
:language 'java
:override t
:feature 'string
`((string_literal) @font-lock-string-face)
:language 'java
:override t
:feature 'literal
`((null_literal) @font-lock-constant-face
(decimal_floating_point_literal) @font-lock-constant-face
(hex_floating_point_literal) @font-lock-constant-face)
:language 'java
:override t
:feature 'type
'((interface_declaration
name: (identifier) @font-lock-type-face)
(class_declaration
name: (identifier) @font-lock-type-face)
(record_declaration
name: (identifier) @font-lock-type-face)
(enum_declaration
name: (identifier) @font-lock-type-face)
(constructor_declaration
name: (identifier) @font-lock-type-face)
(field_access
object: (identifier) @font-lock-type-face)
(method_reference (identifier) @font-lock-type-face)
((scoped_identifier name: (identifier) @font-lock-type-face)
(:match "^[A-Z]" @font-lock-type-face))
(type_identifier) @font-lock-type-face
[(boolean_type)
(integral_type)
(floating_point_type)
(void_type)] @font-lock-type-face)
:language 'java
:override t
:feature 'definition
`((method_declaration
name: (identifier) @font-lock-function-name-face)
(formal_parameter
name: (identifier) @font-lock-variable-name-face)
(catch_formal_parameter
name: (identifier) @font-lock-variable-name-face))
:language 'java
:override t
:feature 'expression
'((method_invocation
object: (identifier) @font-lock-variable-name-face)
(method_invocation
name: (identifier) @font-lock-function-name-face)
(argument_list (identifier) @font-lock-variable-name-face)))
"Tree-sitter font-lock settings.")
(defun java-ts-mode--imenu-1 (node)
"Helper for `java-ts-mode--imenu'.
Find string representation for NODE and set marker, then recurse
the subtrees."
(let* ((ts-node (car node))
(subtrees (mapcan #'java-ts-mode--imenu-1 (cdr node)))
(name (when ts-node
(or (treesit-node-text
(or (treesit-node-child-by-field-name
ts-node "name"))
t)
"Unnamed node")))
(marker (when ts-node
(set-marker (make-marker)
(treesit-node-start ts-node)))))
(cond
((null ts-node) subtrees)
(subtrees
`((,name ,(cons name marker) ,@subtrees)))
(t
`((,name . ,marker))))))
(defun java-ts-mode--imenu ()
"Return Imenu alist for the current buffer."
(let* ((node (treesit-buffer-root-node))
(tree (treesit-induce-sparse-tree
node (rx (or "class_declaration"
"interface_declaration"
"enum_declaration"
"record_declaration"
"method_declaration")))))
(java-ts-mode--imenu-1 tree)))
;;;###autoload
(define-derived-mode java-ts-mode prog-mode "Java"
"Major mode for editing Java, powered by Tree Sitter."
:group 'c
:syntax-table java-ts-mode--syntax-table
(unless (treesit-ready-p nil 'java)
(error "Tree-sitter for Java isn't available"))
(treesit-parser-create 'java)
;; Comments.
(setq-local comment-start "// ")
(setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
(setq-local comment-end "")
;; Indent.
(setq-local treesit-simple-indent-rules java-ts-mode--indent-rules)
;; Navigation.
(setq-local treesit-defun-type-regexp
(rx (or "declaration")))
;; Font-lock.
(setq-local treesit-font-lock-settings java-ts-mode--font-lock-settings)
(setq-local treesit-font-lock-feature-list
'((basic comment keyword constant string operator)
(type definition expression literal annotation)
()))
;; Imenu.
(setq-local imenu-create-index-function #'java-ts-mode--imenu)
(setq-local which-func-functions nil) ;; Piggyback on imenu
(treesit-major-mode-setup))
(provide 'java-ts-mode)
;;; java-ts-mode.el ends here

View file

@ -0,0 +1,150 @@
;;; json-ts-mode.el --- tree sitter support for JSON -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; Author : Theodor Thornhill <theo@thornhill.no>
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
;; Created : November 2022
;; Keywords : json languages tree-sitter
;; This file is part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; 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.
;; This program is distributed in the hope that it will be useful,
;; 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
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'treesit)
(require 'rx)
(defcustom json-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `json-ts-mode'."
:type 'integer
:safe 'integerp
:group 'json)
(defvar json-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 ?` "\"" table)
(modify-syntax-entry ?\240 "." table)
table)
"Syntax table for `json-ts-mode'.")
(defvar json-ts--indent-rules
`((json
((node-is "}") parent-bol 0)
((node-is ")") parent-bol 0)
((node-is "]") parent-bol 0)
((parent-is "object") parent-bol json-ts-mode-indent-offset))))
(defvar json-ts-mode--font-lock-settings
(treesit-font-lock-rules
:language 'json
:feature 'minimal
:override t
`((pair
key: (_) @font-lock-string-face)
(string) @font-lock-string-face
(number) @font-lock-constant-face
[(null) (true) (false)] @font-lock-constant-face
(escape_sequence) @font-lock-constant-face
(comment) @font-lock-comment-face))
"Font-lock settings for JSON.")
(defun json-ts-mode--imenu-1 (node)
"Helper for `json-ts-mode--imenu'.
Find string representation for NODE and set marker, then recurse
the subtrees."
(let* ((ts-node (car node))
(subtrees (mapcan #'json-ts-mode--imenu-1 (cdr node)))
(name (when ts-node
(treesit-node-text
(treesit-node-child-by-field-name
ts-node "key")
t)))
(marker (when ts-node
(set-marker (make-marker)
(treesit-node-start ts-node)))))
(cond
((null ts-node) subtrees)
(subtrees
`((,name ,(cons name marker) ,@subtrees)))
(t
`((,name . ,marker))))))
(defun json-ts-mode--imenu ()
"Return Imenu alist for the current buffer."
(let* ((node (treesit-buffer-root-node))
(tree (treesit-induce-sparse-tree
node "pair")))
(json-ts-mode--imenu-1 tree)))
;;;###autoload
(define-derived-mode json-ts-mode prog-mode "JSON"
"Major mode for editing JSON, powered by Tree Sitter."
:group 'json
:syntax-table json-ts-mode--syntax-table
(unless (treesit-ready-p nil 'json)
(error "Tree Sitter for JSON isn't available"))
(treesit-parser-create 'json)
;; Comments.
(setq-local comment-start "// ")
(setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
(setq-local comment-end "")
;; Indent.
(setq-local treesit-simple-indent-rules json-ts--indent-rules)
;; Navigation.
(setq-local treesit-defun-type-regexp
(rx (or "pair" "object")))
;; Font-lock.
(setq-local treesit-font-lock-settings json-ts-mode--font-lock-settings)
(setq-local treesit-font-lock-feature-list
'((minimal) () ()))
;; Imenu.
(setq-local imenu-create-index-function #'json-ts-mode--imenu)
(setq-local which-func-functions nil) ;; Piggyback on imenu
(treesit-major-mode-setup))
(provide 'json-ts-mode)
;;; json-ts-mode.el ends here