branch: elpa/typst-ts-mode commit 181caafe047813712505e3ff4590c0c686639987 Author: Huan Nguyen <nguyenthieuh...@gmail.com> Commit: Huan Nguyen <nguyenthieuh...@gmail.com>
feat: Exchange current heading with above heading. --- typst-ts-mode.el | 110 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/typst-ts-mode.el b/typst-ts-mode.el index 42bbe652a1..3989302dd8 100644 --- a/typst-ts-mode.el +++ b/typst-ts-mode.el @@ -608,28 +608,108 @@ This does not handle #heading function." (pcase direction ('right (concat heading-string "=")) ('left (substring-no-properties heading-string 1 heading-level)) - (_ (error "%s is not one of: `LEFT' `RIGHT'" direction))))))) + (_ (error "%s is not one of: `left' `right'" direction))))))) + +(defun typst-ts-mode-heading--same-or-higher (node traverse-fn) + "Return the first heading that is the same level or higher than NODE. +`car' will be the found heading node. +`cdr' will say if it is the same level or not. +TRAVERSE-FN dictates in which direction to search. +`treesit-node-next-sibling' for down. +`treesit-node-prev-sibling' for up." + (let ((iterate (funcall traverse-fn node)) + (level (typst-ts-mode-heading--level node)) + (iterate-level nil)) + (while (and iterate + (not (and (string= (treesit-node-type iterate) "heading") + (or (= (setq + iterate-level ;; hack to make it not eval twice + (typst-ts-mode-heading--level iterate)) + level) + ;; parent heading or NODE was a leaf + (< iterate-level level))))) + (setq iterate (funcall traverse-fn iterate))) + ;; there are no level 0 heading + (cons iterate (= (if iterate-level iterate-level 0) level)))) + +(defun typst-ts-mode-heading--level (node) + "Get the level of the heading NODE. +This functions does not check if NODE is actually a heading." + (length (treesit-node-text (treesit-node-child node 0)))) + +(defun typst-ts-mode-heading--find-same-level (node traverse-fn) + "Return the first node with the same level as NODE. +It will report a user-error when it could not find a node +or it was blocked by its parent heading. +See `typst-ts-mode-heading--same-or-higher' for TRAVERSE-FN." + (let* ((other-heading/level + (typst-ts-mode-heading--same-or-higher node traverse-fn))) + (if (cdr other-heading/level) + (car other-heading/level) + (user-error "Could not find another heading")))) + +(defun typst-ts-mode-heading-up () + (interactive) + (typst-ts-mode-meta--dwim 'up)) + +(defun typst-ts-mode-heading--up (current-heading) + "Switch two heading of same level. +CURRENT-HEADING and its content with above heading and its content." + (let* ((current-heading-start (treesit-node-start current-heading)) + (other-heading + (typst-ts-mode-heading--find-same-level + current-heading + #'treesit-node-prev-sibling)) + (other-heading-start (treesit-node-start other-heading)) + (other-heading-end (1- current-heading-start)) + (current-heading-end (car (typst-ts-mode-heading--same-or-higher + current-heading + #'treesit-node-next-sibling))) + (current-heading-content nil) + (other-heading-content + (buffer-substring other-heading-start + other-heading-end))) + (setq current-heading-end (if current-heading-end + (1- (treesit-node-start current-heading-end)) + (point-max))) + (setq current-heading-content + (buffer-substring current-heading-start + current-heading-end)) + (delete-region other-heading-start current-heading-end) + (goto-char other-heading-start) + (if (= ?\n (aref current-heading-content + (1- (length current-heading-content)))) + (insert current-heading-content) + (insert (concat current-heading-content "\n"))) + (insert other-heading-content))) (defun typst-ts-mode-meta--dwim (direction) "Do something depending on the context with meta key + DIRECTION. -`left': `typst-ts-mode-heading-decrease' -`right': `typst-ts-mode-heading-increase' +`left': `typst-ts-mode-heading-decrease'. +`right': `typst-ts-mode-heading-increase'. When there is no relevant action to do it will execute the relevant function in the `GLOBAL-MAP' (example: `right-word')." (let ((heading (typst-ts-mode-heading--at-point-p)) - (key nil)) + ;; car function, cdr string of function for `substitute-command-keys' + (call-me/string + (pcase direction + ('left + (cons (lambda (node) (typst-ts-mode-heading--increase/decrease + direction (treesit-node-child node 0))) + "\\[typst-ts-mode-heading-decrease]")) + ('right + (cons (lambda (node) (typst-ts-mode-heading--increase/decrease + direction (treesit-node-child node 0))) + "\\[typst-ts-mode-heading-decrease]")) + ('up + (cons (lambda (node) (typst-ts-mode-heading--up node)) + "\\[typst-ts-mode-heading-up]")) + ('down "\\[typst-ts-mode-heading-down]") + (_ (error "%s is not one of: `right' `left'" direction))))) (if heading - (typst-ts-mode-heading--increase/decrease direction (treesit-node-child heading 0)) - (progn - (setq key - (substitute-command-keys - (concat - "\\[typst-ts-mode-heading-" - (pcase direction - ('left "decrease]") - ('right "increase]") - (_ (error "%s is not one of: `RIGHT' `LEFT'" direction)))))) - (call-interactively (keymap-lookup global-map key)))))) + (funcall (car call-me/string) heading) + (call-interactively + (keymap-lookup global-map (substitute-command-keys (cdr call-me/string))))))) ;;;###autoload (defun typst-ts-mode-heading-increase ()