diff options
| author | Theodor Thornhill | 2022-12-25 20:11:59 +0100 |
|---|---|---|
| committer | Stefan Monnier | 2022-12-28 13:00:43 -0500 |
| commit | 7e98b8a0fa67f51784024fac3199d774dfa77192 (patch) | |
| tree | 7677db3c9e7685f40f7bbd8b214350ceb0065396 | |
| parent | 7dc24fb611c72697b7d34ba2abce0a0abc972a6b (diff) | |
| download | emacs-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/NEWS | 9 | ||||
| -rw-r--r-- | lisp/simple.el | 88 | ||||
| -rw-r--r-- | lisp/treesit.el | 29 |
3 files changed, 86 insertions, 40 deletions
| @@ -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' | ||
| 48 | Emacs now can set this defvar to customize the behavior of the | ||
| 49 | 'transpose-sexps' function. | ||
| 50 | |||
| 51 | ** New function 'treesit-transpose-sexps' | ||
| 52 | treesit.el now unconditionally sets 'transpose-sexps-function' for all | ||
| 53 | Tree-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 | |||
| 8474 | This function takes one argument ARG, a number. Its expected | ||
| 8475 | return value is a position pair, which is a cons (BEG . END), | ||
| 8476 | where 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. |
| 8443 | Unlike `transpose-words', point must be between the two sexps and not | 8480 | Unlike `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. |
| 8512 | Works for lines, sentences, paragraphs, etc. MOVER is a function that | 8518 | Works for lines, sentences, paragraphs, etc. MOVER is a function |
| 8513 | moves forward by units of the given object (e.g. `forward-sentence', | 8519 | that moves forward by units of the given |
| 8514 | `forward-paragraph'). If ARG is zero, exchanges the current object | 8520 | object (e.g. `forward-sentence', `forward-paragraph'), or a |
| 8515 | with the one containing mark. If ARG is an integer, moves the | 8521 | function calculating a cons of buffer positions. |
| 8516 | current object past ARG following (if ARG is positive) or | 8522 | |
| 8517 | preceding (if ARG is negative) objects, leaving point after the | 8523 | If ARG is zero, exchanges the current object with the one |
| 8518 | current object." | 8524 | containing mark. If ARG is an integer, moves the current object |
| 8525 | past ARG following (if ARG is positive) or preceding (if ARG is | ||
| 8526 | negative) 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. | ||
| 1587 | Arg is the same as in `transpose-sexps'. | ||
| 1588 | |||
| 1589 | Locate the node closest to POINT, and transpose that node with | ||
| 1590 | its sibling node ARG nodes away. | ||
| 1591 | |||
| 1592 | Return a pair of positions as described by | ||
| 1593 | `transpose-sexps-function' for use in `transpose-subr' and | ||
| 1594 | friends." | ||
| 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 | ||