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>