Skip to content

Commit 1cb0bc4

Browse files
committed
matlab-ts-langs-install: utility to download the tree-sitter slib's
1 parent ef9fbcb commit 1cb0bc4

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed

matlab-ts-langs-install.el

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
;;; matlab-ts-langs-install.el --- -*- lexical-binding: t -*-
2+
3+
;;; Commentary:
4+
;;
5+
;; Download ~/.emacs.d/tree-sitter/libtree-sitter-LANGUAGES.so (or .dll or .dylib) from
6+
;; https://github.com/emacs-tree-sitter/tree-sitter-langs latest release
7+
;;
8+
;; This assumes the latest release files have format:
9+
;; tree-sitter-grammars.aarch64-apple-darwin.v0.12.293.tar.gz
10+
;; tree-sitter-grammars.aarch64-unknown-linux-gnu.v0.12.293.tar.gz
11+
;; tree-sitter-grammars.x86_64-apple-darwin.v0.12.293.tar.gz
12+
;; tree-sitter-grammars.x86_64-pc-windows-msvc.v0.12.293.tar.gz
13+
;; tree-sitter-grammars.x86_64-unknown-linux-gnu.v0.12.293.tar.gz
14+
;; and contain
15+
;; BUNDLE-VERSION
16+
;; LANUGAGE1.SLIB-EXT
17+
;; LANUGAGE2.SLIB-EXT
18+
;; ...
19+
;; where SLILB-EXT is so on Linux, dll on Windows, and dylib on Mac. The computation of the
20+
;; platform, e.g. "aarch64-apple-darwin" is done using matlab--ts-langs-platform which was derived
21+
;; from
22+
;; https://github.com/emacs-tree-sitter/tree-sitter-langs/blob/master/tree-sitter-langs-build.el
23+
;;
24+
;; This is an alternative to
25+
;; M-x treesit-install-language-grammar
26+
;; `treesit-install-language-grammar' will download the source and compile it. To do this,
27+
;; you must have the correct compilers and environment.
28+
;;
29+
30+
;;; Code:
31+
32+
(require 'url)
33+
34+
(defun matlab--ts-langs-platform ()
35+
"Return the platform used in the ts-langs-url release *.tar.gz files.
36+
See https://github.com/emacs-tree-sitter/tree-sitter-langs/releases/latest"
37+
;; os / platform strings are from tree-sitter-langs--os and tree-sitter-langs--bundle-file in
38+
;; https://github.com/emacs-tree-sitter/tree-sitter-langs/blob/master/tree-sitter-langs-build.el
39+
;;
40+
;; One option would be to download tree-sitter-langs-build.el to a buffer and eval it so we can
41+
;; get these definitions, but that would be a risk because we can't validate that the code is
42+
;; correct, so we copied the definitions here.
43+
(let ((os (pcase system-type
44+
('darwin "macos")
45+
('gnu/linux "linux")
46+
('android "linux")
47+
('berkeley-unix "freebsd")
48+
('windows-nt "windows")
49+
(_ (error "Unsupported system-type %s" system-type)))))
50+
;; Return platform
51+
(pcase os
52+
("windows" "x86_64-pc-windows-msvc")
53+
("linux" (if (string-prefix-p "aarch64" system-configuration)
54+
"aarch64-unknown-linux-gnu"
55+
"x86_64-unknown-linux-gnu"))
56+
("freebsd" (if (string-prefix-p "aarch64" system-configuration)
57+
"aarch64-unknown-freebsd"
58+
"x86_64-unknown-freebsd"))
59+
("macos" (if (string-prefix-p "aarch64" system-configuration)
60+
"aarch64-apple-darwin"
61+
"x86_64-apple-darwin")))))
62+
63+
(defun matlab--ts-langs-latest-url ()
64+
"Get the latest tree-sitter-langs *.tar.gv URL.
65+
Returns latest *.tar.gz release URL from
66+
https://github.com/emacs-tree-sitter/tree-sitter-langs/"
67+
68+
(let* ((ts-url "https://github.com/emacs-tree-sitter/tree-sitter-langs")
69+
(tags-buf (url-retrieve-synchronously (concat ts-url "/tags")))
70+
release-url)
71+
72+
(with-current-buffer tags-buf
73+
(when (re-search-forward "tree-sitter-langs/releases/tag/" nil t)
74+
(when (looking-at "\\([.0-9]+\\)")
75+
(let ((ver (match-string 1))
76+
;; See in ts-url: tree-sitter-langs-build.el
77+
(platform (matlab--ts-langs-platform)))
78+
79+
(setq release-url (concat ts-url
80+
"/releases/download/" ver "/tree-sitter-grammars."
81+
platform
82+
".v" ver
83+
".tar.gz"))))))
84+
(kill-buffer tags-buf)
85+
release-url))
86+
87+
(defun matlab--ts-langs-tar-result (tar-args)
88+
"Return string \"tar TAR-ARGS\\n<stdout-result>\"."
89+
(format "tar %s\n%s"
90+
(mapconcat #'identity tar-args " ")
91+
(buffer-string)))
92+
93+
(defun matlab--ts-get-langs-to-extract (slib-re tar-args)
94+
"Get ts languages to extract from tar TAR-ARGS stdout in `current-buffer'.
95+
SLIB-RE is the regexp that matches LANGUAGE.SLIB-EXT"
96+
97+
(let ((all-languages '())
98+
(languages-to-extract '()))
99+
100+
(goto-char (point-min))
101+
102+
(while (not (eobp))
103+
(cond ((looking-at slib-re)
104+
(push (match-string 1) all-languages))
105+
((not (or (looking-at "^[\r\n]+$")
106+
(looking-at "^BUNDLE-VERSION$")))
107+
(error "Unexpeced content in output from %s"
108+
(matlab--ts-langs-tar-result tar-args))))
109+
(forward-line))
110+
(setq all-languages (sort all-languages))
111+
112+
(if (y-or-n-p "Do you want extract all tree-sitter language shared libraries,
113+
(y for all, n to specify)? ")
114+
(setq languages-to-extract all-languages)
115+
(let ((prompt "First language to extract: ")
116+
done)
117+
(while (not done)
118+
(let ((lang (completing-read prompt all-languages nil t)))
119+
(if (string= lang "")
120+
(setq done (string-match "\\`Next" prompt))
121+
;; else lanuage entered
122+
(push lang languages-to-extract)
123+
(setq prompt "Next lanugage to extract (enter when done): "))))))
124+
;; result
125+
languages-to-extract))
126+
127+
(defun matlab--ts-langs-write-readme (latest-url languages-to-extract slib-ext dir)
128+
"Write DIR/README-tree-sitter-langs.txt.
129+
Where `current-buffer' is the result of tar extract verbose (-v) from
130+
extracting LATEST-URL with tree-sitter shared libraries extension,
131+
SLIB-EXT for LANGUAGES-TO-EXTRACT."
132+
(let ((download-readme (concat dir "/README-tree-sitter-langs.txt")))
133+
(write-region (concat "M-x matlab--ts-langs-download\n"
134+
"URL: " latest-url "\n"
135+
"Contents: " (string-trim
136+
(replace-regexp-in-string "[\r\n]+" " "
137+
(buffer-string)))
138+
"\n"
139+
"Extracted the following to " dir ":\n"
140+
(mapconcat (lambda (lang)
141+
(concat " libtree-sitter-" lang "." slib-ext))
142+
languages-to-extract
143+
"\n")
144+
"\n")
145+
nil
146+
download-readme)
147+
(message "See %s" download-readme)))
148+
149+
(defun matlab--ts-langs-extract (latest-url dir)
150+
"Extract tree-sitter langs *.tar.gz from current buffer to DIR.
151+
LATEST-URL is the URL used to get *.tar.gz into the current buffer"
152+
(goto-char (point-min))
153+
;; HTTP header starts with: HTTP/1.1 200 OK
154+
(when (not (looking-at "^HTTP/[.0-9]+ 200 OK$"))
155+
(error "Downloaded %s resulted in unexpected response, see %S"
156+
latest-url (current-buffer)))
157+
158+
(re-search-forward "^[ \n\r]") ;; Move over header to start of *.tar.gz content
159+
160+
(let* ((tar-gz-file (url-file-nondirectory latest-url))
161+
(prefix (replace-regexp-in-string "\\.tar\\.gz\\'" "" tar-gz-file))
162+
(tmp-tar-gz (make-temp-file prefix nil ".tar.gz")))
163+
164+
(let ((coding-system-for-write 'no-conversion)
165+
(buffer-file-coding-system nil)
166+
(file-coding-system-alist nil)
167+
;; Have to write to *.tar.gz.tmp to prevent Emacs from re-compressing the contents,
168+
;; then rename
169+
(tmp-tar-gz-dot-tmp (concat tmp-tar-gz ".tmp")))
170+
171+
(write-region (point) (point-max) tmp-tar-gz-dot-tmp)
172+
(delete-file tmp-tar-gz)
173+
(rename-file tmp-tar-gz-dot-tmp tmp-tar-gz))
174+
175+
(condition-case err
176+
(with-temp-buffer
177+
;; extract *.tar.gz to DIR
178+
(let* ((extract-dir (concat dir "/ts-langs"))
179+
(tar-args `("-x" "-v" "-f" ,tmp-tar-gz "-C" ,extract-dir))
180+
status)
181+
182+
(when (not (file-directory-p extract-dir))
183+
(make-directory extract-dir))
184+
185+
(setq status (apply #'call-process "tar" nil t nil tar-args))
186+
(when (not (= status 0))
187+
(error "Non-zero status from: %s" (matlab--ts-langs-tar-result tar-args)))
188+
;; temp buffer should be a list of files we extracted from tar -v output
189+
190+
(let* ((slib-ext (pcase system-type
191+
('darwin "dylib")
192+
('windows-nt "dll")
193+
('gnu/linux "so")
194+
;; assume some other type of linux, e.g. bsdunix, andriod
195+
(_ "so")))
196+
(slib-re (concat "^\\([^ \t\r\n]+\\)\\." slib-ext "$"))
197+
(languages-to-extract (matlab--ts-get-langs-to-extract slib-re tar-args)))
198+
199+
(goto-char (point-min))
200+
(while (not (eobp))
201+
(when (looking-at slib-re)
202+
(let ((slib (match-string 0))
203+
(lang (match-string 1)))
204+
205+
(when (member lang languages-to-extract)
206+
(let ((src-file (concat extract-dir "/" slib))
207+
(dst-file (concat dir "/libtree-sitter-" slib)))
208+
(when (file-exists-p dst-file)
209+
(delete-file dst-file))
210+
(rename-file src-file dst-file)))))
211+
212+
(forward-line))
213+
214+
(delete-directory extract-dir t)
215+
216+
(matlab--ts-langs-write-readme latest-url languages-to-extract slib-ext dir))))
217+
(error
218+
(error "Failed to extract downloaded %s
219+
Error: %s
220+
This could be due use of a tree-sitter lanugage shared library.
221+
Try restarting Emacs without loading any *-ts-mode, then run
222+
M-x matlab-ts-langs-install"
223+
latest-url
224+
(error-message-string err))))
225+
(delete-file tmp-tar-gz)))
226+
227+
(defun matlab-ts-langs-install (&optional dir)
228+
"Download the latest tree-sitter-langs *.tar.gz and extract to DIR.
229+
This will add or replace all
230+
DIR/libtree-sitter-LANGUAGE.SLIB-EXT
231+
shared libraries where SLIB-EXT = so on Linux, dll on Windows, or dylib on Mac.
232+
233+
To see what this will do before running it, visit
234+
https://github.com/emacs-tree-sitter/tree-sitter-langs
235+
and examine the latest release *.tar.gz. The *.SLIB-EXT files will be extracted
236+
from the *.tar.gz file and placed in DIR.
237+
238+
DIR defaults to ~/.emacs.d/tree-sitter
239+
240+
This should be invoked before you load any *-ts-mode packages.
241+
Typical usage:
242+
1. Start Emacs
243+
2. \\[matlab-ts-langs-install]
244+
3. Visit files using LANGUAGE-ts-mode."
245+
246+
(interactive)
247+
248+
(when (not (= emacs-major-version 30))
249+
(error "Unsupported Emacs version, %d
250+
The treesit library requires Emacs 30 and
251+
https://github.com/emacs-tree-sitter/tree-sitter-lang
252+
is known to work with Emacs 30 as of July 2025"
253+
emacs-major-version))
254+
255+
(dolist (command '("tar" "gunzip"))
256+
(when (not (executable-find command))
257+
(user-error "Unable to download, %s is not found on your `exec-path'" command)))
258+
259+
(if (not dir)
260+
(progn
261+
(setq dir (concat (file-truename "~") "/.emacs.d/tree-sitter"))
262+
(when (not (file-directory-p dir))
263+
(make-directory dir t)))
264+
;; Else it must exist.
265+
(when (not (file-directory-p dir))
266+
(error "%d is not a directory" dir))
267+
(setq dir (file-truename dir)))
268+
269+
(let* ((latest-url (matlab--ts-langs-latest-url))
270+
(latest-buf (if (y-or-n-p (format "Download \n %s\n and extract to %s/? "
271+
latest-url dir))
272+
(let ((buf (url-retrieve-synchronously latest-url)))
273+
(message "Downloaded %s (to buffer %S)" latest-url buf)
274+
buf)
275+
(error "Download aborted"))))
276+
(with-current-buffer latest-buf
277+
(matlab--ts-langs-extract latest-url dir))
278+
(kill-buffer latest-buf)))
279+
280+
(provide 'matlab-ts-langs-install)
281+
;;; matlab-ts-langs-install.el ends here

0 commit comments

Comments
 (0)