Subject: bug#66780: closed (Re: bug#66780: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 12 Apr 2025 17:30:03 +0000

Your bug report

#66780: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
66780: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=66780
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#66780: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off
From: Jens Schmidt <[email-masked]>
To: [email-masked]
Date: Sat, 12 Apr 2025 19:29:08 +0200
List-Id: <debbugs-submit.debbugs.gnu.org>

I haven't been using that `rectangle-mark-mode' myself very
frequently/at all, so let's just close this RFE ...

Subject: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off
From: Jens Schmidt <[email-masked]>
To: [email-masked]
Date: Fri, 27 Oct 2023 22:31:24 +0200
List-Id: <debbugs-submit.debbugs.gnu.org>

Severity: minor

All issues reported here are about using rectangle-mark-mode (RMM) when
Transient Mark mode (TMM) is off.

Bug#42663 already has mentioned a few issues of this combination, but
IMO did not completely describe or solve them.

So suppose you start in the Emacs source directory tree

  ./src/emacs -Q --eval='(transient-mark-mode -1)' README

When you then press `C-x <SPC>' to enable RMM, the following happens,
which is expected:

1. The mark is NOT activated and, hence, the region-rectangle is NOT
   shown when you move point.  Also, point movement stays regular, in
   that you cannot advance point to after EOL, for example.

Again, this restriction is expected and is described already in commit
06dcd2be5d42 provided by Sean to solve bug#42663.

However, what has not been described yet is that some parts of RMM
actually _are_ active when one enables it with TMM off:

2. The key bindings in `rectangle-mark-mode-map', so that, for example,
   `C-x C-x' does not exchange point and mark, but instead cycles
   through the corners of the rectangle spanned by mark and point (using
   closest accessible buffer positions as an approximation for those
   positions where point regularly cannot be moved to).

   (Short reproducer when starting off the "emacs -Q ..." above:
   M-: (rectangle-mark-mode -1) RET
   M-<   C-x <SPC>   C-4 C-n   C-e   C-x C-x   C-x C-x   C-x C-x)

3. Advices on `region-extract-function' and `region-insert-function', so
   that C-w and C-y still operate on the current rectangle, and not the
   regular region.

   (Short reproducer when starting off the "emacs -Q ..." above:
   M-: (rectangle-mark-mode -1) RET
   M-<   C-x <SPC>   C-4 C-n   C-4 C-f   C-w)

I found 2. and 3. surprising because I would have expected that RMM is
completely off.  Moreover, RMM does not provide a minor mode lighter so
you cannot easily tell whether it is on or off.

I have attempted to improve the situation by providing documentation
(fix A) in a patch on emacs-29, and some minor code fixes (B and C) in a
patch on emacs-master.  That latter patch still has a placeholder
"bug#XXXXX" which needs to be updated once I have a bug number.

* Fix A: Improve Documentation

Here is the text that Sean has already added to emacs/killing.tex in
commit 06dcd2be5d42:

+The region-rectangle works only when the mark is active.  In
+particular, when Transient Mark mode is off (@pxref{Disabled Transient
+Mark}), in addition to typing @kbd{C-x @key{SPC}} you will need to
+activate the mark.

I first tried to extend on that, but then decided to completely redo the
material, like this:

+  rectangle-mark-mode behaves in a slightly different way when
+Transient Mark mode is off (@pxref{Disabled Transient Mark}).  In this
+case, when you enable rectangle-mark-mode, the region-rectangle is not
+automatically enabled.  Accordingly, cursor movement with @kbd{C-f},
+@kbd{C-n} etc.@: is confined to the regularly accessible buffer
+positions.  However, killing and yanking still operate on the
+rectangle spanned by point and mark.  Also @kbd{C-x C-x} still cycles
+through the corners of that rectangle, but only as far as these are at
+buffer positions that are regularly accessible.
+
+  As mentioned above, rectangle-mark-mode persists as long the region
+is active: If the region gets deactivated, rectangle-mark-mode gets
+deactivated as well.  But with disabled Transient mark mode there is
+usually no active region that would get deactivated, and so you have
+to explicitly switch off rectangle-mark-mode when you no longer want
+to use it.
+
+  To experience all benefits of rectangle-mark-mode and the
+region-rectangle when Transient Mark mode is off, you can temporarily
+activate Transient Mark mode after enabling rectangle-mark-mode, for
+example, with @kbd{C-@key{SPC} C-@key{SPC}}.  @xref{Disabled Transient
+Mark}.

I also replaced the somewhat unhelpful reference to parent node
"@xref{Killing}" further up by references to sibling nodes:

 so in a rectangular fashion, and killing and yanking operate on the
-rectangle.  @xref{Killing}.  The mode persists only as long as the
-region is active.
+rectangle.  @xref{Deletion and Killing}, @ref{Yanking}.  The mode
+persists only as long as the region is active.

* Fix B: Use a Minor Mode Lighter

With TMM off and RMM on, the most important indicator of RMM (the
region-rectangle) is not visible.  And since RMM's minor mode lighter is
nil, one has no visible feedback of it still being active.  That can be
surprising since some of its features are still active as described
above.

I tried to improve that by using a minor mode lighter that goes on only
if TMM is off, like this:

+(defvar rectangle-mark-mode-lighter nil
+  "Lighter displayed for `rectangle-mark-mode'.")
+
 ;;;###autoload
 (define-minor-mode rectangle-mark-mode
   "Toggle the region as rectangular.

 Activates the region if it's inactive and Transient Mark mode is
 on.  Only lasts until the region is next deactivated."
-  :lighter nil
+  :lighter rectangle-mark-mode-lighter
   (rectangle--reset-crutches)
   (when rectangle-mark-mode
+    ;; Make us more visible when Transient Mark mode is off and there
+    ;; is no rectangle (bug#XXXXX).
+    (setq rectangle-mark-mode-lighter
+          (and (not transient-mark-mode) " Rect"))

* Fix C: Mark RMM Movement Commands as Shift-Selectable

Finally, to make RMM better usable with shift-select-mode, I added the
necessary interactive specifiers to the RMM-specific movement commands

  rectangle-right-char
  rectangle-left-char
  rectangle-forward-char
  rectangle-backward-char
  rectangle-next-line
  rectangle-previous-line

Please review.

Thanks.

From 6d7d83f158c3371e59877e02b88c38c664cb0ff9 Mon Sep 17 00:00:00 2001
From: Jens Schmidt <[email-masked]>
Date: Wed, 25 Oct 2023 21:32:17 +0200
Subject: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off

* doc/emacs/killing.texi (Rectangles): Add documentation on specifics
of rectangle-mark-mode when transient-mark-mode is off.
---
 doc/emacs/killing.texi | 32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)

diff --git a/doc/emacs/killing.texi b/doc/emacs/killing.texi
index 47e0b5e37ae..e6a8fe2848e 100644
--- a/doc/emacs/killing.texi
+++ b/doc/emacs/killing.texi
@@ -939,13 +939,8 @@ Rectangles
 (first activating the region if necessary).  When this mode is enabled,
 commands that resize the region (@kbd{C-f}, @kbd{C-n} etc.)@: do
 so in a rectangular fashion, and killing and yanking operate on the
-rectangle.  @xref{Killing}.  The mode persists only as long as the
-region is active.
-
-The region-rectangle works only when the mark is active.  In
-particular, when Transient Mark mode is off (@pxref{Disabled Transient
-Mark}), in addition to typing @kbd{C-x @key{SPC}} you will need to
-activate the mark.
+rectangle.  @xref{Deletion and Killing}, @ref{Yanking}.  The mode
+persists only as long as the region is active.
 
 Unlike the standard region, the region-rectangle can have its corners
 extended past the end of buffer, or inside stretches of white space
@@ -962,6 +957,29 @@ Rectangles
 to modify the dimensions of the region-rectangle before invoking an
 operation on the marked text.
 
+  rectangle-mark-mode behaves in a slightly different way when
+Transient Mark mode is off (@pxref{Disabled Transient Mark}).  In this
+case, when you enable rectangle-mark-mode, the region-rectangle is not
+automatically enabled.  Accordingly, cursor movement with @kbd{C-f},
+@kbd{C-n} etc.@: is confined to the regularly accessible buffer
+positions.  However, killing and yanking still operate on the
+rectangle spanned by point and mark.  Also @kbd{C-x C-x} still cycles
+through the corners of that rectangle, but only as far as these are at
+buffer positions that are regularly accessible.
+
+  As mentioned above, rectangle-mark-mode persists as long the region
+is active: If the region gets deactivated, rectangle-mark-mode gets
+deactivated as well.  But with disabled Transient mark mode there is
+usually no active region that would get deactivated, and so you have
+to explicitly switch off rectangle-mark-mode when you no longer want
+to use it.
+
+  To experience all benefits of rectangle-mark-mode and the
+region-rectangle when Transient Mark mode is off, you can temporarily
+activate Transient Mark mode after enabling rectangle-mark-mode, for
+example, with @kbd{C-@key{SPC} C-@key{SPC}}.  @xref{Disabled Transient
+Mark}.
+
 @node CUA Bindings
 @section CUA Bindings
 @findex cua-mode
-- 
2.30.2

From 2edc489f4f82a72b91624a779f06b488e586507f Mon Sep 17 00:00:00 2001
From: Jens Schmidt <[email-masked]>
Date: Wed, 25 Oct 2023 22:36:15 +0200
Subject: [PATCH] Improve rectangle-mark-mode when transient-mark-mode is off

* lisp/rect.el (rectangle-mark-mode-lighter): Add new variable ...
(rectangle-mark-mode): ... and use it here to make rectangle-mark-mode
more visible.
(rectangle-right-char, rectangle-left-char, rectangle-forward-char)
(rectangle-backward-char, rectangle-next-line)
(rectangle-previous-line): Declare as shift-translatable motion
commands.
---
 lisp/rect.el | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/lisp/rect.el b/lisp/rect.el
index 8dc188b1de0..f0b5a9b806a 100644
--- a/lisp/rect.el
+++ b/lisp/rect.el
@@ -646,15 +646,22 @@ rectangle-mark-mode-map
   "<remap> <next-line>"               #'rectangle-next-line
   "<remap> <previous-line>"           #'rectangle-previous-line)

+(defvar rectangle-mark-mode-lighter nil
+  "Lighter displayed for `rectangle-mark-mode'.")
+
 ;;;###autoload
 (define-minor-mode rectangle-mark-mode
   "Toggle the region as rectangular.

 Activates the region if it's inactive and Transient Mark mode is
 on.  Only lasts until the region is next deactivated."
-  :lighter nil
+  :lighter rectangle-mark-mode-lighter
   (rectangle--reset-crutches)
   (when rectangle-mark-mode
+    ;; Make us more visible when Transient Mark mode is off and there
+    ;; is no rectangle (bug#XXXXX).
+    (setq rectangle-mark-mode-lighter
+          (and (not transient-mark-mode) " Rect"))
     (advice-add 'region-beginning :around #'rectangle--region-beginning)
     (advice-add 'region-end :around #'rectangle--region-end)
     (add-hook 'deactivate-mark-hook
@@ -727,29 +734,29 @@ rectangle--*-char

 (defun rectangle-right-char (&optional n)
   "Like `right-char' but steps into wide chars and moves past EOL."
-  (interactive "p") (rectangle--*-char #'right-char n #'left-char))
+  (interactive "^p") (rectangle--*-char #'right-char n #'left-char))
 (defun rectangle-left-char (&optional n)
   "Like `left-char' but steps into wide chars and moves past EOL."
-  (interactive "p") (rectangle--*-char #'left-char n #'right-char))
+  (interactive "^p") (rectangle--*-char #'left-char n #'right-char))

 (defun rectangle-forward-char (&optional n)
   "Like `forward-char' but steps into wide chars and moves past EOL."
-  (interactive "p") (rectangle--*-char #'forward-char n #'backward-char))
+  (interactive "^p") (rectangle--*-char #'forward-char n #'backward-char))
 (defun rectangle-backward-char (&optional n)
   "Like `backward-char' but steps into wide chars and moves past EOL."
-  (interactive "p") (rectangle--*-char #'backward-char n #'forward-char))
+  (interactive "^p") (rectangle--*-char #'backward-char n #'forward-char))

 (defun rectangle-next-line (&optional n)
   "Like `next-line' but steps into wide chars and moves past EOL.
 Ignores `line-move-visual'."
-  (interactive "p")
+  (interactive "^p")
   (let ((col (rectangle--point-col (point))))
     (forward-line n)
     (rectangle--col-pos col 'point)))
 (defun rectangle-previous-line (&optional n)
   "Like `previous-line' but steps into wide chars and moves past EOL.
 Ignores `line-move-visual'."
-  (interactive "p")
+  (interactive "^p")
   (let ((col (rectangle--point-col (point))))
     (forward-line (- n))
     (rectangle--col-pos col 'point)))
--
2.30.2