branch: elpa/typst-ts-mode
commit 619acd01e210890e746f2e693ca952cb4d68f35c
Merge: 2c82246569 e2e3ed498b
Author: Huan Nguyen <nguyenthieuh...@gmail.com>
Commit: Huan Nguyen <nguyenthieuh...@gmail.com>

    Merge branch 'feat-16/list-item-reordering' into develop
---
 typst-ts-editing.el | 214 +++++++++++++++++++++++++++++++++++++++++++++-------
 typst-ts-mode.el    |   8 +-
 2 files changed, 189 insertions(+), 33 deletions(-)

diff --git a/typst-ts-editing.el b/typst-ts-editing.el
index d08cd40e97..7b920fa10d 100644
--- a/typst-ts-editing.el
+++ b/typst-ts-editing.el
@@ -28,22 +28,22 @@
 (defun typst-ts-mode-heading-up ()
   "Switch the current heading with the heading above."
   (interactive)
-  (typst-ts-mode-meta--dwim 'up))
+  (call-interactively #'outline-move-subtree-up))
 
 (defun typst-ts-mode-heading-down ()
   "Switch the current heading with the heading below."
   (interactive)
-  (typst-ts-mode-meta--dwim 'down))
+  (call-interactively #'outline-move-subtree-down))
 
-(defun typst-ts-mode-heading-increase ()
+(defun typst-ts-mode-heading-left ()
   "Increase the heading level."
   (interactive)
-  (typst-ts-mode-meta--dwim 'right))
+  (call-interactively #'outline-promote))
 
-(defun typst-ts-mode-heading-decrease ()
+(defun typst-ts-mode-heading-right ()
   "Decrease heading level."
   (interactive)
-  (typst-ts-mode-meta--dwim 'left))
+  (call-interactively #'outline-demote))
 
 (defun typst-ts-mode-heading--at-point-p ()
   "Whether the current line is a heading.
@@ -57,35 +57,191 @@ Return the heading node when yes otherwise nil."
         node
       nil)))
 
+(defun typst-ts-mode-item--at-point-p ()
+  "Return item node when point is on item.
+Otherwise nil."
+  (treesit-parent-until (treesit-node-at (point))
+                        (lambda (x) (string= (treesit-node-type x)
+                                             "item"))))
+
+(defun typst-ts-mode-item--with-siblings ()
+  "Return (prev current next numbered-p) items.
+
+The last item in the last tells you if the list is numbered (t) or not (nil).
+
+When current does not have a previous or next sibling,
+the index for it will be nil.
+
+Being a different item type does not count as sibling, ex:
+1. foo
+- bar
+
+When point is not on an item node return nil."
+  (when-let* ((node (typst-ts-mode-item--at-point-p))
+              (get-item-type (lambda (x)
+                               (treesit-node-text (treesit-node-child x 0))))
+              (item-type (funcall get-item-type node))
+              (node-numbered-p t)
+              (same-item-type (lambda (x)
+                                (let ((type (funcall get-item-type x)))
+                                  (or (string= type item-type)
+                                      ;; are they numbers?
+                                      (and node-numbered-p
+                                           (not (= (string-to-number type)
+                                                   0)))))))
+              (only-if (lambda (x) (and (string= (treesit-node-type x)
+                                                 "item")
+                                        (funcall same-item-type x)
+                                        x))))
+    (setq node-numbered-p (not (= (string-to-number item-type) 0)))
+    (cond
+     ((not node) node)
+     (node (list (funcall only-if (treesit-node-prev-sibling node))
+                 node
+                 (funcall only-if (treesit-node-next-sibling node))
+                 node-numbered-p)))))
+
+(defun typst-ts-mode--swap-regions (start1 end1 start2 end2)
+  "Swap region between START1 and END1 with region between START2 and END2."
+  (let ((text1 (buffer-substring start1 end1))
+        (text2 (buffer-substring start2 end2))
+        (marker1-start (make-marker))
+        (marker1-end (make-marker))
+        (marker2-start (make-marker))
+        (marker2-end (make-marker)))
+    (set-marker marker1-start start1)
+    (set-marker marker1-end end1)
+    (set-marker marker2-start start2)
+    (set-marker marker2-end end2)
+
+    (delete-region marker1-start marker1-end)
+    (delete-region marker2-start marker2-end)
+
+    (goto-char marker1-start)
+    (insert text2)
+
+    (goto-char marker2-start)
+    (insert text1)
+
+    (set-marker marker1-start nil)
+    (set-marker marker1-end nil)
+    (set-marker marker2-start nil)
+    (set-marker marker2-end nil)))
+
+(defun typst-ts-mode-item--move (direction)
+  "Moves item node up or down (swap).
+DIRECTION should be `up' or `down'."
+  (let* ( previous current next swap-with numbered-p
+          (bind (lambda ()
+                  (pcase direction
+                    ('up
+                     (setq swap-with previous))
+                    ('down
+                     (setq swap-with next))
+                    (_ (error "%s is not one of: `up' `down'" direction))))))
+    (seq-setq (previous current next numbered-p)
+              (typst-ts-mode-item--with-siblings))
+    (unless current
+      (error "Point is not on an item"))
+    (funcall bind)
+    (unless swap-with
+      (user-error "There is no %s item to swap with"
+                  (if (eq direction 'up) "previous" "next")))
+    ;; numbers may need to be swapped
+    (when numbered-p
+      (let* ((number1 (treesit-node-child current 0))
+             (number2 (treesit-node-child swap-with 0))
+             (current-begin (treesit-node-start number1))
+             (current-end (treesit-node-end number1))
+             (other-begin (treesit-node-start number2))
+             (other-end (treesit-node-end number2)))
+        (save-excursion
+          (typst-ts-mode--swap-regions current-begin current-end
+                                       other-begin other-end))))
+    ;; the nodes must be reinitialized
+    (seq-setq (previous current next numbered-p)
+              (typst-ts-mode-item--with-siblings))
+    (funcall bind)
+    (let ((current-begin (treesit-node-start current))
+          (current-end (treesit-node-end current))
+          (other-begin (treesit-node-start swap-with))
+          (other-end (treesit-node-end swap-with))
+          (column (current-column)))
+      (typst-ts-mode--swap-regions current-begin current-end
+                                   other-begin other-end)
+      (move-to-column column))))
+
+(defun typst-ts-mode-item-up ()
+  "Move the item at point up."
+  (interactive)
+  (typst-ts-mode-item--move 'up))
+
+(defun typst-ts-mode-item-down ()
+  "Move the item at point down."
+  (interactive)
+  (typst-ts-mode-item--move 'down))
+
+(defun typst-ts-mode-meta-up ()
+  "See `typst-ts-mode-meta--dwim'."
+  (interactive)
+  (call-interactively (typst-ts-mode-meta--dwim 'up)))
+
+(defun typst-ts-mode-meta-down ()
+  "See `typst-ts-mode-meta--dwim'."
+  (interactive)
+  (message "%s" (typst-ts-mode-meta--dwim 'down))
+  (call-interactively (typst-ts-mode-meta--dwim 'down)))
+
+(defun typst-ts-mode-meta-left ()
+  "See `typst-ts-mode-meta--dwim'."
+  (interactive)
+  (call-interactively (typst-ts-mode-meta--dwim 'left)))
+
+(defun typst-ts-mode-meta-right ()
+  "See `typst-ts-mode-meta--dwim'."
+  (interactive)
+  (call-interactively (typst-ts-mode-meta--dwim 'right)))
+
 (defun typst-ts-mode-meta--dwim (direction)
-  "Do something depending on the context with meta key + DIRECTION.
+  "Return function depending on the context with meta key + DIRECTION.
+
+When point is at heading:
 `left': `typst-ts-mode-heading-decrease',
 `right': `typst-ts-mode-heading-increase',
 `up': `typst-ts-mode-heading-up',
 `down': `typst-ts-mode-heading-down'.
-When there is no relevant action to do it will execute the relevant function in
+
+When point is at item list:
+`up': `typst-ts-mode-item-up'
+`down': `typst-ts-mode-item-down'
+
+When there is no relevant action to do it will return the relevant function in
 the `GLOBAL-MAP' (example: `right-word')."
-  (let ((heading (typst-ts-mode-heading--at-point-p))
-        ;; car function, cdr string of function for `substitute-command-keys'
-        (call-me/string
-         (pcase direction
-           ('left
-            (cons #'outline-promote
-                  "\\[typst-ts-mode-heading-decrease]"))
-           ('right
-            (cons #'outline-demote
-                  "\\[typst-ts-mode-heading-decrease]"))
-           ('up
-            (cons #'outline-move-subtree-up
-                  "\\[typst-ts-mode-heading-up]"))
-           ('down
-            (cons #'outline-move-subtree-down
-                  "\\[typst-ts-mode-heading-down]"))
-           (_ (error "%s is not one of: `right' `left'" direction)))))
-    (if heading
-        (call-interactively (car call-me/string))
-      (call-interactively
-       (keymap-lookup global-map (substitute-command-keys (cdr 
call-me/string)))))))
+  (let* ((prefix "typst-ts-mode-")
+         (mid (cond
+               ((typst-ts-mode-heading--at-point-p) "heading")
+               ((and (typst-ts-mode-item--at-point-p)
+                     ;; does not exist, maybe will exist at some point
+                     (not (or (eq 'left direction)
+                              (eq 'right direction))))
+                "item")
+               (t nil)))
+         (end
+          (pcase direction
+            ('left
+             "-left")
+            ('right
+             "-right")
+            ('up
+             "-up")
+            ('down
+             "-down")
+            (_ (error "DIRECTION: %s is not one of: `right' `left', `up', 
`down'"
+                      direction)))))
+    (if (not mid)
+        (keymap-lookup global-map (substitute-command-keys
+                                   (concat "\\[" prefix "meta" end "]")))
+      (intern-soft (concat prefix mid end)))))
 
 (defun typst-ts-mode-meta-return (&optional arg)
   "Depending on context, insert a heading or insert an item.
diff --git a/typst-ts-mode.el b/typst-ts-mode.el
index 3b3032c666..6b34097887 100644
--- a/typst-ts-mode.el
+++ b/typst-ts-mode.el
@@ -609,10 +609,10 @@ FILE: file path for the result compile file."
   "C-c C-w" #'typst-ts-watch-mode
   "C-c C-p" #'typst-ts-mode-preview
 
-  "M-<left>" #'typst-ts-mode-heading-decrease
-  "M-<right>" #'typst-ts-mode-heading-increase
-  "M-<down>" #'typst-ts-mode-heading-down
-  "M-<up>" #'typst-ts-mode-heading-up
+  "M-<left>" #'typst-ts-mode-meta-left
+  "M-<right>" #'typst-ts-mode-meta-right
+  "M-<down>" #'typst-ts-mode-meta-down
+  "M-<up>" #'typst-ts-mode-meta-up
   "M-<return>" #'typst-ts-mode-meta-return
 
   ;; don't bind <return>

Reply via email to