all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Theodor Thornhill <theo@thornhill.no>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: Yuan Fu <casouri@gmail.com>, emacs-devel@gnu.org, eliz@gnu.org
Subject: Re: Plug treesit.el into other emacs constructs
Date: Mon, 26 Dec 2022 20:11:45 +0100	[thread overview]
Message-ID: <87o7rq7zf2.fsf@thornhill.no> (raw)
In-Reply-To: <5DF07C4E-2CCD-4561-AFFB-D5D81D67BFE0@thornhill.no>

[-- Attachment #1: Type: text/plain, Size: 1718 bytes --]

uld we use a `transpose-sexp-function` variable, which `treesit` can
>>then set, so `simple.el` doesn't need to know about `treesit` at all?
>>
>
> Yes absolutely! I'll make that change. It makes sense, because we need not really rely on forward-foo anyways:)
>
>>>  (defun transpose-lines (arg)
>>> @@ -8521,6 +8524,9 @@ transpose-subr
>>>  		       (progn (funcall mover (- x)) (point))))))
>>>  	pos1 pos2)
>>>      (cond
>>> +     ((treesit-parser-list)
>>> +      (cl-multiple-value-bind (p1 p2) (funcall aux arg)
>>> +        (transpose-subr-1 p1 p2)))
>>>       ((= arg 0)
>>>        (save-excursion
>>>  	(setq pos1 (funcall aux 1))
>>
>>Please use `pcase-let` instead of `cl-multiple-value-bind` (especially
>>since you use it to decompose something built with `list` rather than
>>with `cl-values`).
>>
>>Also to avid re-testing `treesit-parser-list`, I'd recommend you extend
>>the semantics of `mover` so it can either return a position (the old
>>protocol) or directly return a pair of positions.  You could even add
>>a 3rd kind of return value to explicitly trigger the error message
>>instead of relying on the (cons 0 1) hack.
>>
>>
>>        Stefan
>>
>
> Yeah! I believe this either wasn't the latest patch, or i forgot to
> send it. I'll see what lies around my system and wrap things up.


What do you think about something like this?

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 :)

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.

Theo


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-treesit-transpose-sexps-bug-60128.patch --]
[-- Type: text/x-diff, Size: 8114 bytes --]

From 0dc412eaf16123dcb65381970fb82c0741809753 Mon Sep 17 00:00:00 2001
From: Theodor Thornhill <theo@thornhill.no>
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  | 97 ++++++++++++++++++++++++++++---------------------
 lisp/treesit.el | 24 +++++++++++-
 2 files changed, 78 insertions(+), 43 deletions(-)

diff --git a/lisp/simple.el b/lisp/simple.el
index 4551b749d5..591b659c62 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8438,6 +8438,14 @@ transpose-words
   (interactive "*p")
   (transpose-subr 'forward-word arg))
 
+(defvar-local transpose-sexps-function nil
+  "If non-nil, `transpose-sexps' delegates to this function.
+
+The return value of this function is expected to be a cons of two
+conses, denoting the positions in the current buffer to be
+transposed.  If no such pair of positions is available, signal
+USER-ERROR.")
+
 (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 +8462,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 +8518,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))
diff --git a/lisp/treesit.el b/lisp/treesit.el
index cefbed1a16..9f0965ac68 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1582,6 +1582,27 @@ 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'.
+
+Return a pair of positions describing the regions to transpose
+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)))
+      (if (< (point) (or (treesit-node-end next)
+                         (user-error "Don't have two things to transpose")))
+          (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 +2132,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


  reply	other threads:[~2022-12-26 19:11 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-12 14:33 Plug treesit.el into other emacs constructs Theodor Thornhill
2022-12-12 14:45 ` Eli Zaretskii
2022-12-13 18:17   ` Theodor Thornhill
2022-12-12 15:46 ` Stefan Monnier
2022-12-13 18:27   ` Theodor Thornhill
2022-12-13 19:37     ` Stefan Monnier
2022-12-13 19:53       ` Yuan Fu
2022-12-13 20:06         ` Perry Smith
2022-12-13 23:19         ` Stefan Monnier
2022-12-14  8:14           ` Yuan Fu
2022-12-14  8:42             ` Theodor Thornhill
2022-12-14 14:01             ` Stefan Monnier
2022-12-14 16:24               ` Theodor Thornhill
2022-12-14 17:46                 ` Stefan Monnier
2022-12-14 18:07                   ` Theodor Thornhill
2022-12-14 19:25                     ` Stefan Monnier
2022-12-14 19:35                       ` Stefan Monnier
2022-12-14 20:04                       ` Theodor Thornhill
2022-12-14 20:50                         ` Stefan Monnier
2022-12-14 21:15                           ` Theodor Thornhill
2022-12-14 21:34                             ` Stefan Monnier
2022-12-15 19:37                               ` Theodor Thornhill
2022-12-15 19:56                                 ` Stefan Monnier
2022-12-15 20:03                                   ` Theodor Thornhill
2022-12-15 20:33                                     ` Theodor Thornhill
2022-12-15 20:57                                       ` Theodor Thornhill
2022-12-24  7:00                                         ` Eli Zaretskii
2022-12-24  8:44                                           ` Yuan Fu
2022-12-24 14:01                                         ` Stefan Monnier
2022-12-24 14:15                                           ` Theodor Thornhill
2022-12-26 19:11                                             ` Theodor Thornhill [this message]
2022-12-26 22:46                                               ` Stefan Monnier
2022-12-26 22:51                                                 ` Stefan Monnier
2022-12-27 22:15                                                   ` Theodor Thornhill via Emacs development discussions.
2022-12-28  0:12                                                     ` Stefan Monnier
2022-12-28  9:26                                                       ` Theodor Thornhill via Emacs development discussions.
2022-12-28 18:01                                                         ` Stefan Monnier
2022-12-28 18:27                                                           ` Theodor Thornhill
2022-12-26 22:56                                                 ` Theodor Thornhill
2022-12-27 15:46                                   ` Lynn Winebarger
2022-12-14 23:31               ` Yuan Fu
2022-12-15  0:05                 ` Yuan Fu
2022-12-15  7:09                   ` Eli Zaretskii
2022-12-15  7:14                     ` Theodor Thornhill
2022-12-15  4:37                 ` Stefan Monnier
2022-12-15  5:59                 ` Theodor Thornhill
2022-12-15 21:23                   ` Yuan Fu
2022-12-15 21:28                     ` Theodor Thornhill
2022-12-13 20:02       ` Theodor Thornhill
2022-12-13 23:10         ` Stefan Monnier
2022-12-14 23:32   ` Stephen Leake
2022-12-16 10:02     ` Kévin Le Gouguec
2022-12-16 11:54       ` [SPAM UNSURE] " Stephen Leake
2022-12-17 15:30         ` Kévin Le Gouguec

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87o7rq7zf2.fsf@thornhill.no \
    --to=theo@thornhill.no \
    --cc=casouri@gmail.com \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.