From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Theodor Thornhill via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#60128: 30.0.50; [PATCH]: Add treesit-transpose-sexps Date: Mon, 26 Dec 2022 23:37:40 +0100 Message-ID: <87wn6dg5aj.fsf@thornhill.no> References: <87a63nru7n.fsf@thornhill.no> <87edsytcoh.fsf@thornhill.no> <873591honu.fsf@thornhill.no> <0D9F1198-B5FB-451D-B0E8-DB22910DAB58@gmail.com> Reply-To: Theodor Thornhill Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="33749"; mail-complaints-to="usenet@ciao.gmane.io" Cc: 60128@debbugs.gnu.org, eliz@gnu.org, Stefan Monnier To: Yuan Fu Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Mon Dec 26 23:38:20 2022 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p9w6u-0008co-33 for geb-bug-gnu-emacs@m.gmane-mx.org; Mon, 26 Dec 2022 23:38:20 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1p9w6d-0000DZ-PF; Mon, 26 Dec 2022 17:38:03 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p9w6c-0000D0-Lp for bug-gnu-emacs@gnu.org; Mon, 26 Dec 2022 17:38:02 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1p9w6c-0005io-85 for bug-gnu-emacs@gnu.org; Mon, 26 Dec 2022 17:38:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1p9w6b-0004dt-QR for bug-gnu-emacs@gnu.org; Mon, 26 Dec 2022 17:38:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Theodor Thornhill Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Mon, 26 Dec 2022 22:38:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 60128 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 60128-submit@debbugs.gnu.org id=B60128.167209426917825 (code B ref 60128); Mon, 26 Dec 2022 22:38:01 +0000 Original-Received: (at 60128) by debbugs.gnu.org; 26 Dec 2022 22:37:49 +0000 Original-Received: from localhost ([127.0.0.1]:54319 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p9w6O-0004dR-9y for submit@debbugs.gnu.org; Mon, 26 Dec 2022 17:37:48 -0500 Original-Received: from out-161.mta0.migadu.com ([91.218.175.161]:58424) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p9w6I-0004dE-G3 for 60128@debbugs.gnu.org; Mon, 26 Dec 2022 17:37:47 -0500 X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=thornhill.no; s=key1; t=1672094261; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=7l17f3nPeougVoEJ+eTV4fo5IbsRS1+G0buVl4xl+eY=; b=fhEKPdXitqR4dmVS3JnwsUE2ujBX1UsJOKUMUtuwiyYN2PEtjzCj4ueLc82eS8pgRt7PhB 6RDPf8Kct9OIuQytJr3UQYutsix/vyvZzcfEofzTvCCKWqQf/dh2nEbOC9CPVVI3Og6Gya I/2SQcSf+fRi9tX53Og7rKuXsW+XcSrky8aF8siahaGfMf145ZgObqGmnm9BUV/Ku+Xnkw WUCFiZA1dl0bb4N2kD1K0bMYC1715X5A7owKA+NCFcW341zpBABV0tzVKqMwBDzenVczEm HeT8BwJtQndRko2tWzOWkMncMwnBrMdKPdmDh4FijWgbbURlh696fFrEhncSRg== In-Reply-To: <0D9F1198-B5FB-451D-B0E8-DB22910DAB58@gmail.com> X-Migadu-Flow: FLOW_OUT X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:251922 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Yuan Fu writes: >> On Dec 26, 2022, at 12:53 PM, Theodor Thornhill wrot= e: >>=20 >> Theodor Thornhill writes: >>=20 >>> Theodor Thornhill writes: >>>=20 >>>> Hi there! >>>>=20 >>>> Attached is a patch that enables transpose-sexps for tree-sitter enabl= ed >>>> modes. >>>>=20 >>>> This function will swap the node _before_ node-at-point with the node >>>> _after_, so it would do something like: >>>>=20 >>>> foo a|nd bar =3D> bar and foo| >>>>=20 >>>> or >>>> foo(a + 4,| y + c * b, b, d); =3D> foo(y + c * b, a + 4|, b, d); >>>>=20 >>>> It will _not_ try to swap things that are not siblings. I think that >>>> makes sense in the case of non-lisp languages, since _most_ places you >>>> can transpose-sexps you will end up with broken code. >>>>=20 >>>=20 >>> from 'transpose-subr-1': >>>=20 >>> (if (> (cdr pos1) (car pos2)) (error "Don't have two things to >>> transpose")) >>>=20 >>> I added this hack into the function in the patch, but I think that >>> triggering an error is too much. >>>=20 >>> ;; Hack to trigger the error message in `transpose-subr-1' when we >>> ;; don't have siblings to swap. >>> (list (cons 0 1) (cons 0 1)))) >>>=20 >>> I guess I could just follow suit in my function and do like in the >>> following patch: >>>=20 >>> Theo >>=20 >>=20 >> Considering there is both a bug-report _and_ a discussion around this I >> guess the best idea is to add the patch to this bug report, and continue >> discussing this in the report rather than emacs-devel? >>=20 >> What do you think about this patch? >>=20 >> (copied from emacs-devel): >> It feels a little iffy how to handle the separate return values, but it >> works. I'd be super happy for some feedback on how to best solve that, >> though :) > > By separate return values, do you mean the function returns a cons of > cons? It seems fine to me. Though I think the docstring could be more > specific. Like saying return a cons (REGION . REGION), where REGION is > (BEG . END), where BEG and END... > I updated the patch adressing your comment. The current "protocol" used on the master branch implies that the regions are calculated by actually moving inside the buffer and running (point). Because we don't need that with the ast in hand I'm trying to support a new protocol, where we just supply the data we need. It's a little difficult to "surgically" modify the behavior, so I'm very open to suggestions, but I believe it should work properly as the patch stands. >>=20 >> Also, I made the treesit-transpose-sexps a little better imo, in that we >> only find named nodes to swap, but use every available node for the >> entry. We rarely, if ever want to swap the unnamed nodes. >>=20 >> Eli, does this require a NEWS entry or more documentation? > > IMHO a backend/helper function shouldn=E2=80=99t signal a user-error, it= =E2=80=99s > better to return nil and let the command to signal errors. Yeah, I agree. Adjusted the patch. Let me know what you think! Theo --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-Add-treesit-transpose-sexps-bug-60128.patch >From 74a7980758b929c991e317ea281b7cd4a097fdff Mon Sep 17 00:00:00 2001 From: Theodor Thornhill Date: Sun, 25 Dec 2022 20:11:59 +0100 Subject: [PATCH] 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. --- lisp/simple.el | 106 +++++++++++++++++++++++++++++------------------- lisp/treesit.el | 28 ++++++++++++- 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/lisp/simple.el b/lisp/simple.el index 4551b749d5..99dbfaea9f 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -8438,6 +8438,21 @@ transpose-words (interactive "*p") (transpose-subr 'forward-word arg)) +(defvar-local transpose-sexps-function nil + "If non-nil, `transpose-sexps' delegates to this function. + +This function takes one argument ARG, a number as provided +through running `transpose-sexps'. It supports the following two +return values: + +1. A cons (REGION . REGION), where REGION is (BEG . END) and BEG +and END are buffer positions. + +2. A cons (BEG . END), where BEG and END are buffer positions. + +If the second return value is chosen for this function, it is +expected to behave similarly to `forward-sexp' and friends.") + (defun transpose-sexps (arg &optional interactive) "Like \\[transpose-chars] (`transpose-chars'), but applies to sexps. Unlike `transpose-words', point must be between the two sexps and not @@ -8454,36 +8469,37 @@ transpose-sexps (transpose-sexps arg nil) (scan-error (user-error "Not between two complete sexps"))) (transpose-subr - (lambda (arg) - ;; Here we should try to simulate the behavior of - ;; (cons (progn (forward-sexp x) (point)) - ;; (progn (forward-sexp (- x)) (point))) - ;; Except that we don't want to rely on the second forward-sexp - ;; putting us back to where we want to be, since forward-sexp-function - ;; might do funny things like infix-precedence. - (if (if (> arg 0) - (looking-at "\\sw\\|\\s_") - (and (not (bobp)) - (save-excursion - (forward-char -1) - (looking-at "\\sw\\|\\s_")))) - ;; Jumping over a symbol. We might be inside it, mind you. - (progn (funcall (if (> arg 0) - 'skip-syntax-backward 'skip-syntax-forward) - "w_") - (cons (save-excursion (forward-sexp arg) (point)) (point))) - ;; Otherwise, we're between sexps. Take a step back before jumping - ;; to make sure we'll obey the same precedence no matter which - ;; direction we're going. - (funcall (if (> arg 0) 'skip-syntax-backward 'skip-syntax-forward) - " .") - (cons (save-excursion (forward-sexp arg) (point)) - (progn (while (or (forward-comment (if (> arg 0) 1 -1)) - (not (zerop (funcall (if (> arg 0) - 'skip-syntax-forward - 'skip-syntax-backward) - "."))))) - (point))))) + (if transpose-sexps-function transpose-sexps-function + (lambda (arg) + ;; Here we should try to simulate the behavior of + ;; (cons (progn (forward-sexp x) (point)) + ;; (progn (forward-sexp (- x)) (point))) + ;; Except that we don't want to rely on the second forward-sexp + ;; putting us back to where we want to be, since forward-sexp-function + ;; might do funny things like infix-precedence. + (if (if (> arg 0) + (looking-at "\\sw\\|\\s_") + (and (not (bobp)) + (save-excursion + (forward-char -1) + (looking-at "\\sw\\|\\s_")))) + ;; Jumping over a symbol. We might be inside it, mind you. + (progn (funcall (if (> arg 0) + #'skip-syntax-backward #'skip-syntax-forward) + "w_") + (cons (save-excursion (forward-sexp arg) (point)) (point))) + ;; Otherwise, we're between sexps. Take a step back before jumping + ;; to make sure we'll obey the same precedence no matter which + ;; direction we're going. + (funcall (if (> arg 0) #'skip-syntax-backward #'skip-syntax-forward) + " .") + (cons (save-excursion (forward-sexp arg) (point)) + (progn (while (or (forward-comment (if (> arg 0) 1 -1)) + (not (zerop (funcall (if (> arg 0) + #'skip-syntax-forward + #'skip-syntax-backward) + "."))))) + (point)))))) arg 'special))) (defun transpose-lines (arg) @@ -8509,19 +8525,23 @@ transpose-lines ;; FIXME document SPECIAL. (defun transpose-subr (mover arg &optional special) "Subroutine to do the work of transposing objects. -Works for lines, sentences, paragraphs, etc. MOVER is a function that -moves forward by units of the given object (e.g. `forward-sentence', -`forward-paragraph'). If ARG is zero, exchanges the current object -with the one containing mark. If ARG is an integer, moves the -current object past ARG following (if ARG is positive) or -preceding (if ARG is negative) objects, leaving point after the -current object." - (let ((aux (if special mover - (lambda (x) - (cons (progn (funcall mover x) (point)) - (progn (funcall mover (- x)) (point)))))) - pos1 pos2) +Works for lines, sentences, paragraphs, etc. MOVER is either a +function that moves forward by units of the given +object (e.g. `forward-sentence', `forward-paragraph'), or a +function that calculates a cons of two position-pairs. If ARG is +zero, exchanges the current object with the one containing mark. +If ARG is an integer, moves the current object past ARG +following (if ARG is positive) or preceding (if ARG is negative) +objects, leaving point after the current object." + (let* ((aux (if special mover + (lambda (x) + (cons (progn (funcall mover x) (point)) + (progn (funcall mover (- x)) (point)))))) + (pos1 (save-excursion (funcall aux arg))) + pos2) (cond + ((and (consp (car pos1)) (consp (cdr pos1))) + (transpose-subr-1 (car pos1) (cdr pos1))) ((= arg 0) (save-excursion (setq pos1 (funcall aux 1)) @@ -8542,6 +8562,8 @@ transpose-subr (goto-char (+ (car pos2) (- (cdr pos1) (car pos1)))))))) (defun transpose-subr-1 (pos1 pos2) + (unless (and pos1 pos2) + (error "Don't have two things to transpose")) (when (> (car pos1) (cdr pos1)) (setq pos1 (cons (cdr pos1) (car pos1)))) (when (> (car pos2) (cdr pos2)) (setq pos2 (cons (cdr pos2) (car pos2)))) (when (> (car pos1) (car pos2)) diff --git a/lisp/treesit.el b/lisp/treesit.el index cefbed1a16..2bd7f71f2f 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1582,6 +1582,31 @@ treesit-search-forward-goto (goto-char current-pos))) node)) +(defun treesit-transpose-sexps (&optional arg) + "Tree-sitter `transpose-sexps' function. +Arg is the same as in `transpose-sexps'. + +Locate the node closest to POINT, and transpose that node with +its sibling node ARG nodes away. + +Return a pair of positions as described by +`transpose-sexps-function' for use in `transpose-subr' and +friends." + (let* ((parent (treesit-node-parent (treesit-node-at (point)))) + (child (treesit-node-child parent 0 t))) + (named-let loop ((prev child) + (next (treesit-node-child + parent (+ arg (treesit-node-index child t)) + t))) + (when (and prev next) + (if (< (point) (treesit-node-end next)) + (cons (cons (treesit-node-start prev) + (treesit-node-end prev)) + (cons (treesit-node-start next) + (treesit-node-end next))) + (loop (treesit-node-next-sibling prev t) + (treesit-node-next-sibling next t))))))) + ;;; Navigation, defun, things ;; ;; Emacs lets you define "things" by a regexp that matches the type of @@ -2111,7 +2136,8 @@ treesit-major-mode-setup ;; Defun name. (when treesit-defun-name-function (setq-local add-log-current-defun-function - #'treesit-add-log-current-defun))) + #'treesit-add-log-current-defun)) + (setq-local transpose-sexps-function #'treesit-transpose-sexps)) ;;; Debugging -- 2.34.1 --=-=-=--