aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTheodor Thornhill2022-12-25 20:11:59 +0100
committerStefan Monnier2022-12-28 13:00:43 -0500
commit7e98b8a0fa67f51784024fac3199d774dfa77192 (patch)
tree7677db3c9e7685f40f7bbd8b214350ceb0065396
parent7dc24fb611c72697b7d34ba2abce0a0abc972a6b (diff)
downloademacs-7e98b8a0fa67f51784024fac3199d774dfa77192.tar.gz
emacs-7e98b8a0fa67f51784024fac3199d774dfa77192.zip
Add treesit-transpose-sexps (bug#60128)
We don't really need to rely on forward-sexp to define what to transpose. In tree-sitter we can consider siblings as "balanced expressions", and swap them without doing any movement to calculate where the siblings in question are. * lisp/simple.el (transpose-sexps-function): New defvar-local. (transpose-sexps): Use the new defvar-local if available. (transpose-subr): Check whether the mover function returns a cons of conses, then run transpose-subr-1 on the position-pairs. * lisp/treesit.el (treesit-transpose-sexps): New function.
-rw-r--r--etc/NEWS9
-rw-r--r--lisp/simple.el88
-rw-r--r--lisp/treesit.el29
3 files changed, 86 insertions, 40 deletions
diff --git a/etc/NEWS b/etc/NEWS
index d17e1f1f89f..83aa81eb4b8 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -44,6 +44,15 @@ example, as part of preview for iconified frames.
44 44
45* Editing Changes in Emacs 30.1 45* Editing Changes in Emacs 30.1
46 46
47** New helper 'transpose-sexps-function'
48Emacs now can set this defvar to customize the behavior of the
49'transpose-sexps' function.
50
51** New function 'treesit-transpose-sexps'
52treesit.el now unconditionally sets 'transpose-sexps-function' for all
53Tree-sitter modes. This functionality utilizes the new
54'transpose-sexps-function'.
55
47 56
48* Changes in Specialized Modes and Packages in Emacs 30.1 57* Changes in Specialized Modes and Packages in Emacs 30.1
49--- 58---
diff --git a/lisp/simple.el b/lisp/simple.el
index 4551b749d56..cf0845853a2 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8438,6 +8438,43 @@ are interchanged."
8438 (interactive "*p") 8438 (interactive "*p")
8439 (transpose-subr 'forward-word arg)) 8439 (transpose-subr 'forward-word arg))
8440 8440
8441(defvar transpose-sexps-function
8442 (lambda (arg)
8443 ;; Here we should try to simulate the behavior of
8444 ;; (cons (progn (forward-sexp x) (point))
8445 ;; (progn (forward-sexp (- x)) (point)))
8446 ;; Except that we don't want to rely on the second forward-sexp
8447 ;; putting us back to where we want to be, since forward-sexp-function
8448 ;; might do funny things like infix-precedence.
8449 (if (if (> arg 0)
8450 (looking-at "\\sw\\|\\s_")
8451 (and (not (bobp))
8452 (save-excursion
8453 (forward-char -1)
8454 (looking-at "\\sw\\|\\s_"))))
8455 ;; Jumping over a symbol. We might be inside it, mind you.
8456 (progn (funcall (if (> arg 0)
8457 #'skip-syntax-backward #'skip-syntax-forward)
8458 "w_")
8459 (cons (save-excursion (forward-sexp arg) (point)) (point)))
8460 ;; Otherwise, we're between sexps. Take a step back before jumping
8461 ;; to make sure we'll obey the same precedence no matter which
8462 ;; direction we're going.
8463 (funcall (if (> arg 0) #'skip-syntax-backward #'skip-syntax-forward)
8464 " .")
8465 (cons (save-excursion (forward-sexp arg) (point))
8466 (progn (while (or (forward-comment (if (> arg 0) 1 -1))
8467 (not (zerop (funcall (if (> arg 0)
8468 #'skip-syntax-forward
8469 #'skip-syntax-backward)
8470 ".")))))
8471 (point)))))
8472 "If non-nil, `transpose-sexps' delegates to this function.
8473
8474This function takes one argument ARG, a number. Its expected
8475return value is a position pair, which is a cons (BEG . END),
8476where BEG and END are buffer positions.")
8477
8441(defun transpose-sexps (arg &optional interactive) 8478(defun transpose-sexps (arg &optional interactive)
8442 "Like \\[transpose-chars] (`transpose-chars'), but applies to sexps. 8479 "Like \\[transpose-chars] (`transpose-chars'), but applies to sexps.
8443Unlike `transpose-words', point must be between the two sexps and not 8480Unlike `transpose-words', point must be between the two sexps and not
@@ -8453,38 +8490,7 @@ report errors as appropriate for this kind of usage."
8453 (condition-case nil 8490 (condition-case nil
8454 (transpose-sexps arg nil) 8491 (transpose-sexps arg nil)
8455 (scan-error (user-error "Not between two complete sexps"))) 8492 (scan-error (user-error "Not between two complete sexps")))
8456 (transpose-subr 8493 (transpose-subr transpose-sexps-function arg 'special)))
8457 (lambda (arg)
8458 ;; Here we should try to simulate the behavior of
8459 ;; (cons (progn (forward-sexp x) (point))
8460 ;; (progn (forward-sexp (- x)) (point)))
8461 ;; Except that we don't want to rely on the second forward-sexp
8462 ;; putting us back to where we want to be, since forward-sexp-function
8463 ;; might do funny things like infix-precedence.
8464 (if (if (> arg 0)
8465 (looking-at "\\sw\\|\\s_")
8466 (and (not (bobp))
8467 (save-excursion
8468 (forward-char -1)
8469 (looking-at "\\sw\\|\\s_"))))
8470 ;; Jumping over a symbol. We might be inside it, mind you.
8471 (progn (funcall (if (> arg 0)
8472 'skip-syntax-backward 'skip-syntax-forward)
8473 "w_")
8474 (cons (save-excursion (forward-sexp arg) (point)) (point)))
8475 ;; Otherwise, we're between sexps. Take a step back before jumping
8476 ;; to make sure we'll obey the same precedence no matter which
8477 ;; direction we're going.
8478 (funcall (if (> arg 0) 'skip-syntax-backward 'skip-syntax-forward)
8479 " .")
8480 (cons (save-excursion (forward-sexp arg) (point))
8481 (progn (while (or (forward-comment (if (> arg 0) 1 -1))
8482 (not (zerop (funcall (if (> arg 0)
8483 'skip-syntax-forward
8484 'skip-syntax-backward)
8485 ".")))))
8486 (point)))))
8487 arg 'special)))
8488 8494
8489(defun transpose-lines (arg) 8495(defun transpose-lines (arg)
8490 "Exchange current line and previous line, leaving point after both. 8496 "Exchange current line and previous line, leaving point after both.
@@ -8509,13 +8515,15 @@ With argument 0, interchanges line point is in with line mark is in."
8509;; FIXME document SPECIAL. 8515;; FIXME document SPECIAL.
8510(defun transpose-subr (mover arg &optional special) 8516(defun transpose-subr (mover arg &optional special)
8511 "Subroutine to do the work of transposing objects. 8517 "Subroutine to do the work of transposing objects.
8512Works for lines, sentences, paragraphs, etc. MOVER is a function that 8518Works for lines, sentences, paragraphs, etc. MOVER is a function
8513moves forward by units of the given object (e.g. `forward-sentence', 8519that moves forward by units of the given
8514`forward-paragraph'). If ARG is zero, exchanges the current object 8520object (e.g. `forward-sentence', `forward-paragraph'), or a
8515with the one containing mark. If ARG is an integer, moves the 8521function calculating a cons of buffer positions.
8516current object past ARG following (if ARG is positive) or 8522
8517preceding (if ARG is negative) objects, leaving point after the 8523 If ARG is zero, exchanges the current object with the one
8518current object." 8524containing mark. If ARG is an integer, moves the current object
8525past ARG following (if ARG is positive) or preceding (if ARG is
8526negative) objects, leaving point after the current object."
8519 (let ((aux (if special mover 8527 (let ((aux (if special mover
8520 (lambda (x) 8528 (lambda (x)
8521 (cons (progn (funcall mover x) (point)) 8529 (cons (progn (funcall mover x) (point))
@@ -8542,6 +8550,8 @@ current object."
8542 (goto-char (+ (car pos2) (- (cdr pos1) (car pos1)))))))) 8550 (goto-char (+ (car pos2) (- (cdr pos1) (car pos1))))))))
8543 8551
8544(defun transpose-subr-1 (pos1 pos2) 8552(defun transpose-subr-1 (pos1 pos2)
8553 (unless (and pos1 pos2)
8554 (error "Don't have two things to transpose"))
8545 (when (> (car pos1) (cdr pos1)) (setq pos1 (cons (cdr pos1) (car pos1)))) 8555 (when (> (car pos1) (cdr pos1)) (setq pos1 (cons (cdr pos1) (car pos1))))
8546 (when (> (car pos2) (cdr pos2)) (setq pos2 (cons (cdr pos2) (car pos2)))) 8556 (when (> (car pos2) (cdr pos2)) (setq pos2 (cons (cdr pos2) (car pos2))))
8547 (when (> (car pos1) (car pos2)) 8557 (when (> (car pos1) (car pos2))
diff --git a/lisp/treesit.el b/lisp/treesit.el
index cefbed1a168..203a724fe7a 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1582,6 +1582,32 @@ BACKWARD and ALL are the same as in `treesit-search-forward'."
1582 (goto-char current-pos))) 1582 (goto-char current-pos)))
1583 node)) 1583 node))
1584 1584
1585(defun treesit-transpose-sexps (&optional arg)
1586 "Tree-sitter `transpose-sexps' function.
1587Arg is the same as in `transpose-sexps'.
1588
1589Locate the node closest to POINT, and transpose that node with
1590its sibling node ARG nodes away.
1591
1592Return a pair of positions as described by
1593`transpose-sexps-function' for use in `transpose-subr' and
1594friends."
1595 (let* ((parent (treesit-node-parent (treesit-node-at (point))))
1596 (child (treesit-node-child parent 0 t)))
1597 (named-let loop ((prev child)
1598 (next (treesit-node-next-sibling child t)))
1599 (when (and prev next)
1600 (if (< (point) (treesit-node-end next))
1601 (if (= arg -1)
1602 (cons (treesit-node-start prev)
1603 (treesit-node-end prev))
1604 (when-let ((n (treesit-node-child
1605 parent (+ arg (treesit-node-index prev t)) t)))
1606 (cons (treesit-node-end n)
1607 (treesit-node-start n))))
1608 (loop (treesit-node-next-sibling prev t)
1609 (treesit-node-next-sibling next t)))))))
1610
1585;;; Navigation, defun, things 1611;;; Navigation, defun, things
1586;; 1612;;
1587;; Emacs lets you define "things" by a regexp that matches the type of 1613;; Emacs lets you define "things" by a regexp that matches the type of
@@ -2111,7 +2137,8 @@ before calling this function."
2111 ;; Defun name. 2137 ;; Defun name.
2112 (when treesit-defun-name-function 2138 (when treesit-defun-name-function
2113 (setq-local add-log-current-defun-function 2139 (setq-local add-log-current-defun-function
2114 #'treesit-add-log-current-defun))) 2140 #'treesit-add-log-current-defun))
2141 (setq-local transpose-sexps-function #'treesit-transpose-sexps))
2115 2142
2116;;; Debugging 2143;;; Debugging
2117 2144