From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Yuan Fu Newsgroups: gmane.emacs.devel Subject: Re: TypeScript support for tree-sitter (was Re: Call for volunteers: add tree-sitter support to major modes) Date: Mon, 10 Oct 2022 10:07:07 -0700 Message-ID: References: <83czb1jrm3.fsf@gnu.org> <87h70b8zqs.fsf@thornhill.no> <835ygrhd1p.fsf@gnu.org> <87bkqj8w8n.fsf@thornhill.no> Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3696.120.41.1.1\)) Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="28975"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Eli Zaretskii , emacs-devel@gnu.org To: Theodor Thornhill Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Oct 10 19:28:59 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 1ohwaI-0007MT-Gv for ged-emacs-devel@m.gmane-mx.org; Mon, 10 Oct 2022 19:28:58 +0200 Original-Received: from localhost ([::1]:40312 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ohwaH-0007HJ-FU for ged-emacs-devel@m.gmane-mx.org; Mon, 10 Oct 2022 13:28:57 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:49722) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ohwFH-0006Ez-Mb for emacs-devel@gnu.org; Mon, 10 Oct 2022 13:07:17 -0400 Original-Received: from mail-pf1-x429.google.com ([2607:f8b0:4864:20::429]:38861) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1ohwFE-0001M2-0M; Mon, 10 Oct 2022 13:07:15 -0400 Original-Received: by mail-pf1-x429.google.com with SMTP id p14so6948646pfq.5; Mon, 10 Oct 2022 10:07:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=1tk2gdQalrw85snGSYZrfGwr7RPuLFs394UmJsF/+WE=; b=MQIdGMrIlmlDq2Z7KzrWxrAsDbdcNM286ppnQulJpG/HylkyGap3FBLf6WpsP29zw+ kdu8tyymaj2mX/VZl6y3IGS7M5S9hhZdm9XrSrQMFo2hHNNfhAME6JrBZgMbyCUdsCev nyNvryelze74csZUTDqrFTNgb3RP1r/0bBFPvL5Zc6h2G7neBjqT7oXuNR+0xiHU5ZMP PFgvZUDCuqYUbdEvyF5++gmKyy9o+jexDOSsurFeVpL6+ubhxgOV4sIK0k60yCU7l7DU FUdcXvKezmSf6DjdpgYoR+wtKZYaxL/3prowCw/+T8DvvG2YNW20tO5bm4eKJfi8wZw3 TP4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=1tk2gdQalrw85snGSYZrfGwr7RPuLFs394UmJsF/+WE=; b=2nXooQ64geAh3MM5hioKOg0F3zZal7aWLLN6v1kvJCcLrIMrEK295kk2tEX68JLTIC Jp2WuTat5svzTaOUlmnXvVmQJoKaMi+Ioxw1mXAXxahnwJ2+9c9YTGY1JATm6747KyVF M40qVO5D70kH4jm3+HkIv7S0GC/F7nAxGCDR3L2rSVKFj2GO2CpqFUTJMYED2UEb3BY+ FWmd/zazkkRFmK2bA5Gt27OozGdnCgmBKTqL9GYVmU/XZSaKLdTuUrK4GdBKTFhQKrxO xXGssOlGSwtcYmp2bHrG33FM4PERrZ7E1kFBHCiUS9L2QoTPKtNumwbE+5Owfna0FqQy vs/g== X-Gm-Message-State: ACrzQf3LE3QBRuAoeHmcz6d6BOf6xjde06C4doG7jlbo5vOBc764rWiv 3sRwoUYLQeZMo5MVkkxaq8w= X-Google-Smtp-Source: AMsMyM7lSzPDhL+UiiFGWlesx1JoqO8Hr6K+lJU3nplrqFqFXQD0i1XFaIfmcoCZiI13DzGKfAltlw== X-Received: by 2002:a62:1684:0:b0:554:cade:6970 with SMTP id 126-20020a621684000000b00554cade6970mr20925559pfw.11.1665421629089; Mon, 10 Oct 2022 10:07:09 -0700 (PDT) Original-Received: from smtpclient.apple (cpe-172-117-161-177.socal.res.rr.com. [172.117.161.177]) by smtp.gmail.com with ESMTPSA id u186-20020a6260c3000000b00561c6a4c1b0sm7333843pfb.176.2022.10.10.10.07.08 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 10 Oct 2022 10:07:08 -0700 (PDT) In-Reply-To: <87bkqj8w8n.fsf@thornhill.no> X-Mailer: Apple Mail (2.3696.120.41.1.1) Received-SPF: pass client-ip=2607:f8b0:4864:20::429; envelope-from=casouri@gmail.com; helo=mail-pf1-x429.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=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" Xref: news.gmane.io gmane.emacs.devel:297391 Archived-At: > On Oct 10, 2022, at 9:43 AM, Theodor Thornhill = wrote: >=20 > Eli Zaretskii writes: >=20 >>> From: Theodor Thornhill >>> Cc: Yuan Fu >>> Date: Mon, 10 Oct 2022 17:28:11 +0200 >>>=20 >>> Here's support for TypeScript with tree-sitter. >>=20 >> Thanks, please announce in NEWS. >=20 >=20 > See attached patch. I believe this should adhere to Yuan Fu's = standards > aswell. >=20 > Thanks, >=20 > Theodor >=20 > <0001-Add-TypeScript-support-with-tree-sitter.patch> Thanks! Some very minor comments: =46rom 92138c19ca09b08f7b8aff964542b2c440c7bb8e Mon Sep 17 00:00:00 2001 From: Theodor Thornhill Date: Mon, 10 Oct 2022 17:23:59 +0200 Subject: [PATCH] Add TypeScript support with tree-sitter * lisp/progmodes/typescript-mode.el (typescript-mode): New major mode for TypeScript with support for tree-sitter --- etc/NEWS | 6 + lisp/progmodes/typescript-mode.el | 320 ++++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 lisp/progmodes/typescript-mode.el diff --git a/etc/NEWS b/etc/NEWS index 88b1431d6a..e1f816d4db 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2774,3 +2774,9 @@ Emacs buffers, like indentation and the like. The = new ert function This is a lightweight variant of 'js-mode' that is used by default when visiting JSON files. =20 +=0C +** New mode 'typescript-mode'. +Support is added for TypeScript, based on the new integration with +Tree-Sitter. There's support for font-locking, indentation and +navigation. + I think we should menntion that this mode requires tree-sitter to = function. =0C * Incompatible Lisp Changes in Emacs 29.1 =20 diff --git a/lisp/progmodes/typescript-mode.el = b/lisp/progmodes/typescript-mode.el new file mode 100644 index 0000000000..363f7d150d --- /dev/null +++ b/lisp/progmodes/typescript-mode.el @@ -0,0 +1,8 @@ +;;; typescript-mode.el --- tree sitter support for Typescript -*- = lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author : Theodor Thornhill +;; Maintainer : Theodor Thornhill +;; Created : April 2022 +;; Keywords : typescript languages tree-sitter I think we're suppose to add "This file is part of GNU Emacs" in the header. + +;; 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 = . + +(require 'treesit) + + +(defcustom typescript-mode-indent-offset 2 + "Number of spaces for each indentation step in `typescript-mode'." + :type 'integer + :safe 'integerp + :group 'typescript) + +(defvar typescript-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 ?=3D "." 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 `typescript-mode'.") + Regarding below: again, sorry for the nitpick, but could you wrap lines to 70 columns? +(defvar typescript-mode--indent-rules + `((tsx + ((node-is "}") parent-bol 0) + ((node-is ")") parent-bol 0) + ((node-is "]") parent-bol 0) + ((node-is ">") parent-bol 0) + ((node-is ".") parent-bol ,typescript-mode-indent-offset) + ((parent-is "ternary_expression") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "named_imports") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "statement_block") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "type_arguments") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "variable_declarator") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "arguments") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "array") parent-bol ,typescript-mode-indent-offset) + ((parent-is "formal_parameters") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "template_substitution") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "object_pattern") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "object") parent-bol ,typescript-mode-indent-offset) + ((parent-is "object_type") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "enum_body") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "arrow_function") parent-bol = ,typescript-mode-indent-offset) + ((parent-is "parenthesized_expression") parent-bol = ,typescript-mode-indent-offset) + + ;; JSX + ((parent-is "jsx_opening_element") parent = ,typescript-mode-indent-offset) + ((node-is "jsx_closing_element") parent 0) + ((parent-is "jsx_element") parent ,typescript-mode-indent-offset) + ((node-is "/") parent 0) + ((parent-is "jsx_self_closing_element") parent = ,typescript-mode-indent-offset) + (no-node parent-bol 0)))) + +(defvar typescript-mode--settings + (treesit-font-lock-rules + :language 'tsx + :override t + '( + (template_string) @font-lock-string-face + + ((identifier) @font-lock-constant-face + (:match "^[A-Z_][A-Z_\\d]*$" @font-lock-constant-face)) + + (nested_type_identifier module: (identifier) @font-lock-type-face) + (type_identifier) @font-lock-type-face + (predefined_type) @font-lock-type-face + + (new_expression + constructor: (identifier) @font-lock-type-face) + + (function + name: (identifier) @font-lock-function-name-face) + + (function_declaration + name: (identifier) @font-lock-function-name-face) + + (method_definition + name: (property_identifier) @font-lock-function-name-face) + + (variable_declarator + name: (identifier) @font-lock-function-name-face + value: [(function) (arrow_function)]) + + (variable_declarator + name: (array_pattern + (identifier) + (identifier) @font-lock-function-name-face) + value: (array (number) (function))) + + (assignment_expression + left: [(identifier) @font-lock-function-name-face + (member_expression + property: (property_identifier) = @font-lock-function-name-face)] + right: [(function) (arrow_function)]) + + (call_expression + function: + [(identifier) @font-lock-function-name-face + (member_expression + property: (property_identifier) = @font-lock-function-name-face)]) + + (variable_declarator + name: (identifier) @font-lock-variable-name-face) + + (enum_declaration (identifier) @font-lock-type-face) + + (enum_body (property_identifier) @font-lock-type-face) + + (enum_assignment name: (property_identifier) @font-lock-type-face) + + (assignment_expression + left: [(identifier) @font-lock-variable-name-face + (member_expression + property: (property_identifier) = @font-lock-variable-name-face)]) + + (for_in_statement + left: (identifier) @font-lock-variable-name-face) + + (arrow_function + parameter: (identifier) @font-lock-variable-name-face) + + (arrow_function + parameters: [(_ (identifier) @font-lock-variable-name-face) + (_ (_ (identifier) @font-lock-variable-name-face)) + (_ (_ (_ (identifier) = @font-lock-variable-name-face)))]) + + + (pair key: (property_identifier) @font-lock-variable-name-face) + + (pair value: (identifier) @font-lock-variable-name-face) + + (pair + key: (property_identifier) @font-lock-function-name-face + value: [(function) (arrow_function)]) + + (property_signature + name: (property_identifier) @font-lock-variable-name-face) + + ((shorthand_property_identifier) @font-lock-variable-name-face) + + (pair_pattern key: (property_identifier) = @font-lock-variable-name-face) + + ((shorthand_property_identifier_pattern) = @font-lock-variable-name-face) + + (array_pattern (identifier) @font-lock-variable-name-face) + + (jsx_opening_element + [(nested_identifier (identifier)) (identifier)] + @font-lock-function-name-face) + + (jsx_closing_element + [(nested_identifier (identifier)) (identifier)] + @font-lock-function-name-face) + + (jsx_self_closing_element + [(nested_identifier (identifier)) (identifier)] + @font-lock-function-name-face) + + (jsx_attribute (property_identifier) @font-lock-constant-face) + + [(this) (super)] @font-lock-keyword-face + + [(true) (false) (null)] @font-lock-constant-face + (regex pattern: (regex_pattern)) @font-lock-string-face + (number) @font-lock-constant-face + + (string) @font-lock-string-face + (template_string) @font-lock-string-face + + (template_substitution + ["${" "}"] @font-lock-constant-face) + + ["!" + "abstract" + "as" + "async" + "await" + "break" + "case" + "catch" + "class" + "const" + "continue" + "debugger" + "declare" + "default" + "delete" + "do" + "else" + "enum" + "export" + "extends" + "finally" + "for" + "from" + "function" + "get" + "if" + "implements" + "import" + "in" + "instanceof" + "interface" + "keyof" + "let" + "namespace" + "new" + "of" + "private" + "protected" + "public" + "readonly" + "return" + "set" + "static" + "switch" + "target" + "throw" + "try" + "type" + "typeof" + "var" + "void" + "while" + "with" + "yield" + ] @font-lock-keyword-face + + (comment) @font-lock-comment-face + ))) + +(defun typescript-mode--move-to-node (fn) + (when-let ((found-node + (treesit-parent-until + (treesit-node-at (point)) + (lambda (parent) + (treesit-query-capture + parent + typescript-mode--defun-query))))) + (goto-char (funcall fn found-node)))) + +(defun typescript-mode--beginning-of-defun (&optional _arg) + (typescript-mode--move-to-node #'treesit-node-start)) + +(defun typescript-mode--end-of-defun (&optional _arg) + (typescript-mode--move-to-node #'treesit-node-end)) + +(defvar typescript-mode--defun-query + (treesit-query-compile + 'tsx + "[(import_statement) + (function_declaration) + (type_alias_declaration) + (interface_declaration) + (lexical_declaration)] @defun")) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-mode)) + +(define-derived-mode typescript-mode prog-mode "TypeScript" + "Major mode for editing typescript." + :group 'typescript + :syntax-table typescript-mode--syntax-table + + (unless (or (treesit-can-enable-p) + (treesit-language-available-p 'tsx)) + (error "Tree sitter for TypeScript isn't available.")) + + ;; Comments + (setq-local comment-start "// ") + (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") + (setq-local comment-end "") + + (setq-local treesit-simple-indent-rules = typescript-mode--indent-rules) + (setq-local indent-line-function #'treesit-indent) + + (setq-local beginning-of-defun-function = #'typescript-mode--beginning-of-defun) + (setq-local end-of-defun-function #'typescript-mode--end-of-defun) + + (unless font-lock-defaults + (setq font-lock-defaults '(nil t))) + + (setq-local treesit-font-lock-settings typescript-mode--settings) + + (treesit-font-lock-enable)) + +(provide 'typescript-mode) + +;;; typescript-mode.el ends here --=20 2.34.1