From d56e37c83c721c5bcffcf434138b27482b7e3fa6 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Fri, 18 Apr 2025 19:52:19 +0300 Subject: [PATCH] Embed elixir in heex as well as elixir->heex->elixir (bug#76788). * lisp/progmodes/elixir-ts-mode.el (elixir-ts--range-rules): Rename to a shorter name from 'elixir-ts--treesit-range-rules'. (elixir-ts--font-lock-feature-list, elixir-ts--thing-settings) (elixir-ts--range-rules): New variables with default values extracted from 'elixir-ts-mode'. (elixir-ts-mode): Use 'elixir-ts--font-lock-feature-list', 'elixir-ts--thing-settings', 'elixir-ts--range-rules' and 'heex-ts--range-rules'. Use 'treesit-merge-font-lock-feature-list' to merge 'heex-ts--font-lock-feature-list'. * lisp/progmodes/heex-ts-mode.el (heex-ts--font-lock-feature-list, heex-ts--range-rules): New variables. (heex-ts-mode): Use 'heex-ts--font-lock-feature-list', 'heex-ts--range-rules'. Merge 'elixir-ts--font-lock-settings', 'elixir-ts--font-lock-feature-list', 'elixir-ts--thing-settings' for embedding elixir in heex. Enable the 'sexp' navigation by default with 'treesit-cycle-sexp-type'. * lisp/progmodes/c-ts-mode.el (c-ts-mode): Append 'treesit-range-rules' to possibly already existing list in 'treesit-range-settings'. * lisp/treesit.el (treesit-language-at-point-default): Optimize to use 'when-let*'. --- lisp/progmodes/c-ts-mode.el | 15 +++-- lisp/progmodes/elixir-ts-mode.el | 111 +++++++++++++++++-------------- lisp/progmodes/heex-ts-mode.el | 50 ++++++++++++-- lisp/treesit.el | 5 +- 4 files changed, 116 insertions(+), 65 deletions(-) diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index fd255d68a57..8d4b6f587d5 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -1516,13 +1516,14 @@ in your init files." treesit-font-lock-settings c-ts-mode-doxygen-comment-font-lock-settings)) (setq-local treesit-range-settings - (treesit-range-rules - :embed 'doxygen - :host 'c - :local t - `(((comment) @cap - (:match - ,c-ts-mode--doxygen-comment-regex @cap))))))))) + (append treesit-range-settings + (treesit-range-rules + :embed 'doxygen + :host 'c + :local t + `(((comment) @cap + (:match + ,c-ts-mode--doxygen-comment-regex @cap)))))))))) (derived-mode-add-parents 'c-ts-mode '(c-mode)) diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el index 2a0a388a4e4..3a136894454 100644 --- a/lisp/progmodes/elixir-ts-mode.el +++ b/lisp/progmodes/elixir-ts-mode.el @@ -567,18 +567,59 @@ "Tree-sitter font-lock settings.") -(defvar elixir-ts--treesit-range-rules - (when (treesit-available-p) - (treesit-range-rules - :embed 'heex - :host 'elixir - '((sigil (sigil_name) @_name - (:match "^[HF]$" @_name) - (quoted_content) @heex))))) +(defvar elixir-ts--font-lock-feature-list + '(( elixir-comment elixir-doc elixir-definition) + ( elixir-string elixir-keyword elixir-data-type) + ( elixir-sigil elixir-builtin elixir-string-escape) + ( elixir-function-call elixir-variable elixir-operator elixir-number )) + "Tree-sitter font-lock feature list.") +(defvar elixir-ts--thing-settings + `((sexp (not (or (and named + ,(rx bos (or "source" "comment") eos)) + (and anonymous + ,(rx (or "{" "}" "[" "]" "(" ")" + "do" "end")))))) + (list + (or (and "\\`arguments\\'" ,#'elixir-ts--with-parens-0-p) + (and "\\`unary_operator\\'" ,#'elixir-ts--with-parens-1-p) + ,(rx bos (or "block" + "quoted_atom" + "string" + "interpolation" + "sigil" + "quoted_keyword" + "list" + "tuple" + "bitstring" + "map" + "do_block" + "anonymous_function") + eos))) + (sexp-default + ;; For `C-M-f' in "&|(a)" + ("(" . ,(lambda (node) + (equal (treesit-node-type (treesit-node-parent node)) + "unary_operator")))) + (sentence + ,(rx bos (or "call") eos)) + (text + ,(rx bos (or "string" "sigil" "comment") eos))) + "`treesit-thing-settings' for Elixir.") + +(defvar elixir-ts--range-rules + (treesit-range-rules + :embed 'heex + :host 'elixir + '((sigil (sigil_name) @_name + (:match "^[HF]$" @_name) + (quoted_content) @heex)))) + +(defvar heex-ts--range-rules) (defvar heex-ts--thing-settings) (defvar heex-ts--indent-rules) (defvar heex-ts--font-lock-settings) +(defvar heex-ts--font-lock-feature-list) (defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _) "Return the beginning of non-space characters for the parent node of PARENT." @@ -693,11 +734,7 @@ Return nil if NODE is not a defun node or doesn't have a name." ;; Font-lock. (setq-local treesit-font-lock-settings elixir-ts--font-lock-settings) (setq-local treesit-font-lock-feature-list - '(( elixir-comment elixir-doc elixir-definition) - ( elixir-string elixir-keyword elixir-data-type) - ( elixir-sigil elixir-builtin elixir-string-escape) - ( elixir-function-call elixir-variable elixir-operator elixir-number ))) - + elixir-ts--font-lock-feature-list) ;; Imenu. (setq-local treesit-simple-imenu-settings @@ -708,37 +745,7 @@ Return nil if NODE is not a defun node or doesn't have a name." ;; Navigation. (setq-local treesit-thing-settings - `((elixir - (sexp (not (or (and named - ,(rx bos (or "source" "comment") eos)) - (and anonymous - ,(rx (or "{" "}" "[" "]" "(" ")" - "do" "end")))))) - (list - (or (and "\\`arguments\\'" ,#'elixir-ts--with-parens-0-p) - (and "\\`unary_operator\\'" ,#'elixir-ts--with-parens-1-p) - ,(rx bos (or "block" - "quoted_atom" - "string" - "interpolation" - "sigil" - "quoted_keyword" - "list" - "tuple" - "bitstring" - "map" - "do_block" - "anonymous_function") - eos))) - (sexp-default - ;; For `C-M-f' in "&|(a)" - ("(" . ,(lambda (node) - (equal (treesit-node-type (treesit-node-parent node)) - "unary_operator")))) - (sentence - ,(rx bos (or "call") eos)) - (text - ,(rx bos (or "string" "sigil" "comment") eos))) + `((elixir ,@elixir-ts--thing-settings) (heex ,@heex-ts--thing-settings))) (setq-local treesit-defun-type-regexp '("call" . elixir-ts--defun-p)) @@ -747,7 +754,12 @@ Return nil if NODE is not a defun node or doesn't have a name." ;; Embedded Heex. (when (treesit-ensure-installed 'heex) - (setq-local treesit-range-settings elixir-ts--treesit-range-rules) + (setq-local treesit-range-settings + (append elixir-ts--range-rules + ;; Leave only local parsers from heex + ;; for elixir->heex->elixir embedding. + (seq-filter (lambda (r) (nth 2 r)) + heex-ts--range-rules))) (setq-local treesit-font-lock-settings (append treesit-font-lock-settings @@ -758,12 +770,9 @@ Return nil if NODE is not a defun node or doesn't have a name." heex-ts--indent-rules)) (setq-local treesit-font-lock-feature-list - '(( elixir-comment elixir-doc elixir-definition - heex-comment heex-keyword heex-doctype ) - ( elixir-string elixir-keyword elixir-data-type - heex-component heex-tag heex-attribute heex-string ) - ( elixir-sigil elixir-builtin elixir-string-escape) - ( elixir-function-call elixir-variable elixir-operator elixir-number )))) + (treesit-merge-font-lock-feature-list + treesit-font-lock-feature-list + heex-ts--font-lock-feature-list))) (treesit-major-mode-setup) (setq-local syntax-propertize-function #'elixir-ts--syntax-propertize) diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el index 38d75e69243..e5a4fe196ac 100644 --- a/lisp/progmodes/heex-ts-mode.el +++ b/lisp/progmodes/heex-ts-mode.el @@ -132,6 +132,12 @@ ]))) "Tree-sitter font-lock settings.") +(defvar heex-ts--font-lock-feature-list + '(( heex-comment heex-keyword heex-doctype ) + ( heex-component heex-tag heex-attribute heex-string ) + () ()) + "Tree-sitter font-lock feature list.") + (defun heex-ts--defun-name (node) "Return the name of the defun NODE. Return nil if NODE is not a defun node or doesn't have a name." @@ -166,6 +172,24 @@ Return nil if NODE is not a defun node or doesn't have a name." ,(rx bos (or "comment" "text") eos))) "`treesit-thing-settings' for HEEx.") +(defvar heex-ts--range-rules + (treesit-range-rules + :embed 'elixir + :host 'heex + '((directive [(partial_expression_value) + (ending_expression_value)] + @cap)) + + :embed 'elixir + :host 'heex + :local t + '((directive (expression_value) @cap) + (expression (expression_value) @cap)))) + +(defvar elixir-ts--font-lock-settings) +(defvar elixir-ts--font-lock-feature-list) +(defvar elixir-ts--thing-settings) + ;;;###autoload (define-derived-mode heex-ts-mode html-mode "HEEx" "Major mode for editing HEEx, powered by tree-sitter." @@ -204,11 +228,29 @@ Return nil if NODE is not a defun node or doesn't have a name." (setq-local treesit-simple-indent-rules heex-ts--indent-rules) (setq-local treesit-font-lock-feature-list - '(( heex-comment heex-keyword heex-doctype ) - ( heex-component heex-tag heex-attribute heex-string ) - () ())) + heex-ts--font-lock-feature-list) - (treesit-major-mode-setup))) + (when (treesit-ready-p 'elixir) + (require 'elixir-ts-mode) + (treesit-parser-create 'elixir) + + (setq-local treesit-range-settings heex-ts--range-rules) + + (setq-local treesit-font-lock-settings + (append treesit-font-lock-settings + elixir-ts--font-lock-settings)) + (setq-local treesit-font-lock-feature-list + (treesit-merge-font-lock-feature-list + treesit-font-lock-feature-list + elixir-ts--font-lock-feature-list)) + + (setq-local treesit-thing-settings + (append treesit-thing-settings + `((elixir ,@elixir-ts--thing-settings))))) + + (treesit-major-mode-setup) + ;; Enable the 'sexp' navigation by default + (treesit-cycle-sexp-type))) (derived-mode-add-parents 'heex-ts-mode '(heex-mode)) diff --git a/lisp/treesit.el b/lisp/treesit.el index 2b2551c169f..72897580ccd 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -195,9 +195,8 @@ Returns the language at POSITION, or nil if there's no parser in the buffer. When there are multiple parsers that cover POSITION, use the parser with the deepest embed level as it's the \"most relevant\" parser at POSITION." - (let ((parser (car (treesit-parsers-at position)))) - (when parser - (treesit-parser-language parser)))) + (when-let* ((parser (car (treesit-parsers-at position)))) + (treesit-parser-language parser))) ;;; Node API supplement