~theo/tree-sitter-modes

a91051b4096aebfc8a8a417bea3527e8f3746f03 — Theodor Thornhill 8 days ago
Add go-mode and typescript-mode
3 files changed, 572 insertions(+), 0 deletions(-)

A README.md
A go-mode.el
A typescript-mode.el
A  => README.md +16 -0
@@ 1,16 @@
# tree sitter modes

This repo tries to implement as many major modes as possible for use with the
new feature branch version of tree sitter for emacs.  We implement font locking
and indentation.

## Note on support
Supporting modes that have an emacs native mode is out of scope for this
package.  That should be handled by emacs proper.  Also, this mode only supports
tree-sitter related functionality.  If your mode does a lot more than indenting,
navigation and font locking, consider using that instead.

## included modes

- typescript
- go

A  => go-mode.el +236 -0
@@ 1,236 @@
;;; go-mode.el --- tree sitter support for Golang  -*- lexical-binding: t; -*-

;; Copyright (C) Theodor Thornhill

;; Author     : Theodor Thornhill <theo@thornhill.no>
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
;; Created    : April 2022
;; Modified   : 2022
;; Version    : 0.1
;; Keywords   : go golang languages tree-sitter
;; X-URL      : https://git.sr.ht/~theo/tree-sitter-modes
;; Package-Requires: ((emacs "29.1"))

;; 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/>.

(require 'treesit)


(defvar go-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 `go-mode'.")

(defun go-backward-up-list ()
  (lambda (node parent bol &rest _)
    (save-excursion
      (backward-up-list 1 nil t)
      (goto-char
       (treesit-node-start
        (treesit-node-at (point) (point) 'go)))
      (back-to-indentation)
      (treesit-node-start
       (treesit-node-at (point) (point) 'go)))))

(defvar go-indent-rules
  `((go
     (no-node (go-backward-up-list) 4)
     ((node-is "}") parent-bol 0)
     ((node-is ")") parent-bol 0)
     ((node-is "]") parent-bol 0)
     ((parent-is "source_file") parent-bol 0)
     ((parent-is "short_var_declaration") parent-bol 4)
     ((parent-is "parameter_list") parent-bol 4)
     ((parent-is "assignment_statement") parent-bol 4)
     ((parent-is "import_spec_list") parent-bol 4)
     ((parent-is "field_declaration_list") parent-bol 4)
     ((parent-is "selector_expression") parent-bol 4)
     ((parent-is "argument_list") parent-bol 4)
     ((parent-is "literal_value") parent-bol 4)
     ((parent-is "block") parent-bol 4)
     )))

(defvar go-font-lock-settings-1
  '((go
     (
      (interpreted_string_literal) @font-lock-string-face
      (raw_string_literal) @font-lock-string-face
      (type_identifier) @font-lock-type-face
      (package_identifier) @font-lock-type-face

      (var_spec name: (identifier) @font-lock-variable-name-face)
      (short_var_declaration left: (expression_list (identifier)) @font-lock-variable-name-face)
      (function_declaration name: (identifier) @font-lock-function-name-face)

      (parameter_declaration (identifier) @font-lock-variable-name-face)

      (field_declaration name: (field_identifier) @font-lock-variable-name-face)

      (call_expression
       function: (identifier) @font-lock-function-name-face)

      (call_expression
       function: (selector_expression
                  field: (field_identifier) @font-lock-function-name-face))

      (function_declaration
       name: (identifier) @font-lock-function-name-face)

      (method_declaration
       name: (field_identifier) @font-lock-function-name-face)


      (const_spec
       name: (identifier) @font-lock-constant-face)

      [(true) (false) (nil) (int_literal) (float_literal)] @font-lock-constant-face
      
      ["!"
       "!="
       "%"
       "%="
       "&"
       "&&"
       "&="
       "*"
       "*"
       "*="
       "+"
       "++"
       "+="
       "-"
       "--"
       "-="
       "..."
       "/"
       "/="
       ":="
       "<"
       "<-"
       "<<"
       "<<="
       "<="
       "="
       "=="
       ">"
       ">="
       ">>"
       ">>="
       "^"
       "^="
       "|"
       "|="
       "||"
       "~"
       ] @font-lock-builtin-face

      ["break"
       "default"
       "func"
       "interface"
       "select"
       "case"
       "defer"
       "go"
       "map"
       "struct"
       "chan"
       "else"
       "goto"
       "package"
       "switch"
       "const"
       "fallthrough"
       "if"
       "range"
       "type"
       "continue"
       "for"
       "import"
       "return"
       "var"] @font-lock-keyword-face

      (comment) @font-lock-comment-face
      ))))

(defun go-move-to-node (fn)
  (when-let ((found-node (treesit-parent-until
                          (treesit-node-at (point) (point) 'go)
                          (lambda (parent)
                            (let ((parent-type (treesit-node-type parent)))
                              (or (equal "function_declaration" parent-type)
                                  (equal "interface_declaration" parent-type)))))))
    (goto-char (funcall fn found-node))))

(defun go-beginning-of-defun (&optional arg)
  (go-move-to-node #'treesit-node-start))

(defun go-end-of-defun (&optional arg)
  (go-move-to-node #'treesit-node-end))


(define-derived-mode go-mode prog-mode "go"
  "Major mode for editing typescript.

Key bindings:

\\{go-map}"

  :group 'go
  :syntax-table go-syntax-table

  (unless (or (treesit-should-enable-p)
              (treesit-language-available-p 'go))
    (error "Tree sitter isn't available.  Did you compile emacs from the feature/tree-sitter branch?"))

  ;; Comments
  (setq-local comment-start "// ")
  (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
  (setq-local comment-end "")

  (treesit-get-parser-create 'go)
  (setq-local treesit-simple-indent-rules go-indent-rules)
  (setq-local indent-line-function #'treesit-indent)
  (setq-local beginning-of-defun-function #'go-beginning-of-defun)
  (setq-local end-of-defun-function #'go-end-of-defun)

  ;; This needs to be non-nil, because reasons
  (unless font-lock-defaults
    (setq font-lock-defaults '(nil t)))

  (setq-local treesit-font-lock-defaults
              '((go-font-lock-settings-1)))

  (treesit-font-lock-enable))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.go\\'" . go-mode))

(provide 'go-mode)

;;; go-mode.el ends here

A  => typescript-mode.el +320 -0
@@ 1,320 @@
;;; typescript-mode.el --- tree sitter support for Typescript  -*- lexical-binding: t; -*-

;; Copyright (C) Theodor Thornhill

;; Author     : Theodor Thornhill <theo@thornhill.no>
;; Maintainer : Theodor Thornhill <theo@thornhill.no>
;; Created    : April 2022
;; Modified   : 2022
;; Version    : 0.1
;; Keywords   : typescript languages tree-sitter
;; X-URL      : https://git.sr.ht/~theo/tree-sitter-modes
;; Package-Requires: ((emacs "29.1"))

;; 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/>.

(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 ?=  "."     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-mode'.")

(defun ts-backward-up-list ()
  (lambda (node parent bol &rest _)
    (save-excursion
      (backward-up-list 1 nil t)
      (goto-char
       (treesit-node-start
        (treesit-node-at (point) (point) 'tsx)))
      (back-to-indentation)
      (treesit-node-start
       (treesit-node-at (point) (point) 'tsx)))))

(defvar typescript-mode-indent-rules
  `((tsx
     (no-node (ts-backward-up-list) ,typescript-mode-indent-offset)
     ((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 "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)
     ;; TODO(Theo): This one is a little off.  Meant to hit the dangling '/' in
     ;; a jsx-element.  But it is also division operator...
     ((node-is "/") parent 0)
     ((parent-is "jsx_self_closing_element") parent ,typescript-mode-indent-offset))))

(defvar typescript-mode-font-lock-settings-1
  '((tsx
     (
      ((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))
      (number) @font-lock-constant-face

      (string) @font-lock-string-face

      ;; template strings need to be last in the file for embedded expressions
      ;; to work properly
      (template_string) @font-lock-string-face

      (template_substitution
       "${" @font-lock-constant-face
       (_)
       "}" @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) (point) 'tsx)
                          (lambda (parent)
                            (let ((parent-type (treesit-node-type parent)))
                              (or (equal "function_declaration" parent-type)
                                  (equal "interface_declaration" parent-type)))))))
    (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))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode-mode))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-mode-mode))

(define-derived-mode typescript-mode-mode prog-mode "typescriptreact"
  "Major mode for editing typescript.

Key bindings:

\\{typescript-mode-map}"

  :group 'typescript
  :syntax-table typescript-mode-syntax-table

  (unless (or (treesit-should-enable-p)
              (treesit-language-available-p 'tsx))
    (error "Tree sitter isn't available.  Did you compile emacs from the feature/tree-sitter branch?"))

  ;; Comments
  (setq-local comment-start "// ")
  (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
  (setq-local comment-end "")

  (treesit-get-parser-create 'tsx)
  (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)

  ;; This needs to be non-nil, because reasons
  (unless font-lock-defaults
    (setq font-lock-defaults '(nil t)))

  (setq-local treesit-font-lock-defaults
              '((typescript-mode-font-lock-settings-1)))

  (treesit-font-lock-enable))

(provide 'typescript-mode)

;;; typescript-mode.el ends here