Skip to content

Commit a778abd

Browse files
authored
Merge pull request #7 from dgud/dgud/squash-pr
Fix of pull req #2
2 parents eb579dd + bf5adaa commit a778abd

File tree

2 files changed

+164
-27
lines changed

2 files changed

+164
-27
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Add to your .emacs file:
2323
:mode ("\\.erl\\'" . erlang-ts-mode)
2424
:defer 't)
2525
```
26-
Install/compile erlang treesitter support (first time only):
26+
Install/compile erlang treesitter support (first time or update treesitter grammer):
2727

2828
```
2929
M-x treesit-install-language-grammar

erlang-ts.el

Lines changed: 163 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
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

@@ -46,7 +46,7 @@
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'.
240284
Use `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

Comments
 (0)