Subject: bug#62892: proposal to extend mark-sexp to go forward and backward on command

From: Zachary Kanfer <[email-masked]>
Date: Sun, 16 Apr 2023 22:25:28 -0400

C-M-@ is bound to `#'mark-sexp`. This command works incrementally -- that
is, calling this once marks the sexp after point; calling it again marks
one more sexp (in total, the two sexps after point).

It would be convenient to easily be able to mark sexps backwards as well.

If you've already marked sexps backwards, mark-sexp extends the region
backwards. But if you haven't, the only way to mark backwards is to pass a
negative prefix argument.

It often is a better experience to iteratively select the sexps you want.
Finally, there's no way to mark both directions, e.g., one sexp forward and
two backwards.

Attached is a patch to add #'mark-sexp-forwards and #'mark-sexp-backwards.
These functions extend the region by sexps forward and backward. They are
mutually incremental: you can call mark-sexp-fowards, then
mark-sexp-backwards twice, then mark-sexp-forwards. After this, the region
will consist of the two sexps before point and the two sexps after point.

These functions are very similar to #'mark-sexp:
- They will extend the region if it already exists.
- They can be called from lisp code with a numeric argument.
- They can take a prefix argument if called interactively.

Thanks,
Zachary Kanfer
From 3a35790a79eff3d597e13a17d740357bc556d81e Mon Sep 17 00:00:00 2001
From: Zachary Kanfer <[email-masked]>
Date: Sun, 16 Apr 2023 22:16:39 -0400
Subject: [PATCH] Add mark-sexp-forward, mark-sexp-backward

---
 etc/NEWS                |  4 ++++
 lisp/emacs-lisp/lisp.el | 41 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/etc/NEWS b/etc/NEWS
index c61a9ec3c5f..9f2b7d21f9b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -81,6 +81,10 @@ mistaken compositions, this will now work as well.
 This works like 'kill-matching-buffers', but without asking for
 confirmation.
 
+---
+** New commands 'mark-sexp-forward' and 'mark-sexp-backward'.
+These work like mark-sexp, but explicitly allow sexps to be marked forward and backward.
+
 
 * Changes in Specialized Modes and Packages in Emacs 30.1
 
diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el
index 417c218c6d7..d6649b2497d 100644
--- a/lisp/emacs-lisp/lisp.el
+++ b/lisp/emacs-lisp/lisp.el
@@ -129,6 +129,47 @@ mark-sexp
 	    (point))
 	  nil t))))
 
+(defun mark-sexp-helper (number-of-expressions)
+  "A helper function for 'mark-sexp-[forward, backward]'.
+
+If NUMBER-OF-EXPRESSIONS is positive, mark that many sexps forward;
+otherwise, amrk backward."
+  (if (use-region-p)
+      (let* ((forward (>= number-of-expressions 0))
+             (beginning-of-region (region-beginning))
+             (end-of-region (region-end))
+             (at-end-of-region (= end-of-region (point)))
+             (new-border-point
+              (save-excursion
+                (goto-char (if forward (region-end) (region-beginning)))
+                (condition-case nil
+                    (forward-sexp number-of-expressions)
+                  (scan-error (user-error "No more s-expressions there!")))
+                (point)))
+             (new-beginning-of-region (min beginning-of-region new-border-point))
+             (new-end-of-region (max end-of-region new-border-point)))
+        (goto-char (if at-end-of-region
+                       new-end-of-region
+                     new-beginning-of-region))
+        (set-mark (if at-end-of-region
+                      new-beginning-of-region
+                    new-end-of-region)))
+    (mark-sexp number-of-expressions)))
+
+(defun mark-sexp-forward (&optional number-of-expressions)
+  "Mark NUMBER-OF-EXPRESSIONS s-expressions forward.
+
+ Repeated calls to this mark more s-expressions."
+  (interactive "p")
+  (mark-sexp-helper (or number-of-expressions 1)))
+
+(defun mark-sexp-backward (&optional number-of-expressions)
+  "Mark NUMBER-OF-EXPRESSIONS s-expressions backward.
+
+ Repeated calls to this mark more s-expressions."
+  (interactive "p")
+  (mark-sexp-helper (- (or number-of-expressions 1))))
+
 (defun forward-list (&optional arg interactive)
   "Move forward across one balanced group of parentheses.
 This command will also work on other parentheses-like expressions
-- 
2.38.4