Skip to content

Commit 5bd2305

Browse files
josteinktheothornhill
authored andcommitted
Implement experimental TSX-support via tree-sitter, if available.
Implement support using recipe suggested by @josemiguelo in this comment: #4 (comment)
1 parent 4f056f6 commit 5bd2305

File tree

5 files changed

+571
-0
lines changed

5 files changed

+571
-0
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,25 @@ packages:
8282

8383
Initializing these with `typescript.el` will then become a matter of
8484
creating your own `typescript-mode-hook` in your `init.el` file.
85+
86+
87+
# Tree sitter integration
88+
For now we have integration with both tree-sitter implementations. The oldest
89+
one, the rust variant by @ubolonton is defined in `typescript-tree-sitter.el`,
90+
and the newer, emacs feature branch variant is defined in
91+
`typescript-ts.el`. The former requires the tree-sitter packages from MELPA, and
92+
the latter requires an emacs built from the feature/tree-sitter upstream branch.
93+
94+
Steps to get things working for now:
95+
96+
1. Get emacs from the feature branch:
97+
- `git clone -b feature/tree-sitter git://git.sv.gnu.org/emacs.git`
98+
- `cd emacs && make bootstrap`
99+
100+
2. From the typescript repo
101+
- run `install-tsx.sh `. If this script doesn't work for you try to find some
102+
guide on the interwebs and report back to me.
103+
- move the compiled file from `dist/` to `~/emacs/tree-sitter`
104+
105+
3. enable `typescript-ts-mode` in some `.tsx` or `.ts` file and off you go
106+

install-tsx.sh

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
3+
lang=$1
4+
5+
if [ $(uname) == "Darwin" ]
6+
then
7+
soext="dylib"
8+
else
9+
soext="so"
10+
fi
11+
12+
# Retrieve sources.
13+
git clone "https://github.com/tree-sitter/tree-sitter-typescript.git" \
14+
--depth 1
15+
cd "tree-sitter-typescript/tsx/src"
16+
17+
# Build.
18+
cc -c -I. parser.c
19+
# Compile scanner.c.
20+
cc -fPIC -c -I. scanner.c
21+
# Link.
22+
cc -fPIC -shared *.o -o "libtree-sitter-tsx.${soext}"
23+
24+
mkdir -p ../../../dist
25+
cp "libtree-sitter-tsx.${soext}" ../../../dist
26+
cd ../../../
27+
rm -rf "tree-sitter-tsx"

typescript-mode.el

+11
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,17 @@ Key bindings:
29832983
;;;###autoload
29842984
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode))
29852985

2986+
;;;###autoload
2987+
(ignore-errors
2988+
;; experimental TSX-support via tree-sitter, if available
2989+
(require 'tree-sitter)
2990+
(require 'tree-sitter-langs)
2991+
(define-derived-mode typescript-tsx-mode typescript-mode "tsx")
2992+
(add-hook typescript-tsx-mode-hook #'tree-sitter-hl-mode)
2993+
(add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx))
2994+
(add-to-list 'auto-mode-alist '("\\.tsx?\\'" . typescript-tsx-mode)))
2995+
2996+
29862997
(provide 'typescript-mode)
29872998

29882999
;;; typescript-mode.el ends here

typescript-tree-sitter.el

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
;;; typescript-tree-sitter.el --- tree sitter support for Typescript -*- lexical-binding: t; -*-
2+
3+
;; Copyright (C) Theodor Thornhill
4+
5+
;; Author : Theodor Thornhill <[email protected]>
6+
;; Maintainer : Jostein Kjønigsen <[email protected]>
7+
;; Theodor Thornhill <[email protected]>
8+
;; Created : April 2022
9+
;; Modified : 2022
10+
;; Version : 0.4
11+
;; Keywords : typescript languages
12+
;; X-URL : https://github.com/emacs-typescript/typescript.el
13+
;; Package-Requires: ((emacs "26.1") (tree-sitter "0.12.1") (tree-sitter-indent "0.1") (tree-sitter-langs "0.9.1"))
14+
15+
;; This program is free software; you can redistribute it and/or modify
16+
;; it under the terms of the GNU General Public License as published by
17+
;; the Free Software Foundation, either version 3 of the License, or
18+
;; (at your option) any later version.
19+
20+
;; This program is distributed in the hope that it will be useful,
21+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+
;; GNU General Public License for more details.
24+
25+
;; You should have received a copy of the GNU General Public License
26+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
27+
28+
;; Note about indentation:
29+
;; The indentation mechanics are adapted from Felipe Lemas tree-sitter-indent
30+
;; package. We don't need a generic solution for now, because we are waiting
31+
;; for Emacs proper. Let's just make it work for us, then move over to emacs
32+
;; when that is ready. No need for a dependency.
33+
34+
;;; Code:
35+
(require 'cl-lib)
36+
(require 'seq)
37+
(require 'subr-x)
38+
39+
(when t
40+
;; In order for the package to be usable and installable (and hence
41+
;; compilable) without tree-sitter, wrap the `require's within a dummy `when'
42+
;; so they're only executed when loading this file but not when compiling it.
43+
44+
(require 'tree-sitter)
45+
(require 'tree-sitter-hl)
46+
(require 'tree-sitter-langs))
47+
;; Vars and functions defined by the above packages:
48+
(defvar tree-sitter-major-mode-language-alist)
49+
(declare-function tree-sitter-hl-mode "ext:tree-sitter-hl")
50+
(declare-function tsc-node-end-position "ext:tree-sitter")
51+
(declare-function tsc-node-start-position "ext:tree-sitter")
52+
53+
(defvar typescript-tree-sitter-syntax-table)
54+
(defvar typescript-tree-sitter-map)
55+
56+
(defvar typescript-tree-sitter-scopes
57+
'((indent
58+
;; if parent node is one of these and node is not first → indent
59+
. (try_statement
60+
if_statement
61+
object
62+
template_substitution
63+
;; function_declaration
64+
interface_declaration
65+
lexical_declaration
66+
expression_statement
67+
return_statement
68+
named_imports
69+
arguments
70+
jsx_self_closing_element
71+
jsx_element
72+
jsx_opening_element))
73+
(outdent
74+
;; these nodes always outdent (1 shift in opposite direction)
75+
. (")"
76+
"}"
77+
"]"
78+
"/")))
79+
"Current scopes in use for tree-sitter-indent.")
80+
81+
(defun typescript-tree-sitter--indent (node)
82+
(let-alist typescript-tree-sitter-scopes
83+
(member (tsc-node-type node) .indent)))
84+
85+
(defun typescript-tree-sitter--outdent (node)
86+
(let-alist typescript-tree-sitter-scopes
87+
(member (tsc-node-type node) .outdent)))
88+
89+
(defun typescript-tree-sitter--highest-node-at-position (position)
90+
(save-excursion
91+
(goto-char position)
92+
(let ((current-node (tree-sitter-node-at-pos)))
93+
(while (and
94+
current-node
95+
(when-let ((parent-node (tsc-get-parent current-node)))
96+
(when (and ;; parent and current share same position
97+
(eq (tsc-node-start-byte parent-node)
98+
(tsc-node-start-byte current-node)))
99+
(setq current-node parent-node)))))
100+
current-node)))
101+
102+
(defun typescript-tree-sitter--parentwise-path (node)
103+
(let ((path (list node))
104+
(next-parent-node (tsc-get-parent node)))
105+
(while next-parent-node
106+
(push next-parent-node path)
107+
(setq next-parent-node (tsc-get-parent next-parent-node)))
108+
path))
109+
110+
(cl-defun typescript-tree-sitter--indents-in-path (parentwise-path)
111+
(seq-map
112+
(lambda (current-node)
113+
(cond
114+
((typescript-tree-sitter--outdent current-node) 'outdent)
115+
((typescript-tree-sitter--indent current-node) 'indent)
116+
(t 'no-indent)))
117+
parentwise-path))
118+
119+
(defun typescript-tree-sitter--updated-column (column indent)
120+
(pcase indent
121+
(`no-indent column)
122+
(`indent (+ column typescript-tree-sitter-indent-offset))
123+
(`outdent (- column typescript-tree-sitter-indent-offset))
124+
(_ (error "Unexpected indent instruction: %s" indent))))
125+
126+
(cl-defun typescript-tree-sitter--indent-column ()
127+
(seq-reduce
128+
#'typescript-tree-sitter--updated-column
129+
(typescript-tree-sitter--indents-in-path
130+
(typescript-tree-sitter--parentwise-path
131+
(typescript-tree-sitter--highest-node-at-position
132+
(save-excursion (back-to-indentation) (point)))))
133+
0))
134+
135+
;;;; Public API
136+
137+
;;;###autoload
138+
(defun typescript-tree-sitter-indent-line ()
139+
(let ((first-non-blank-pos ;; see savep in `smie-indent-line'
140+
(save-excursion
141+
(forward-line 0)
142+
(skip-chars-forward " \t")
143+
(point)))
144+
(new-column
145+
(typescript-tree-sitter--indent-column)))
146+
(when (numberp new-column)
147+
(if (< first-non-blank-pos (point))
148+
(save-excursion (indent-line-to new-column))
149+
(indent-line-to new-column)))))
150+
151+
(defgroup typescript-tree-sitter-indent nil "Indent lines using Tree-sitter as backend"
152+
:group 'tree-sitter)
153+
154+
(defcustom typescript-tree-sitter-indent-offset 2
155+
"Indent offset for typescript-tree-sitter-mode."
156+
:type 'integer
157+
:group 'typescript)
158+
159+
(defvar typescript-tree-sitter-mode-map
160+
(let ((map (make-sparse-keymap)))
161+
map)
162+
"Keymap used in typescript-tree-sitter buffers.")
163+
164+
(defvar typescript-tree-sitter-mode-syntax-table
165+
(let ((table (make-syntax-table)))
166+
(modify-syntax-entry ?@ "_" table)
167+
table))
168+
169+
;;;###autoload
170+
(define-derived-mode typescript-tree-sitter-mode prog-mode "typescriptreact"
171+
"Major mode for editing Typescript code.
172+
173+
Key bindings:
174+
\\{typescript-tree-sitter-mode-map}"
175+
:group 'typescript
176+
:syntax-table typescript-tree-sitter-mode-syntax-table
177+
178+
(setq-local indent-line-function #'typescript-tree-sitter-indent-line)
179+
;; (setq-local beginning-of-defun-function #'typescript-beginning-of-defun)
180+
;; (setq-local end-of-defun-function #'typescript-end-of-defun)
181+
182+
;; https://github.com/ubolonton/emacs-tree-sitter/issues/84
183+
(unless font-lock-defaults
184+
(setq font-lock-defaults '(nil)))
185+
186+
;; Comments
187+
(setq-local comment-start "// ")
188+
(setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
189+
(setq-local comment-end "")
190+
191+
(tree-sitter-hl-mode))
192+
193+
(add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tree-sitter-mode . tsx))
194+
195+
(provide 'typescript-tree-sitter)
196+
197+
;;; typescript-tree-sitter.el ends here

0 commit comments

Comments
 (0)