2323; ; Keywords: erlang, languages, treesitter
2424; ; URL: https://github.com/erlang/emacs-erlang-ts
2525; ; Package-Requires: ((emacs "29.2") (erlang "27.2"))
26- ; ; Package-Version: 0.2
26+ ; ; Package-Version: 0.3
2727
2828; ;; Commentary:
2929
4646; ; :mode ("\\ .erl\\ '" . erlang-ts-mode)
4747; ; :defer 't)
4848; ; ```
49- ; ; Install/compile erlang treesitter support (first time only ):
49+ ; ; Install/compile erlang treesitter support (first time or upgrade grammer ):
5050; ;
5151; ; ```
5252; ; M-x treesit-install-language-grammar
@@ -155,21 +155,27 @@ FUNC with ARGS will be called if `erlang-ts-mode' is not active."
155155 :override t
156156 `( ; ; Might be slow but don't know a better way to do it
157157 (call expr: (_) @font-lock-type-face
158- (:pred erlang-ts-paren-is- type @font-lock-type-face))
158+ (:pred erlang-ts-in- type-context-p @font-lock-type-face))
159159 (type_name name: (atom ) @font-lock-type-face)
160160 (export_type_attribute types: (fa fun: (atom ) @font-lock-type-face))
161161 (record_decl name: (atom ) @font-lock-type-face
162162 (record_field name: (atom ) @font-lock-property-name-face))
163+ ; ; for records without fields e.g
164+ ; ; `-record(name, {}).`
165+ (record_decl name: (atom ) @font-lock-type-face)
163166 (record_name name: (atom ) @font-lock-type-face))
164167
165168 :language 'erlang
166169 :feature 'definition
167170 `((function_clause name: (atom ) @font-lock-function-name-face)
171+ (callback fun: (atom ) @font-lock-function-name-face)
168172 (spec fun: (atom ) @font-lock-function-name-face)
169173 (fa fun: (atom ) @font-lock-function-name-face)
170174 (binary_op_expr lhs: (atom ) @font-lock-function-name-face " /"
171175 rhs: (integer))
172- (internal_fun fun: (atom ) @font-lock-function-name-face))
176+ (internal_fun fun: (atom ) @font-lock-function-name-face)
177+ (external_fun module: (module name: (atom ) @font-lock-function-name-face)
178+ fun: (atom ) @font-lock-function-name-face))
173179
174180 :language 'erlang
175181 :feature 'guards
@@ -185,22 +191,52 @@ FUNC with ARGS will be called if `erlang-ts-mode' is not active."
185191 (remote_module module: (atom )
186192 @module (:equal " erlang" @module))
187193 fun: (atom ) @fun (:match , erlang-ext-bif-regexp @fun))
188- @font-lock-builtin-face))
194+ @font-lock-builtin-face)
195+ (call expr: (atom ) @font-lock-builtin-face
196+ (:match , erlang-guards-regexp @font-lock-builtin-face)))
189197
190198 :language 'erlang
191199 :feature 'preprocessor
192200 :override t
193201 `((wild_attribute name: (_) @font-lock-preprocessor-face)
194- (pp_define lhs: (macro_lhs name: (_) @font-lock-preprocessor-face))
202+ (module_attribute ([" -" " module" ]) @font-lock-preprocessor-face)
203+ (behaviour_attribute ([" -" " behaviour" " behavior" ]) @font-lock-preprocessor-face)
204+ (deprecated_attribute ([" -" " deprecated" ]) @font-lock-preprocessor-face)
205+ (export_attribute ([" -" " export" ]) @font-lock-preprocessor-face)
206+ (import_attribute ([" -" " import" ]) @font-lock-preprocessor-face)
207+ (export_type_attribute ([" -" " export_type" ]) @font-lock-preprocessor-face)
208+ (compile_options_attribute ([" -" " compile" ]) @font-lock-preprocessor-face)
209+ (file_attribute ([" -" " file" ]) @font-lock-preprocessor-face)
210+ (feature_attribute ([" -" " feature" ]) @font-lock-preprocessor-face)
211+ (optional_callbacks_attribute ([" -" " optional_callbacks" ]) @font-lock-preprocessor-face)
212+
213+ (pp_define ([" -" " define" ]) @font-lock-preprocessor-face)
214+ (pp_include ([" -" " include" ]) @font-lock-preprocessor-face)
215+ (pp_include_lib ([" -" " include_lib" ]) @font-lock-preprocessor-face)
216+ (pp_undef ([" -" " undef" ]) @font-lock-preprocessor-face)
217+ (pp_ifdef ([" -" " ifdef" ]) @font-lock-preprocessor-face)
218+ (pp_ifndef ([" -" " ifndef" ]) @font-lock-preprocessor-face)
219+ (pp_else ([" -" " else" ]) @font-lock-preprocessor-face)
220+ (pp_endif ([" -" " endif" ]) @font-lock-preprocessor-face)
221+ (pp_if ([" -" " if" ]) @font-lock-preprocessor-face)
222+ (pp_elif ([" -" " elif" ]) @font-lock-preprocessor-face)
223+
224+ (record_decl ([" -" " record" ]) @font-lock-preprocessor-face)
195225 (macro_call_expr name: (_) @font-lock-preprocessor-face)
196- ([" module" " export" " import" " compile" " define" " record"
197- " spec" " type" " export_type" " opaque" " behaviour" " include" " include_lib" ]
198- @font-lock-preprocessor-face))
226+ (callback ([" -" " callback" ]) @font-lock-preprocessor-face)
227+
228+ (type_alias ([" -" " type" ]) @font-lock-preprocessor-face)
229+ (opaque ([" -" " opaque" ]) @font-lock-preprocessor-face)
230+ (spec ([" -" " spec" ]) @font-lock-preprocessor-face))
199231
200232 :language 'erlang
201233 :feature 'constant
202- `(((atom ) @font-lock-constant-face (:match " ^'.*" @font-lock-constant-face))
203- ((char) @font-lock-constant-face (:match " ^$.*" @font-lock-constant-face)))
234+ :override t
235+ `((macro_call_expr name: (var) @font-lock-constant-face
236+ (:pred erlang-ts-predefined-macro-p @font-lock-constant-face))
237+
238+ ((atom ) @font-lock-constant-face (:match , erlang-atom-quoted-regexp @font-lock-constant-face))
239+ ((char) @font-lock-constant-face))
204240
205241 :language 'erlang
206242 :feature 'index-atom
@@ -217,38 +253,136 @@ FUNC with ARGS will be called if `erlang-ts-mode' is not active."
217253 :feature 'variable
218254 `((var) @font-lock-variable-name-face)
219255
256+ :language 'erlang
257+ :feature 'remote-module
258+ :override t
259+ `((call expr: (remote module: (remote_module module: (atom ) @font-lock-constant-face)
260+ fun: (atom ) @font-lock-function-name-face))
261+ (external_fun module: (module name: (atom ) @font-lock-constant-face))
262+ (remote module: (remote_module module: (atom ) @font-lock-constant-face)))
263+
220264 :language 'erlang
221265 :feature 'function-call
222266 `((call expr: (_) @font-lock-function-call-face))
223267
224268 :language 'erlang
225269 :feature 'bracket
226- '(([" (" " )" " [" " ]" " {" " { " " }" " <<" " >>" ]) @font-lock-bracket-face)
270+ '(([" (" " )" " [" " ]" " {" " }" " <<" " >>" ]) @font-lock-bracket-face)
227271
228272 :language 'erlang
229273 :feature 'delimiter
230274 '(([" ." " ," " ;" " |" ]) @font-lock-delimiter-face)
231275
232276 :language 'erlang
233277 :feature 'operator
234- ; ; Add "<:-" "<:=" "&&" when available in tree-sitter
235278 '(([ " ->" " ||" " <-" " <=" " +" " -" " *" " /" " ++"
236- " >" " >=" " <" " =<" " =" " ==" " =:=" " =/=" ])
279+ " >" " >=" " <" " =<" " =" " ==" " =:=" " =/="
280+ " <:-" " <:=" " &&" ])
237281 @font-lock-operator-face))
238282
239283 " Tree-sitter font-lock settings for `erlang-ts-mode' .
240284Use `treesit-font-lock-level' or `treesit-font-lock-feature-list'
241285 to change settings" )
242286
243- (defun erlang-ts-paren-is-type (node )
244- " Check if any parent of NODE is a type."
245- (let ((type (treesit-node-type node)))
246- (cond ((member type '(" type_alias" " ann_type" " type_sig"
247- " opaque" " field_type" ))
248- t )
249- ((not type) nil )
250- (t
251- (erlang-ts-paren-is-type (treesit-node-parent node))))))
287+ (defun erlang-ts-in-type-context-p (node )
288+ " Check if NODE is within a type definition context."
289+ (when node
290+ (let ((parent (treesit-node-parent node)))
291+ (cond
292+ ((null parent) nil )
293+ ((member (treesit-node-type parent)
294+ '(" type_alias" " ann_type" " type_sig" " opaque" " field_type" )) t )
295+ (t (erlang-ts-in-type-context-p parent))))))
296+
297+ (defun erlang-ts-predefined-macro-p (node )
298+ " Check if macro_call_expr var NODE is a builtin macro."
299+ (when node
300+ (if (member (treesit-node-text node)
301+ '(" OTP_RELEASE" " MACHINE"
302+ " MODULE" " MODULE_STRING"
303+ " FILE" " LINE"
304+ " FUNCTION_NAME" " FUNCTION_ARITY"
305+ " FEATURE_AVAILABLE" " FEATURE_ENABLED" ))
306+ t
307+ nil )))
308+
309+ (defvar erlang-ts--syntax-propertize-query
310+ (when (treesit-available-p)
311+ (treesit-query-compile
312+ 'erlang
313+ '(((char) @node-char)
314+ ((atom ) @node-atom)
315+ ((string ) @node-string-triple-quoted (:match " ^\"\"\" " @node-string-triple-quoted))
316+ ((string ) @node-string)))))
317+
318+ (defun erlang-ts--process-node (node )
319+ " Process a single or double quoted string or atom node.
320+ NODE is the treesit node to process."
321+ (let* ((node-text (treesit-node-text node))
322+ (node-start (treesit-node-start node))
323+ (node-end (treesit-node-end node))
324+ (first-char (aref node-text 0 ))
325+ (last-char (aref node-text (1- (length node-text)))))
326+ (when (and (or (eq first-char ?\" ) (eq first-char ?\' ))
327+ (eq first-char last-char))
328+ (let ((escaped-last-quote (and (eq last-char ?\" )
329+ (> (length node-text) 1 )
330+ (eq (aref node-text (- (length node-text) 2 )) ?\\ ))))
331+ (put-text-property node-start (1+ node-start) 'syntax-table (string-to-syntax " |" ))
332+ (put-text-property (1- node-end) node-end 'syntax-table (string-to-syntax " |" ))
333+ (unless escaped-last-quote
334+ (put-text-property (1- node-end) node-end 'syntax-table (string-to-syntax " |" )))
335+ (let ((content-start (1+ node-start))
336+ (content-end (1- node-end)))
337+ (when (> content-end content-start)
338+ (put-text-property content-start content-end 'syntax-table (syntax-table ))))))))
339+
340+ (defun erlang-ts--process-node-char (node )
341+ " Process char NODE like `$\'' or `$\"' ."
342+ (let* ((node-start (treesit-node-start node))
343+ (node-end (treesit-node-end node)))
344+ (when (> node-end node-start)
345+ (let ((custom-table (copy-syntax-table (syntax-table ))))
346+ (modify-syntax-entry ?' " w" custom-table)
347+ (modify-syntax-entry ?\" " w" custom-table)
348+ (put-text-property node-start node-end 'syntax-table custom-table)))))
349+
350+ (defun erlang-ts--process-node-triple-quoted (node )
351+ " Process a triple quoted string node.
352+ NODE is the treesit node to process."
353+ (let* ((node-text (treesit-node-text node))
354+ (node-start (treesit-node-start node))
355+ (node-end (treesit-node-end node))
356+ (text-length (length node-text)))
357+ (put-text-property node-start (+ node-start 3 ) 'syntax-table (string-to-syntax " |" ))
358+ (when (>= text-length 3 )
359+ (put-text-property (- node-end 3 ) node-end 'syntax-table (string-to-syntax " |" )))
360+ (let ((content-start (+ node-start 3 ))
361+ (content-end (- node-end 3 )))
362+ (when (> content-end content-start)
363+ (put-text-property content-start content-end 'syntax-table (syntax-table ))))))
364+
365+ (defun erlang-ts--syntax-propertize (start end )
366+ " Apply syntax properties for Erlang specific patterns from START to END."
367+ (let ((captures
368+ (treesit-query-capture 'erlang erlang-ts--syntax-propertize-query start end)))
369+ (pcase-dolist (`(, name . , node ) captures)
370+ (pcase name
371+ ('node-char (erlang-ts--process-node-char node))
372+ ('node-atom (erlang-ts--process-node node))
373+ ('node-string (erlang-ts--process-node node))
374+ ('node-string-triple-quoted (erlang-ts--process-node-triple-quoted node))))))
375+
376+ (defvar erlang-ts-mode-syntax-table nil
377+ " Syntax table in use in Erlang-ts-mode buffers." )
378+
379+ (defun erlang-ts-syntax-table-init ()
380+ " Initialize the syntax table for `erlang-ts-mode' ."
381+ (unless erlang-ts-mode-syntax-table
382+ (let ((table (copy-syntax-table erlang-mode-syntax-table)))
383+ (modify-syntax-entry ?$ " w" table)
384+ (setq erlang-ts-mode-syntax-table table)))
385+ (set-syntax-table erlang-ts-mode-syntax-table))
252386
253387(defun erlang-ts-setup ()
254388 " Setup treesit for erlang."
@@ -269,12 +403,13 @@ Use `treesit-font-lock-level' or `treesit-font-lock-feature-list'
269403 (builtin ; ; Level 3
270404 variable
271405 guards
406+ function-call
272407 constant)
273408 (operator ; ; Level 4
409+ remote-module
274410 delimiter
275411 bracket
276412 number
277- function-call
278413 index-atom)))
279414
280415 ; ; Should we set this or let the user decide?
@@ -306,7 +441,8 @@ Use `treesit-font-lock-level' or `treesit-font-lock-feature-list'
306441 (advice-add #'erlang-font-lock-level-3 :around #'erlang-ts--font-lock-level-3 )
307442 (advice-add #'erlang-font-lock-level-4 :around #'erlang-ts--font-lock-level-4 )
308443
309- (treesit-major-mode-setup))
444+ (treesit-major-mode-setup)
445+ (setq-local syntax-propertize-function #'erlang-ts--syntax-propertize ))
310446
311447
312448(defun erlang-ts-unload-function ()
@@ -326,7 +462,8 @@ Use `treesit-font-lock-level' or `treesit-font-lock-feature-list'
326462;;;### autoload
327463(define-derived-mode erlang-ts-mode erlang-mode " erl-ts"
328464 " Major mode for editing erlang with tree-sitter."
329- :syntax-table erlang-mode-syntax-table
465+ :syntax-table nil
466+ (erlang-ts-syntax-table-init)
330467 (when (treesit-ready-p 'erlang )
331468 (treesit-parser-create 'erlang )
332469 (erlang-ts-setup)))
0 commit comments