branch: elpa/typst-ts-mode
commit 0e4894ffcde658037529ac038882bd76e8603b78
Merge: 2a3fb8e577 075b450e44
Author: meowking <mr.meowk...@tutamail.com>
Commit: meowking <mr.meowk...@tutamail.com>

    merge: fix: maarkup-extended font feature
---
 typst-ts-core.el    |  73 ++++++++++++++++++++
 typst-ts-editing.el | 195 ++++++++++++++++++++++++++++++++++------------------
 typst-ts-mode.el    | 155 ++++++++++++++++++++---------------------
 3 files changed, 275 insertions(+), 148 deletions(-)

diff --git a/typst-ts-core.el b/typst-ts-core.el
index fab4ba780f..b5ab461e23 100644
--- a/typst-ts-core.el
+++ b/typst-ts-core.el
@@ -32,6 +32,15 @@
   :type 'natnum
   :group 'typst-ts)
 
+(defconst typst-ts-core--container-node-types
+  ;; '_math_group' here is because `treesit-parent-until' doesn't hanlde node 
type alias well
+  ;; TODO file a bug
+  '("block" "content" "group" "math" "_math_group"))
+
+(defconst typst-ts-mode--container-node-types-regexp
+  (regexp-opt typst-ts-core--container-node-types)
+  "Container node types regexp.")
+
 (defun typst-ts-core-column-at-pos (point)
   "Get the column at position POINT."
   (save-excursion
@@ -45,6 +54,14 @@
     (back-to-indentation)
     (point)))
 
+(defun typst-ts-core-line-bol-nonwhite-pos (&optional pos)
+  "POS."
+  (save-excursion
+    (when pos
+      (goto-char pos))
+    (back-to-indentation)
+    (point)))
+
 (defun typst-ts-core-get-node-at-bol-nonwhite (&optional pos)
   "Get node at the first non-whitespace character at line beginning.
 If POS is given, operate on the line that POS locates at."
@@ -60,6 +77,62 @@ POS.  May return nil."
   (treesit-node-parent
    (typst-ts-core-get-node-at-bol-nonwhite pos)))
 
+(defun typst-ts-core-for-lines-covered-by-node (node fn)
+  "Execute FN on all lines covered by NODE.
+Currently the effect of FN shouldn't change line number."
+  (let ((ns (treesit-node-start node))
+        ;; use line number not position since when editing, position of node 
end
+        ;; changes, but the information is not updated
+        (ne-line-num (line-number-at-pos (treesit-node-end node))))
+    (save-excursion
+      (goto-char ns)
+      (while (< (line-number-at-pos) ne-line-num)
+        (funcall fn)
+        (forward-line 1))
+      ;; in case the last line is the last line of buffer, we separate this
+      ;; operation from while loop
+      (funcall fn))))
+
+
+;; Emacs 29 doesn't support string type PRED, so this function is created for
+;; convenience
+(defun typst-ts-core-parent-util-type (node type &optional include-node 
same-context)
+  "See `treesit-parent-until'.
+TYPE is an regexp expression for matching types.
+SAME-CONTEXT: whether the parent should be in the current context with NODE.
+The following example means parent item node is in a different context with
+`hi' text node
+- #[
+hi
+]
+NODE TYPE INCLUDE-NODE see `treesit-parent-until'."
+  (let ((matched-node
+         (treesit-parent-until
+          node
+          (lambda (node)
+            (let ((node-type (treesit-node-type node)))
+              (or (and same-context
+                       (string-match-p
+                        typst-ts-mode--container-node-types-regexp node-type))
+                  (string-match-p type node-type))))
+          include-node)))
+    (when (and matched-node
+               (string-match-p type (treesit-node-type matched-node)))
+      matched-node)))
+
+(defun typst-ts-core-prev-sibling-ignore-types (node types)
+  "Find previous sibling node ignoring nodes whose type matches TYPES.
+NODE: current node.
+TYPES is an regexp expression."
+  (let* ((prev-node (treesit-node-prev-sibling node))
+         (prev-node-type (treesit-node-type prev-node)))
+    (while (and prev-node-type
+                (string-match-p types prev-node-type))
+      (setq
+       prev-node (treesit-node-prev-sibling prev-node)
+       prev-node-type (treesit-node-type prev-node)))
+    prev-node))
+
 (defun typst-ts-core-node-get (node instructions)
   "Get things from NODE by INSTRUCTIONS.
 It's a copy of Emacs 30's `treesit-node-get' function."
diff --git a/typst-ts-editing.el b/typst-ts-editing.el
index d757421513..e1c7edd6be 100644
--- a/typst-ts-editing.el
+++ b/typst-ts-editing.el
@@ -104,36 +104,80 @@ Using ARG argument will ignore the context and it will 
insert a heading instead.
   "Handle RET depends on condition.
 When prefix ARG is non-nil, call global return function."
   (interactive "P")
-  (let (execute-result node)
+  (let (execute-result)
     (unless current-prefix-arg
       (setq
        execute-result
        (catch 'execute-result
-         (when-let* ((cur-pos (point))
-                     (cur-node (treesit-node-at cur-pos))
-                     (cur-node-type (treesit-node-type cur-node))
-                     (parent-node (treesit-node-parent cur-node))  ; could be 
nil
-                     (parent-node-type (treesit-node-type parent-node)))
+         (let* ((cur-pos (point))
+                (cur-node (treesit-node-at cur-pos))
+                (cur-node-type (treesit-node-type cur-node))
+                (parent-node (treesit-node-parent cur-node))  ; could be nil
+                (parent-node-type (treesit-node-type parent-node))
+                node)
            ;; (message "%s %s" cur-node parent-node)
            (cond
-            (arg (throw 'execute-result 'default))
             ;; on item node end
             ((and (eolp)
-                  (setq node 
(typst-ts-core-get-parent-of-node-at-bol-nonwhite))
-                  (equal (treesit-node-type node) "item"))
-             (if (> (treesit-node-child-count node) 1)
-                 (typst-ts-mode-insert--item node)
-               ;; no text means delete the item on current line: (item -) 
instead of (item - (text))
-               (beginning-of-line)
-               (kill-line)
-               (indent-according-to-mode))
+                  (setq node (typst-ts-core-parent-util-type
+                              
(typst-ts-core-get-parent-of-node-at-bol-nonwhite)
+                              "item" t t)))
+             (let* ((item-node node)
+                    (has-children (treesit-node-child item-node 1))
+                    (next-line-node
+                     (typst-ts-core-get-parent-of-node-at-bol-nonwhite
+                      (save-excursion
+                        (forward-line 1)
+                        (point))))
+                    (next-line-top-node  ; get container type or `item' type 
node
+                     (typst-ts-core-parent-util-type
+                      next-line-node
+                      (regexp-opt
+                       (append
+                        typst-ts-core--container-node-types
+                        '("item")))
+                      t)))
+               (if has-children
+                   ;; example:
+                   ;; - #[| <- return
+                   ;; ]
+                   (if (and next-line-top-node
+                            ;; end of buffer situation (or next line is the end
+                            ;; line (and no newline character))
+                            (not (equal
+                                  (line-number-at-pos
+                                   (save-excursion
+                                     (forward-line 1)
+                                     (point)))
+                                  (line-number-at-pos (point-max)))))
+                       (call-interactively #'newline)
+                     (typst-ts-mode-insert--item item-node))
+                 ;; no text means delete the item on current line: (item -)
+                 (beginning-of-line)
+                 (kill-line)
+                 ;; whether the previous line is in an item
+                 (let* ((prev-line-item-node
+                         (typst-ts-core-parent-util-type
+                          (typst-ts-core-get-parent-of-node-at-bol-nonwhite
+                           (save-excursion
+                             (forward-line -1)
+                             (point)))
+                          "item" t t)))
+                   (if prev-line-item-node
+                       (progn
+                         ;; sometimes there is no newlines characters at the 
EOL
+                         (ignore-errors
+                           (kill-line))
+                         (forward-line -1)
+                         (end-of-line)
+                         (call-interactively #'newline))
+                     (indent-according-to-mode)))))
              (throw 'execute-result 'success))
             )))))
     ;; execute default action if not successful
     (unless (eq execute-result 'success)
       ;; we only need to look for global keybinding, see `(elisp) Active 
Keymaps'
-      (let ((global-ret-function
-             (global-key-binding (kbd "RET"))))
+      (let ((global-ret-function (global-key-binding (kbd "RET"))))
         (if (not current-prefix-arg)
             (call-interactively global-ret-function)
           (if (yes-or-no-p
@@ -188,10 +232,25 @@ When there is no section it will insert a heading below 
point."
     (insert heading-level " ")
     (indent-according-to-mode)))
 
+(defun typst-ts-editing--indent-item-node-lines (node offset)
+  (let ((item-node-min-column
+         (typst-ts-core-column-at-pos
+          (typst-ts-core-line-bol-nonwhite-pos
+           (treesit-node-start node)))))
+    (if (< (+ item-node-min-column offset) 0)
+        (setq offset (- item-node-min-column)))
+    (typst-ts-core-for-lines-covered-by-node
+     node
+     (lambda ()
+       (indent-line-to
+        (+ (typst-ts-core-column-at-pos
+            (typst-ts-core-line-bol-nonwhite-pos))
+           offset))))))
+
 (defun typst-ts-mode-cycle (&optional _arg)
   "Cycle."
   (interactive "P")
-  (let (execute-result)
+  (let (execute-result node)
     (setq
      execute-result
      ;; plz manually throw `\'success' to `execute-result'
@@ -199,6 +258,10 @@ When there is no section it will insert a heading below 
point."
        (when-let* ((cur-pos (point))
                    (cur-node (treesit-node-at cur-pos))
                    (cur-node-type (treesit-node-type cur-node))
+                   (cur-line-nonwhite-bol-node
+                    (typst-ts-core-get-node-at-bol-nonwhite))
+                   (cur-line-nonwhite-bol-node-type
+                    (treesit-node-type cur-line-nonwhite-bol-node))
                    (parent-node (treesit-node-parent cur-node))  ; could be nil
                    (parent-node-type (treesit-node-type parent-node)))
          (cond
@@ -206,56 +269,52 @@ When there is no section it will insert a heading below 
point."
            (insert-tab)
            (throw 'execute-result 'success))
 
-          ((or (equal cur-node-type "parbreak")
-               (equal parent-node-type "item")
-               ;; please turn on whitespace-mode to test the following 
conditions
-               (eobp)
-               (eq (point) (1- (point-max))))
-           (when-let* ((cur-line-bol
-                        (save-excursion
-                          (back-to-indentation)
-                          (point)))
-                       (prev-nonwhite-pos (save-excursion
-                                            (goto-char cur-line-bol)
-                                            (skip-chars-backward "\s\r\n\t")
-                                            (1- (point))))
-                       ((and (not (eq prev-nonwhite-pos 0))  ; first line
-                             (not (eq  ; has previous sibling
-                                   (line-number-at-pos prev-nonwhite-pos)
-                                   (line-number-at-pos (point))))))
-                       (prev-nonwhite-line-node
-                        (treesit-node-at prev-nonwhite-pos))
-                       (prev-nonwhite-line-bol
-                        ;; TODO typst-ts-core-get-node-bol
-                        (save-excursion
-                          (goto-char prev-nonwhite-pos)
-                          (back-to-indentation)
-                          (point)))
-                       (prev-nonwhite-line-top-node
-                        (treesit-node-parent
-                         (treesit-node-at prev-nonwhite-line-bol)))
-                       (cur-line-bol-column (typst-ts-core-column-at-pos 
cur-line-bol))
-                       (prev-nonwhite-line-bol-column
-                        (typst-ts-core-column-at-pos prev-nonwhite-line-bol)))
-             (cond
-              ;; 1. el
-              ;; 2. psy| <- can toggle indent
-              ((and
-                (equal (treesit-node-type prev-nonwhite-line-top-node) "item")
-                ;; previous nonwhite-line ending is not '\' character
-                (not (equal (treesit-node-type prev-nonwhite-line-node) 
"linebreak")))
-               ;; TODO cycle all its children
-               (let (point)
-                 (if (not (eq cur-line-bol-column 
prev-nonwhite-line-bol-column))
-                     (progn
-                       (setq point (point))
-                       (indent-line-to prev-nonwhite-line-bol-column)
-                       (goto-char (- point typst-ts-mode-indent-offset)))
-                   (setq point (point))
-                   (indent-line-to (+ typst-ts-mode-indent-offset
-                                      prev-nonwhite-line-bol-column))
-                   (goto-char (+ typst-ts-mode-indent-offset point)))
-                 (throw 'execute-result 'success))))))
+
+          ((setq node
+                 (typst-ts-core-parent-util-type
+                  cur-line-nonwhite-bol-node "item" t t))
+           (let* ((cur-item-node node)
+                  (prev-significant-node
+                   (typst-ts-core-prev-sibling-ignore-types
+                    cur-item-node
+                    "parbreak"))
+                  (prev-significant-node-type
+                   (treesit-node-type prev-significant-node))
+                  prev-item-node)
+             
+             (if (equal prev-significant-node-type "item")
+                 (setq prev-item-node prev-significant-node)
+               (if (equal
+                    "item"
+                    (treesit-node-type
+                     (treesit-node-parent prev-significant-node)))
+                   (setq prev-item-node (treesit-node-parent
+                                         prev-significant-node))))
+             
+             ;; (message "%s, %s" cur-item-node prev-item-node)
+             
+             (unless prev-item-node
+               (throw 'execute-result 'default))
+
+             (let* ((cur-item-node-start-column
+                     (typst-ts-core-column-at-pos
+                      (treesit-node-start cur-item-node)))
+                    (prev-item-node-start-column
+                     (typst-ts-core-column-at-pos
+                      (treesit-node-start prev-item-node)))
+                    (offset
+                     (- cur-item-node-start-column
+                        prev-item-node-start-column)))
+               (if (>= offset typst-ts-mode-indent-offset)
+                   (typst-ts-editing--indent-item-node-lines
+                    cur-item-node
+                    (- (+ offset typst-ts-mode-indent-offset)))
+                 (typst-ts-editing--indent-item-node-lines
+                  cur-item-node
+                  (- typst-ts-mode-indent-offset (abs offset)))))
+
+             (throw 'execute-result 'success)))
+          
           (t nil)))))
     ;; execute default action if not successful
     (unless (eq execute-result 'success)
diff --git a/typst-ts-mode.el b/typst-ts-mode.el
index b292cf6732..0f9f1a8edd 100644
--- a/typst-ts-mode.el
+++ b/typst-ts-mode.el
@@ -388,55 +388,6 @@ If you want to customize the rules, please customize the 
same name variable
     (markup-standard code-standard math-standard)
     (markup-extended code-extended math-extended)))
 
-(defconst typst-ts-mode--container-node-types-regexp
-  ;; '_math_group' here is because `treesit-parent-until' doesn't hanlde node 
type alias well
-  ;; TODO file a bug
-  (regexp-opt '("block" "content" "group" "math" "_math_group"))
-  "Container node types regexp.")
-
-(defun typst-ts-mode--identation-item-linebreak (_node _parent bol)
-  "Where the current line is underneath a item with linebreak as ending.
-Ignore whitespaces.
-BOL: beginning of the current line.
-See `treesit-simple-indent-rules'."
-  (when-let* ((prev-nonwhite-pos (save-excursion
-                                   (goto-char bol)
-                                   (skip-chars-backward "\s\r\n\t")
-                                   (1- (point))))
-              ((and (not (eq prev-nonwhite-pos 0))  ; first line
-                    (not (eq  ; has previous sibling
-                          (line-number-at-pos prev-nonwhite-pos)
-                          (line-number-at-pos (point))))))
-              (prev-nonwhite-line-node
-               (treesit-node-at prev-nonwhite-pos))
-              ((equal (treesit-node-type prev-nonwhite-line-node) "linebreak"))
-
-              (prev-nonwhite-line-heading-node
-               (save-excursion
-                 (goto-char prev-nonwhite-pos)
-                 (back-to-indentation)
-                 (treesit-node-at (point))))
-              ((equal (treesit-node-type prev-nonwhite-line-heading-node) "-"))
-
-              (prev-nonwhite-line-top-node (treesit-node-parent
-                                            prev-nonwhite-line-heading-node)))
-    (equal (treesit-node-type prev-nonwhite-line-top-node) "item")))
-
-(defun typst-ts-mode--indentation-item-linebreak-get-pos (_node _parent bol)
-  "Get the previous item indentation position.
-See `typst-ts-mode--identation-item-linebreak'.
-BOL: beginning of the current line.
-This function is used instead of `parent-bol' is to make sure in the situation
-where current point is point-max with no newline character at ending can also
-work well.  Example:
-1. el \\$
-    2. psy \\$
-        | <- insert cursor should be here."
-  (save-excursion
-    (goto-char bol)
-    (skip-chars-backward "\s\r\n\t")
-    (back-to-indentation)
-    (point)))
 
 (defun typst-ts-mode-indent--grand-parent-bol (_node parent _bol)
   "Return the grand parent beginning of line position.
@@ -503,34 +454,32 @@ NODE, PARENT and BOL see `treesit-simple-indent-rules'."
      ;;   .split(" ")
      ((n-p-gp "." "field" nil) parent-bol typst-ts-mode-indent-offset)
 
-     ;; multi-line item
-     ;; - foo
-     ;;   bar
-     ((and (parent-is "item")
-           (lambda (node &rest parent bol)
-             (treesit-node-prev-sibling node)))
-      (lambda (node &rest parent bol)
-        (treesit-node-start (treesit-node-prev-sibling node)))
-      0)
+     ((parent-is "comment") prev-adaptive-prefix 0)
 
      ;; item - child item
-     ((and (node-is "item") (parent-is "item")) parent-bol 
typst-ts-mode-indent-offset)
-
-     ;; item - previous nonwhite line is item type and the ending is a 
linebreak
-     (typst-ts-mode--identation-item-linebreak
-      typst-ts-mode--indentation-item-linebreak-get-pos 
typst-ts-mode-indent-offset)
-
-     ;; item - item should follow its previous line item's indentation level
+     ((and (node-is "item") (parent-is "item")) parent-bol
+      typst-ts-mode-indent-offset)
+     
+     ;; multi-line item
+     ;; -  #[hi] foo
+     ;;    bar
+     ;; my try with `prev-adaptive-prefix' failed even after set the
+     ;; `adaptive-fill-regexp'
+     ((match nil "item" nil 2 nil)
+      typst-ts-mode--indentation-multiline-item-get-anchor 0)
+     
+     ;; item - new item content should follow its previous line's indentation
+     ;; level
      ((and no-node
-           (lambda (node parent &rest _)
-             (save-excursion
-               (forward-line -1)
-               (back-to-indentation)
-               (string= "item" (treesit-node-type
-                                (treesit-node-parent
-                                 (treesit-node-at (point))))))))
-      prev-line
-      0)
+           typst-ts-mode--indentation-prev-line-is-item-p
+           ;; not in container
+           ;; example:
+           ;; - hi
+           ;;   hi #[
+           ;;     - hello | <- return
+           ;;   ]
+           (not (n-p-gp nil "parbreak" 
,typst-ts-mode--container-node-types-regexp)))
+      typst-ts-mode--indentation-multiline-item-get-anchor_ 0)
 
      ;; raw block
      ;; whether normally or in insertion, the current node is always nil...
@@ -560,6 +509,32 @@ NODE, PARENT and BOL see `treesit-simple-indent-rules'."
      (catch-all prev-line 0)))
   "Tree-sitter indent rules for `typst-ts-mode'.")
 
+(defun typst-ts-mode--indentation-multiline-item-get-anchor (_node parent _bol)
+  "Return the start of second child of PARENT."
+  (treesit-node-start (treesit-node-child parent 1)))
+
+(defun typst-ts-mode--indentation-multiline-item-get-anchor_ (_node _parent 
_bol)
+  "Return the start of second child of the current item.
+This function is meant to be used when user hits a return key."
+  (treesit-node-start
+   (treesit-node-child
+    (typst-ts-core-parent-util-type
+     (treesit-node-at
+      (save-excursion
+        (forward-line -1)
+        (back-to-indentation)
+        (point)))
+     "item" t)
+    1)))
+
+(defun typst-ts-mode--indentation-prev-line-is-item-p (_node _parent _bol)
+  (save-excursion
+    (forward-line -1)
+    (back-to-indentation)
+    (typst-ts-core-parent-util-type
+     (treesit-node-at (point))
+     "item" t)))
+
 
 (defun typst-ts-mode-comment-setup()
   "Setup comment related stuffs for `typst-ts-mode'."
@@ -580,6 +555,7 @@ NODE, PARENT and BOL see `treesit-simple-indent-rules'."
          (grandparent-node (treesit-node-parent parent-node)))
     (and (equal (treesit-node-type node) "ident")
          (equal (treesit-node-type parent-node) "call")
+         (equal (treesit-node-field-name parent-node) "pattern")
          (equal (treesit-node-type grandparent-node) "let"))))
 
 (defun typst-ts-mode--imenu-name-function (node)
@@ -587,9 +563,13 @@ NODE, PARENT and BOL see `treesit-simple-indent-rules'."
   (treesit-node-text node))
 
 ;; outline-minor-mode
-(defconst typst-ts-mode-outline-regexp "^[[:space:]]*\\(=+\\) "
+(defconst typst-ts-mode-outline-regexp "^[[:space:]]*\\(=+\\)"
   "Regexp identifying Typst header.")
 
+(defconst typst-ts-mode-outline-heading-alist
+  '(("=" . 1) ("==" . 2) ("===" . 3) ("====" . 4) ("=====" . 5) ("======" . 6))
+  "See `outline-heading-alist'.")
+
 (defun typst-ts-mode-outline-level ()
   "Return the level of the heading at point."
   (save-excursion
@@ -695,6 +675,17 @@ typst tree sitter grammar (at least %s)!" 
(current-time-string min-time))
                (file-name-nondirectory buffer-file-name)
                typst-ts-compile-options))))
 
+  ;; Although without enabling `outline-minor-mode' also works, enabling it
+  ;; provides outline ellipsis (if you use `set-display-table-slot' to set)
+  (outline-minor-mode t)
+
+  ;; FIXME
+  ;; necessary for
+  ;; `typst-ts-mode-cycle'(`typst-ts-editing--indent-item-node-lines')
+  ;; since it calculate offset based on character
+  ;; (maybe also some indentation rules)
+  (indent-tabs-mode -1)
+
   (typst-ts-mode-check-grammar-version))
 
 ;;;###autoload
@@ -736,6 +727,14 @@ typst tree sitter grammar (at least %s)!" 
(current-time-string min-time))
 
   ;; Imenu
   (setq-local treesit-simple-imenu-settings
+              ;; Here we uses a trick. In the docs of
+              ;; `treesit-simple-imenu-settings', the second parameter should
+              ;; be a regexp string. However, it can be anything that
+              ;; the PRED in `treesit-thing-settings' can be
+              ;; For emacs 30, there are some restriction (second param must be
+              ;; regexp string) when you use default settings for outline
+              ;; (outline from imenu) see `treesit-major-mode-setup' and
+              ;; `treesit-outline-predicate'
               `(("Functions" typst-ts-mode--imenu-function-defintion-p nil
                  typst-ts-mode--imenu-name-function)
                 ("Headings" "^heading$" nil 
typst-ts-mode--imenu-name-function)))
@@ -748,17 +747,13 @@ typst tree sitter grammar (at least %s)!" 
(current-time-string min-time))
   ;; (setq-local treesit-thing-settings
   ;;             `((typst ())))
 
-
   ;; Outline
   (if nil  ; (>= emacs-major-version 30)
       ;; FIXME maybe it's a upstream bug. Circle top-level section will cycle 
all the content below
       (setq treesit-outline-predicate (regexp-opt '("section" "source_file")))
     (setq-local outline-regexp typst-ts-mode-outline-regexp)
     (setq-local outline-level #'typst-ts-mode-outline-level))
-  ;; Although without enabling `outline-minor-mode' also works, enabling it
-  ;; provides outline ellipsis
-  ;; TODO add it to after-hook
-  (outline-minor-mode t)
+  (setq-local outline-heading-alist typst-ts-mode-outline-heading-alist)
 
   (treesit-major-mode-setup)
 

Reply via email to