From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Theodor Thornhill via "Emacs development discussions." Newsgroups: gmane.emacs.devel Subject: Re: Tree sitter support for C-like languages Date: Sat, 12 Nov 2022 08:22:29 +0100 Message-ID: <87sfiozlfu.fsf@thornhill.no> References: <87tu36em9t.fsf@thornhill.no> <45FD2F78-F15B-488B-9348-A8E298D8AD35@gmail.com> <87v8nmyqqp.fsf@thornhill.no> Reply-To: Theodor Thornhill Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="36333"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Yuan Fu Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Nov 12 08:23:31 2022 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1otkrS-0009F6-K3 for ged-emacs-devel@m.gmane-mx.org; Sat, 12 Nov 2022 08:23:30 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1otkqm-0000kF-6j; Sat, 12 Nov 2022 02:22:48 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1otkqj-0000ji-QD for emacs-devel@gnu.org; Sat, 12 Nov 2022 02:22:45 -0500 Original-Received: from out0.migadu.com ([2001:41d0:2:267::]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1otkqe-0008Pc-9V for emacs-devel@gnu.org; Sat, 12 Nov 2022 02:22:45 -0500 X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=thornhill.no; s=key1; t=1668237757; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=bv2OvyZdd2o2lL2636Wf8Ee9TfWARmlblD51a0/BQ1k=; b=MqgMvtraZsDeTDLDnDuPswnMwFUVY8NugJmm+wrME18s4tTUikblCiwi9U+jpLK9VoBbmh wNZ/hJufhMbcjMIu35oe8WXfMUIJoyQQbW69OUfug8LNVBhlAc2dDfPKIx7U+b/PeuMwth bJnorQ0YmgfMr86xyHarxhB8cVwBGUCVh8PEmLBf4pjOJc/Uz64HdvF06t4E9HSEpN/cBk fsTOGOBtiGbwTLHnrEnupwHBlwpdJX6PBP9smiegEZLXEoUCHI9u9tZUZybc/vnGruPxle RIbvgEUzyNI9xk679vs9dH0U8/wQVqbmTRtQ6WP2cgVQn4bVx+uBEj2/9Xblrg== In-Reply-To: X-Migadu-Flow: FLOW_OUT Received-SPF: pass client-ip=2001:41d0:2:267::; envelope-from=theo@thornhill.no; helo=out0.migadu.com X-Spam_score_int: -23 X-Spam_score: -2.4 X-Spam_bar: -- X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=0.1, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:299617 Archived-At: --=-=-= Content-Type: text/plain > > I noticed that with the default indent style, Emacs indents like this: > > int main () { > for (int j = 0; j < 5; j++) > a[j] = 3; > int i = 1; > swap(i, a[i+1]); > Point p = {0, 1}; > } > > int main () > { > for (int j = 0; j < 5; j++) > a[j] = 3; > int i = 1; > swap(i, a[i+1]); > Point p = {0, 1}; > } > > Is this expected? Try this one instead: --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-Add-Tree-Sitter-modes-for-C-like-languages.patch >From dbcd64db88a665393297996fba165b8adc66ba50 Mon Sep 17 00:00:00 2001 From: Theodor Thornhill Date: Thu, 10 Nov 2022 17:15:49 +0100 Subject: [PATCH] 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. * lisp/progmodes/c-ts-mode.el: New major mode with Tree Sitter support. * 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. --- etc/NEWS | 28 ++- lisp/progmodes/c++-ts-mode.el | 424 +++++++++++++++++++++++++++++++++ lisp/progmodes/c-ts-mode.el | 414 ++++++++++++++++++++++++++++++++ lisp/progmodes/css-ts-mode.el | 131 ++++++++++ lisp/progmodes/java-ts-mode.el | 289 ++++++++++++++++++++++ lisp/progmodes/json-ts-mode.el | 150 ++++++++++++ 6 files changed, 1434 insertions(+), 2 deletions(-) create mode 100644 lisp/progmodes/c++-ts-mode.el create mode 100644 lisp/progmodes/c-ts-mode.el create mode 100644 lisp/progmodes/css-ts-mode.el create mode 100644 lisp/progmodes/java-ts-mode.el create mode 100644 lisp/progmodes/json-ts-mode.el diff --git a/etc/NEWS b/etc/NEWS index 9ed78bc6b3..3ce9810ece 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2786,8 +2786,32 @@ 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'. +navigation. Tree-Sitter is required for this mode to function. + +** New mode c-ts-mode'. +Support is added for C, 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. + +** New mode c++-ts-mode'. +Support is added for c++, 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. + +** New mode java-ts-mode'. +Support is added for Java, 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. + +** New mode css-ts-mode'. +Support is added for CSS, 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. + +** New mode json-ts-mode'. +Support is added for JSON, 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. * Incompatible Lisp Changes in Emacs 29.1 diff --git a/lisp/progmodes/c++-ts-mode.el b/lisp/progmodes/c++-ts-mode.el new file mode 100644 index 0000000000..7e3bb7d141 --- /dev/null +++ b/lisp/progmodes/c++-ts-mode.el @@ -0,0 +1,424 @@ +;;; c++-ts-mode.el --- tree sitter support for C++ -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author : Theodor Thornhill +;; Maintainer : Theodor Thornhill +;; Created : November 2022 +;; Keywords : c++ 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 . + + +;;; 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 'cpp) + +(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 'cpp) + +(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'.") + +(defvar c++-ts-mode--indent-styles + (let ((common + `(((parent-is "translation_unit") parent-bol 0) + ((node-is ")") parent 1) + ((node-is "]") parent-bol 0) + ((node-is "else") parent-bol 0) + ((node-is "case") parent-bol 0) + ((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 "field_declaration_list") parent-bol c++-ts-mode-indent-offset) + ((node-is "field_initializer_list") parent-bol ,(* c++-ts-mode-indent-offset 2)) + ((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)))) + `((gnu + ,@common + ((node-is "}") parent-bol 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 "compound_statement") 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) + ((match "while" "do_statement") parent 0) + ((parent-is "do_statement") parent-bol c++-ts-mode-indent-offset) + (no-node parent-bol c++-ts-mode-indent-offset)) + (k&r + ,@common + ((node-is "}") grand-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 "compound_statement") grand-parent 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) + (no-node parent-bol c++-ts-mode-indent-offset)) + (linux + ,@common + ((node-is "}") grand-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 "compound_statement") grand-parent 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) + (no-node parent-bol c++-ts-mode-indent-offset)) + (bsd + ,@common + ((node-is "}") parent-bol 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 "compound_statement") parent 0) + ((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) + (no-node parent-bol c++-ts-mode-indent-offset)))) + "Indent rules supported by `c++-ts-mode'.") + +(defun c++-ts-mode--set-indent-style () + "Helper function to set indentation style." + (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)) + ('k&r (alist-get 'k&r c++-ts-mode--indent-styles)) + ('bsd (alist-get 'bsd c++-ts-mode--indent-styles)) + ('linux (alist-get 'linux c++-ts-mode--indent-styles)))))) + `((cpp ,@style)))) + +(defvar c++-ts-mode--keywords + '("and" "and_eq" "bitand" "bitor" "catch" "class" "co_await" + "co_return" "co_yield" "compl" "concept" "consteval" "constexpr" + "constinit" "decltype" "delete" "else" "explicit" "final" "for" + "friend" "friend" "if" "mutable" "namespace" "new" "noexcept" + "not" "not_eq" "operator" "or" "or_eq" "override" "private" + "protected" "public" "requires" "return" "static" "struct" + "template" "throw" "try" "typename" "using" "virtual" "xor" "xor_eq" + "switch" "case") + "C++ keywords for tree-sitter font-locking.") + +(defvar c++-ts-mode--preproc-keywords + '("#define" "#if" "#ifdef" "#ifndef" "#else" "#elif" "#endif" "#include") + "C++ keywords for tree-sitter font-locking.") + +(defvar c++-ts-mode--operators + '("=" "-" "*" "/" "+" "%" "~" "|" "&" "^" "<<" ">>" "->" + "." "<" "<=" ">=" ">" "==" "!=" "!" "&&" "||" "-=" + "+=" "*=" "/=" "%=" "|=" "&=" "^=" ">>=" "<<=" "--" "++") + "C++ operators for tree-sitter font-locking.") + +(defvar c++-ts-mode--font-lock-settings + (treesit-font-lock-rules + :language 'cpp + :override t + :feature 'comment + `((comment) @font-lock-comment-face + (comment) @contexual) + :language 'cpp + :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 'cpp + :override t + :feature 'constant + `((true) @font-lock-constant-face + (false) @font-lock-constant-face + (null) @font-lock-constant-face + (this) @font-lock-constant-face) + :language 'cpp + :override t + :feature 'keyword + `([,@c++-ts-mode--keywords] @font-lock-keyword-face + (auto) @font-lock-keyword-face) + :language 'cpp + :override t + :feature 'operator + `([,@c++-ts-mode--operators] @font-lock-builtin-face) + :language 'cpp + :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 'cpp + :override t + :feature 'literal + `((number_literal) @font-lock-constant-face + (char_literal) @font-lock-constant-face) + :language 'cpp + :override t + :feature 'type + '((primitive_type) @font-lock-type-face + (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 'cpp + :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) + + (parameter_declaration + type: (type_identifier) @font-lock-type-face) + + (function_definition + type: (type_identifier) @font-lock-function-name-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 'cpp + :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 'cpp + :override t + :feature 'statement + '((expression_statement (identifier) @font-lock-variable-name-face) + (labeled_statement + label: (statement_identifier) @font-lock-type-face)) + :language 'cpp + :override t + :feature 'error + '((ERROR) @font-lock-warning-face)) + "Tree-sitter font-lock settings.") + +(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 prog-mode "C++" + "Major mode for editing C++, powered by Tree Sitter." + :group 'cpp + :syntax-table c++-ts-mode--syntax-table + + (unless (treesit-ready-p nil 'cpp) + (error "Tree Sitter for C++ isn't available")) + + (treesit-parser-create 'cpp) + + ;; Comments. + (setq-local comment-start "// ") + (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") + (setq-local comment-end "") + + ;; Indent. + (when (eq c++-ts-mode-indent-style 'linux) + (setq-local indent-tabs-mode t)) + (setq-local treesit-simple-indent-rules + (c++-ts-mode--set-indent-style)) + + ;; Navigation. + (setq-local treesit-defun-type-regexp + (rx (or "specifier" + "definition"))) + + ;; Electric + (setq-local electric-indent-chars + (append "{}():;," electric-indent-chars)) + + ;; Font-lock. + (setq-local treesit-font-lock-settings c++-ts-mode--font-lock-settings) + (setq-local treesit-font-lock-feature-list + '((comment preprocessor operator constant string literal keyword) + (type definition expression statement) + (error))) + + ;; Imenu. + (setq-local imenu-create-index-function #'c++-ts-mode--imenu) + (setq-local which-func-functions nil) ;; Piggyback on imenu + (treesit-major-mode-setup)) + +(provide 'c++-ts-mode) + +;;; c++-ts-mode.el ends here diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el new file mode 100644 index 0000000000..d99d863d60 --- /dev/null +++ b/lisp/progmodes/c-ts-mode.el @@ -0,0 +1,414 @@ +;;; c-ts-mode.el --- tree sitter support for C -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author : Theodor Thornhill +;; Maintainer : Theodor Thornhill +;; Created : November 2022 +;; Keywords : c 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 . + + +;;; 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'.") + +(defvar c-ts-mode--indent-styles + (let ((common + `(((parent-is "translation_unit") parent-bol 0) + ((node-is ")") parent 1) + ((node-is "]") parent-bol 0) + ((node-is "else") parent-bol 0) + ((node-is "case") parent-bol 0) + ((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 "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)))) + `((gnu + ,@common + ((node-is "}") parent-bol 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 "compound_statement") 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) + ((match "while" "do_statement") parent 0) + ((parent-is "do_statement") parent-bol c-ts-mode-indent-offset) + (no-node parent-bol c-ts-mode-indent-offset)) + (k&r + ,@common + ((node-is "}") grand-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 "compound_statement") grand-parent 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) + (no-node parent-bol c-ts-mode-indent-offset)) + (linux + ,@common + ((node-is "}") grand-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 "compound_statement") grand-parent 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) + (no-node parent-bol c-ts-mode-indent-offset)) + (bsd + ,@common + ((node-is "}") parent-bol 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 "compound_statement") parent 0) + ((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) + (no-node parent-bol c-ts-mode-indent-offset)))) + "Indent rules supported by `c-ts-mode'.") + +(defun c-ts-mode--set-indent-style () + "Helper function to set indentation style." + (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)) + ('k&r (alist-get 'k&r c-ts-mode--indent-styles)) + ('bsd (alist-get 'bsd c-ts-mode--indent-styles)) + ('linux (alist-get 'linux c-ts-mode--indent-styles)))))) + `((c ,@style)))) + +(defvar c-ts-mode--keywords + '("const" "default" "enum" "extern" "inline" "static" + "struct" "typedef" "union" "volatile" "goto" "register" + "sizeof" "return" + "while" "for" "do" "continue" "break" + "if" "else" "case" "switch") + "C keywords for tree-sitter font-locking.") + +(defvar c-ts-mode--preproc-keywords + '("#define" "#if" "#ifdef" "#ifndef" "#else" "#elif" "#endif" + "#include") + "C keywords for tree-sitter font-locking.") + +(defvar c-ts-mode--operators + '("=" "-" "*" "/" "+" "%" "~" "|" "&" "^" "<<" ">>" "->" + "." "<" "<=" ">=" ">" "==" "!=" "!" "&&" "||" "-=" + "+=" "*=" "/=" "%=" "|=" "&=" "^=" ">>=" "<<=" "--" "++") + "C operators for tree-sitter font-locking.") + +(defvar c-ts-mode--font-lock-settings + (treesit-font-lock-rules + :language 'c + :override t + :feature 'comment + `((comment) @font-lock-comment-face + (comment) @contexual) + :language 'c + :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 'c + :override t + :feature 'constant + `((true) @font-lock-constant-face + (false) @font-lock-constant-face + (null) @font-lock-constant-face) + :language 'c + :override t + :feature 'keyword + `([,@c-ts-mode--keywords] @font-lock-keyword-face) + :language 'c + :override t + :feature 'operator + `([,@c-ts-mode--operators] @font-lock-builtin-face) + :language 'c + :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 'c + :override t + :feature 'literal + `((number_literal) @font-lock-constant-face + (char_literal) @font-lock-constant-face) + :language 'c + :override t + :feature 'type + '((primitive_type) @font-lock-type-face) + :language 'c + :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 'c + :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 'c + :override t + :feature 'statement + '((expression_statement (identifier) @font-lock-variable-name-face)) + :language 'c + :override t + :feature 'error + '((ERROR) @font-lock-warning-face)) + "Tree-sitter font-lock settings.") + +(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 prog-mode "C" + "Major mode for editing C, powered by Tree Sitter." + :group 'c + :syntax-table c-ts-mode--syntax-table + + (unless (treesit-ready-p nil 'c) + (error "Tree Sitter for C isn't available")) + + (treesit-parser-create 'c) + + ;; Comments. + (setq-local comment-start "// ") + (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") + (setq-local comment-end "") + + ;; Indent. + (when (eq c-ts-mode-indent-style 'linux) + (setq-local indent-tabs-mode t)) + (setq-local treesit-simple-indent-rules + (c-ts-mode--set-indent-style)) + + ;; Navigation. + (setq-local treesit-defun-type-regexp + (rx (or "specifier" + "definition"))) + + ;; Electric + (setq-local electric-indent-chars + (append "{}():;," electric-indent-chars)) + + ;; Font-lock. + (setq-local treesit-font-lock-settings c-ts-mode--font-lock-settings) + (setq-local treesit-font-lock-feature-list + '((comment preprocessor operator constant string literal keyword) + (type definition expression statement) + (error))) + + ;; Imenu. + (setq-local imenu-create-index-function #'c-ts-mode--imenu) + (setq-local which-func-functions nil) ;; Piggyback on imenu + (treesit-major-mode-setup)) + +(provide 'c-ts-mode) + +;;; c-ts-mode.el ends here diff --git a/lisp/progmodes/css-ts-mode.el b/lisp/progmodes/css-ts-mode.el new file mode 100644 index 0000000000..c1a8d4e94d --- /dev/null +++ b/lisp/progmodes/css-ts-mode.el @@ -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 +;; Maintainer : Theodor Thornhill +;; 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 . + + +;;; 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 diff --git a/lisp/progmodes/java-ts-mode.el b/lisp/progmodes/java-ts-mode.el new file mode 100644 index 0000000000..734a8be471 --- /dev/null +++ b/lisp/progmodes/java-ts-mode.el @@ -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 +;; Maintainer : Theodor Thornhill +;; 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 . + + +;;; 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 diff --git a/lisp/progmodes/json-ts-mode.el b/lisp/progmodes/json-ts-mode.el new file mode 100644 index 0000000000..13eb5b78a9 --- /dev/null +++ b/lisp/progmodes/json-ts-mode.el @@ -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 +;; Maintainer : Theodor Thornhill +;; 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 . + + +;;; 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 -- 2.34.1 --=-=-=--