|
| 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