branch: elpa/typst-ts-mode commit 7646790a9626739f1fa6e1f7405c9d5922d5e46b Author: Meow King <mr.meowk...@anche.no> Commit: Meow King <mr.meowk...@anche.no>
feat: load embedding language settings dynamically --- typst-ts-embedding-lang-settings.el | 230 ++++++++++++++++++++++++++++++++++++ typst-ts-mode.el | 76 ++++++++++-- 2 files changed, 297 insertions(+), 9 deletions(-) diff --git a/typst-ts-embedding-lang-settings.el b/typst-ts-embedding-lang-settings.el new file mode 100644 index 0000000000..cde20a4e81 --- /dev/null +++ b/typst-ts-embedding-lang-settings.el @@ -0,0 +1,230 @@ +;;; typst-ts-embedding-lang-settings.el --- Embedding Languages Settings -*- lexical-binding: t; -*- +;; Copyright (C) 2023 Meow King <mr.meowk...@anche.no> + +;; This file is NOT part of Emacs. +;; 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: + +;; This file contains settings for tree sitter languages. +;; to test settings: +;; emacs --batch -l ./typst-ts-embedding-lang-settings.el --eval "(typst-ts-embedding-lang-settings-test)" + +;;; Code: +(require 'treesit) + +(defvar typst-ts-embedding-lang-settings + '((python . (:feature + python + :font-lock python--treesit-settings + :indentation nil + :ts-feature-list + '(( comment definition) + ( keyword string type) + ( assignment builtin constant decorator + escape-sequence number string-interpolation ) + ( bracket delimiter function operator variable property )))) + (rust . (:feature + rust-ts-mode + :font-lock rust-ts-mode--font-lock-settings + :indentation rust-ts-mode--indent-rules + :ts-feature-list + '(( comment definition) + ( keyword string) + ( assignment attribute builtin constant escape-sequence + number type) + ( bracket delimiter error function operator property variable))))) + "Settings for raw block languages.") + + +;; from vimscript-ts-mode (https://github.com/nverno/vimscript-ts-mode) +(defun typst-ts-els--merge-features (a b) + "Merge `treesit-font-lock-feature-list's A with B." + (cl-loop for x in a + for y in b + collect (seq-uniq (append x y)))) + +;; hugely insprired by vimscript-ts-mode (https://github.com/nverno/vimscript-ts-mode) +(defun typst-ts-els-merge-settings (settings) + "Merge SETTINGS." + (let ((feature (plist-get settings :feature)) + (font-lock-settings (plist-get settings :font-lock)) + (indentation-rules (plist-get settings :indentation)) + (ts-feature-list (plist-get settings :ts-feature-list))) + (require feature) + (setq font-lock-settings (eval font-lock-settings)) + (setq indentation-rules (eval indentation-rules)) + (setq ts-feature-list (eval ts-feature-list)) + (setq-local + treesit-font-lock-settings (append treesit-font-lock-settings + font-lock-settings) + treesit-simple-indent-rules (append treesit-simple-indent-rules + indentation-rules) + treesit-font-lock-feature-list (typst-ts-els--merge-features + treesit-font-lock-feature-list + ts-feature-list)))) + +(defun typst-ts-els-merge-lang (lang) + "Merge embedding language LANG settings." + (let ((settings (alist-get lang typst-ts-embedding-lang-settings))) + (if settings + (typst-ts-els-merge-settings settings) + (signal 'lang-no-in-settings '(lang))))) + +(defun typst-ts-els--try-get-variable--try-name (prefixes postfixes) + (let (variable) + (catch 'exit + (cl-loop for prefix in prefixes + for postfix in postfixes + do + (ignore-errors + (setq variable (intern (concat prefix postfix))) + (throw 'exit (eval variable))))))) + +(defun typst-ts-els--try-get-fls--try-name (_lang prefixes) + (typst-ts-els--try-get-variable--try-name + prefixes + '("--font-lock-settings" "-font-lock-settings"))) + +(defun typst-ts-els--try-get-ir--try-name (_lang prefixes) + (typst-ts-els--try-get-variable--try-name + prefixes + '("--indent-rules" "-indent-rules"))) + +(defvar typst-ts-els--try-get-feature-name-list + '("-ts-mode" "-mode" "") + "Used by `typset-ts-els--try-require-feature'.") + +(defvar typst-ts-els--try-variable-prefix-list + '("-ts-mode" "-ts" "") + "Used by `typset-ts-els--try-require-feature'.") + +(defvar typst-ts-els--try-get-fls--func-list + '(typst-ts-els--try-get-fls--try-name) + "TODO documentation.") + +(defvar typst-ts-els--try-get-ir--func-list + '(typst-ts-els--try-get-ir--try-name) + "TODO documentation.") + +(defun typst-ts-els--try-require-feature (lang) + "Try to require feature for LANG. +If success, then return the feature symbol, else nil." + (let (feature) + (catch 'exit + (dolist (elem typst-ts-els--try-get-feature-name-list) + (setq feature + (intern (concat (symbol-name lang) elem))) + (ignore-errors + (require feature) + (throw 'exit feature)))))) + +(defun typst-ts-els--try-get-variable-value (lang prefixes func-list) + "Try to get value of a variable specified for LANG. +The variable is like font lock settings or indentation rules. It is specified +by FUNC-LIST. +Return nil if cannot get. +PREFIXES: a list of variable name prefix. +FUNC-LIST: functions to be called to get the variable value." + (let (value) + (catch 'exit + (dolist (func func-list) + (ignore-errors + (setq value (funcall func lang prefixes)) + (when value + (throw 'exit value))))))) + +(defun typst-ts-els--try-get-font-lock-settings (lang prefixes) + (typst-ts-els--try-get-variable-value + lang prefixes typst-ts-els--try-get-fls--func-list)) + +(defun typst-ts-els--try-get-indentation-rules (lang prefixes) + (typst-ts-els--try-get-variable-value + lang prefixes typst-ts-els--try-get-ir--func-list)) + +;; NOTE this operation is high cost +(defun typst-ts-els--try-get-ts-feature-list (mode) + (with-temp-buffer + (funcall mode) + treesit-font-lock-feature-list)) + +(defvar-local typst-ts-els--include-languages + '(typst) + "DON'T MANUALLY CHANGE THIS VARIABLE!") + +(defun typst-ts-els-include-dynamically (_ranges _parser) + "Include language setting dynamically. +Use this function as one notifier of `treesit-parser-notifiers'." + (let ((parsers (treesit-parser-list)) + lang + feature prefixes font-lock-settings indentation-rules ts-feature-list) + (unless (eq (length parsers) (length typst-ts-els--include-languages)) + (dolist (parser parsers) + (setq lang (treesit-parser-language parser)) + (unless (member lang typst-ts-els--include-languages) + (unwind-protect + (condition-case _err + ;; first try loading settings from configuration + (typst-ts-els-merge-lang lang) + (error + ;; if not found or encountering error during loading, + ;; then guess variables to load + (catch 'exit + (ignore-errors + ;; require feature + (setq feature (typst-ts-els--try-require-feature lang)) + (unless feature + (throw 'exit "no feature found")) + + (setq prefixes + (cl-loop for postfix in typst-ts-els--try-variable-prefix-list + collect (concat (symbol-name lang) postfix))) + + ;; merge font lock settings + (setq font-lock-settings + (typst-ts-els--try-get-font-lock-settings lang prefixes)) + (setq treesit-font-lock-settings + (append treesit-font-lock-settings + font-lock-settings)) + + ;; merge indent rules + (setq indentation-rules + (typst-ts-els--try-get-indentation-rules lang prefixes)) + (setq treesit-simple-indent-rules + (append treesit-simple-indent-rules + indentation-rules)) + + ;; merge ts feature lists + (setq ts-feature-list + (typst-ts-els--try-get-ts-feature-list + (intern (concat (symbol-name lang) postfix)))) + (setq treesit-font-lock-feature-list + (typst-ts-els--merge-features + treesit-font-lock-feature-list + ts-feature-list)) + )))) + (add-to-list typst-ts-els--include-languages lang)) + ))))) + +(defun typst-ts-embedding-lang-settings-test () + "Test typst-ts-embedding-lang-settings." + (dolist (setting-entry typst-ts-embedding-lang-settings) + (message "Testing %s ..." (car setting-entry)) + (typst-ts-els-merge-settings (cdr setting-entry))) + (message "No problem found!")) + + +(provide 'typst-ts-embedding-lang-settings) + +;;; typst-ts-embedding-lang-settings.el ends here diff --git a/typst-ts-mode.el b/typst-ts-mode.el index a41ace7372..baa09cdd5b 100644 --- a/typst-ts-mode.el +++ b/typst-ts-mode.el @@ -33,6 +33,8 @@ (require 'compile) (require 'outline) +(require 'typst-ts-embedding-lang-settings) + (defgroup typst-ts nil "Tree Sitter enabled Typst Writing." :prefix "typst-ts" @@ -61,6 +63,11 @@ The compile options will be passed to the end of :type 'string :group 'typst-ts) +(defcustom typst-ts-mode-highlight-raw-block t + "Enable highlighting raw block." + :type 'boolean + :group 'typst-ts) + (defvar typst-ts-mode-before-compile-hook nil "Hook runs after compile.") @@ -133,6 +140,12 @@ is eliminated." :set-after '(typst-ts-markup-header-same-height) :group 'typst-ts-faces) +(defcustom typst-ts-mode-raw-block-lang-list + '(python) ; TODO + "Raw Block Lang List." + :type '(list symbol) + :group 'typst-ts) + ;; Face ========================================================================= (defface typst-ts-watch-modeline-indicator-face '((t :inherit (underline bold))) @@ -323,7 +336,6 @@ is eliminated." ;; ============================================================================== - ;; TODO typst has three modes (namely 'markup', 'code' and 'math') ;; Currently only add common settings to syntax table (defvar typst-ts-mode-syntax-table @@ -370,7 +382,8 @@ is eliminated." (raw_blck "```" @typst-ts-markup-rawblock-indicator-face (ident) :? @typst-ts-markup-rawspan-lang-face - (blob) @typst-ts-markup-rawblock-blob-face ;; TODO use function to fontify region + ;; NOTE let embedded language fontify blob + ;; (blob) @typst-ts-markup-rawblock-blob-face "```" @typst-ts-markup-rawblock-indicator-face) (label) @typst-ts-markup-label-face ;; TODO more precise highlight (upstream) (ref) @typst-ts-markup-reference-face) @@ -457,6 +470,12 @@ is eliminated." (attach ["^" "_"] @font-lock-operator-face) (align) @font-lock-operator-face))) +(defconst typst-ts-mode-font-lock-feature-list + '((comment common) + (markup-basic code-basic math-basic) + (markup-standard code-standard math-standard) + (markup-extended code-extended math-extended))) + (defconst typst-ts-mode--bracket-node-types '("block" "content" "group") "Bracket node types.") @@ -879,7 +898,7 @@ TODO lack of documentation." (parent-node-type (treesit-node-type parent-node))) (cond ((or (equal cur-node-type "parbreak") - (eq (point) (point-max))) + (eobp)) (when-let* ((cur-line-bol (save-excursion (back-to-indentation) @@ -942,6 +961,35 @@ TODO lack of documentation." (define-key map (kbd "TAB") #'typst-ts-mode-cycle) map)) +(defun typst-ts-mode--language-at-point (pos) + "Get the treesit language should be used at POS. +See `treesit-language-at-point-function'." + (let ((lang + (when-let* ((cur-node (treesit-node-at pos 'typst)) + ((equal (treesit-node-type cur-node) "blob")) + (parent-node (treesit-node-parent cur-node)) + ((equal (treesit-node-type + (treesit-node-parent cur-node)) "raw_blck")) + (lang-node ; (indent) + (treesit-node-prev-sibling cur-node))) + (intern (treesit-node-text lang-node))))) + (if lang lang 'typst))) + +(defun typst-ts-mode--treesit-range-rules (langs) + ;; from vimscript-ts-mode.el + "Create range captures for LANGS." + (cl-loop for lang in langs + when (treesit-ready-p lang) + nconc + (treesit-range-rules + :host 'typst + :embed lang + :local t + `((raw_blck + lang: (_) @_lang + (blob) @capture + (:equal @_lang ,(symbol-name lang))))))) + ;;;###autoload (define-derived-mode typst-ts-mode text-mode "Typst" "Major mode for editing Typst, powered by tree-sitter." @@ -950,7 +998,12 @@ TODO lack of documentation." (unless (treesit-ready-p 'typst) (error "Tree-sitter for Typst isn't available")) - (treesit-parser-create 'typst) + + (let ((parser (treesit-parser-create 'typst))) + (when typst-ts-mode-highlight-raw-block + (treesit-parser-add-notifier + parser + 'typst-ts-els-include-dynamically))) ;; Comments. (typst-ts-mode-comment-setup) @@ -968,11 +1021,7 @@ TODO lack of documentation." (setq-local treesit-font-lock-level 4) (setq-local treesit-font-lock-settings (apply #'treesit-font-lock-rules typst-ts-mode-font-lock-rules)) - (setq-local treesit-font-lock-feature-list - '((comment common) - (markup-basic code-basic math-basic) - (markup-standard code-standard math-standard) - (markup-extended code-extended math-extended))) + (setq-local treesit-font-lock-feature-list typst-ts-mode-font-lock-feature-list) ;; Indentation (setq-local treesit-simple-indent-rules typst-ts-mode--indent-rules) @@ -989,6 +1038,15 @@ TODO lack of documentation." typst-ts-mode-executable-location (file-name-nondirectory buffer-file-name) typst-ts-mode-compile-options)) + + ;; use parser notifier to add font lock dynamically + ;; TODO merge indentation rule + ;; TODO merge font lock feature list + + (setq-local treesit-language-at-point-function + 'typst-ts-mode--language-at-point) + (setq-local treesit-range-settings + (typst-ts-mode--treesit-range-rules typst-ts-mode-raw-block-lang-list)) ;; Outline (setq-local outline-regexp typst-ts-mode-outline-regexp)