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