GNU bug report logs - #46515
Repeat mode

Previous Next

Package: emacs;

Reported by: Juri Linkov <juri <at> linkov.net>

Date: Sun, 14 Feb 2021 18:54:02 UTC

Severity: normal

Tags: fixed, patch

Fixed in version 28.0.50

Done: Juri Linkov <juri <at> linkov.net>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 46515 in the body.
You can then email your comments to 46515 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Sun, 14 Feb 2021 18:54:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Juri Linkov <juri <at> linkov.net>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Sun, 14 Feb 2021 18:54:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: bug-gnu-emacs <at> gnu.org
Subject: Repeat mode
Date: Sun, 14 Feb 2021 20:27:08 +0200
[Message part 1 (text/plain, inline)]
Tags: patch

Like discussed in bug#12572, bug#15234 and recently in
https://lists.gnu.org/archive/html/emacs-devel/2021-02/msg00139.html
here is a patch that provides an opt-in feature for easy-to-repeat
key sequences:

C-x u u u - undo sequences
C-x o o o - switch windows
C-x right left right left - next/previous buffer switching
M-g n n n p p p - next-error navigation
C-x { { { } } } - window resizing
...

[repeat-mode.patch (text/x-diff, inline)]
diff --git a/lisp/repeat.el b/lisp/repeat.el
index 795577c93f..1b31963d22 100644
--- a/lisp/repeat.el
+++ b/lisp/repeat.el
@@ -329,6 +329,39 @@ repeat-message
 
 ;;;;; ************************* EMACS CONTROL ************************* ;;;;;
 
+
+;;; repeat-mode
+
+(defcustom repeat-exit-key [return] ; like `isearch-exit'
+  "Key that stops the modal repeating of keys in sequence."
+  :type '(choice (const :tag "No special key to exit repeat sequence" nil)
+		 (key-sequence :tag "Key that exits repeat sequence"))
+  :group 'convenience
+  :version "28.1")
+
+;;;###autoload
+(define-minor-mode repeat-mode
+  "Toggle Repeat mode.
+When Repeat mode is enabled, and the command symbol has the property named
+`repeat-map', this map is activated temporarily for the next command."
+  :global t :group 'convenience
+  (if (not repeat-mode)
+      (remove-hook 'post-command-hook 'repeat-post-hook)
+    (add-hook 'post-command-hook 'repeat-post-hook)))
+
+(defun repeat-post-hook ()
+  "Function run after commands to set transient keymap."
+  (when repeat-mode
+    (let ((repeat-map (and (symbolp this-command)
+                           (get this-command 'repeat-map))))
+      (when repeat-map
+        (when (boundp repeat-map)
+          (setq repeat-map (symbol-value repeat-map)))
+        (let ((map (copy-keymap repeat-map)))
+          (when repeat-exit-key
+            (define-key map repeat-exit-key 'ignore))
+          (set-transient-map map))))))
+
 (provide 'repeat)
 
 ;;; repeat.el ends here
diff --git a/lisp/bindings.el b/lisp/bindings.el
index 2f4bab11cf..70ddd9d9ba 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -950,6 +950,12 @@ global-map
 ;; Richard said that we should not use C-x <uppercase letter> and I have
 ;; no idea whereas to bind it.  Any suggestion welcome.  -stef
 ;; (define-key ctl-x-map "U" 'undo-only)
+(defvar undo-repeat-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "u" 'undo)
+    map)
+  "Keymap to repeat undo `C-x u u' sequences.  Used in `repeat-mode'.")
+(put 'undo 'repeat-map 'undo-repeat-map)
 
 (define-key esc-map "!" 'shell-command)
 (define-key esc-map "|" 'shell-command-on-region)
@@ -964,6 +970,17 @@ ctl-x-map
 (define-key global-map [XF86Back] 'previous-buffer)
 (put 'previous-buffer :advertised-binding [?\C-x left])
 
+(defvar next-buffer-repeat-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [right] 'next-buffer)
+    (define-key map [C-right] 'next-buffer)
+    (define-key map [left] 'previous-buffer)
+    (define-key map [C-left] 'previous-buffer)
+    map)
+  "Keymap to repeat next-buffer key sequences.  Used in `repeat-mode'.")
+(put 'next-buffer 'repeat-map 'next-buffer-repeat-map)
+(put 'previous-buffer 'repeat-map 'next-buffer-repeat-map)
+
 (let ((map minibuffer-local-map))
   (define-key map "\en"   'next-history-element)
   (define-key map [next]  'next-history-element)
@@ -1036,6 +1053,17 @@ global-map
 
 (define-key ctl-x-map "`" 'next-error)
 
+(defvar next-error-repeat-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map    "n" 'next-error)
+    (define-key map "\M-n" 'next-error)
+    (define-key map    "p" 'previous-error)
+    (define-key map "\M-p" 'previous-error)
+    map)
+  "Keymap to repeat next-error key sequences.  Used in `repeat-mode'.")
+(put 'next-error 'repeat-map 'next-error-repeat-map)
+(put 'previous-error 'repeat-map 'next-error-repeat-map)
+
 (defvar goto-map (make-sparse-keymap)
   "Keymap for navigation commands.")
 (define-key esc-map "g" goto-map)
diff --git a/lisp/window.el b/lisp/window.el
index 2d0a73b426..d1a0f80da9 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -10252,6 +10252,30 @@ ctl-x-4-map
 (define-key ctl-x-4-map "1" 'same-window-prefix)
 (define-key ctl-x-4-map "4" 'other-window-prefix)
 
+(defvar other-window-repeat-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "o" 'other-window)
+    map)
+  "Keymap to repeat other-window key sequences.  Used in `repeat-mode'.")
+
+(put 'other-window 'repeat-map 'other-window-repeat-map)
+
+(defvar resize-window-repeat-map
+  (let ((map (make-sparse-keymap)))
+    ;; Standard keys:
+    (define-key map "^" 'enlarge-window)
+    (define-key map "}" 'enlarge-window-horizontally)
+    (define-key map "{" 'shrink-window-horizontally)
+    ;; Additional keys:
+    (define-key map "v" 'shrink-window)
+    map)
+  "Keymap to repeat window resizing commands.  Used in `repeat-mode'.")
+
+(put 'enlarge-window 'repeat-map 'resize-window-repeat-map)
+(put 'enlarge-window-horizontally 'repeat-map 'resize-window-repeat-map)
+(put 'shrink-window-horizontally 'repeat-map 'resize-window-repeat-map)
+(put 'shrink-window 'repeat-map 'resize-window-repeat-map)
+
 (provide 'window)
 
 ;;; window.el ends here

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Mon, 15 Feb 2021 00:00:02 GMT) Full text and rfc822 format available.

Message #8 received at 46515 <at> debbugs.gnu.org (full text, mbox):

From: Matt Armstrong <matt <at> rfc20.org>
To: Juri Linkov <juri <at> linkov.net>, 46515 <at> debbugs.gnu.org
Subject: Re: bug#46515: Repeat mode
Date: Sun, 14 Feb 2021 15:59:32 -0800
First thing: neat idea, and don't listen to me.  :-)

Have you thought about making it more clear to the user that their keys
are now doing different things?  Most successful "modal" interfaces I
have seen have clear indicators.

One idea is to look at the other places in Emacs that already use
`set-transient-map' in this way and try to be "at least as good" as
those. `kmacro' and `indent-rigidly' are two reasonable examples. They
print messages when active that describe the newly active key bindings.
repeat.el doesn't describe the key binding, but it does say a repeat
mode is active. Kmacro is so smart that it aranges for the repeat key to
be based on whatever key the command was invoked with.

As far as this general approach for creating small transient modes, I
can't help but think it is too low level. An approach that had a bit
more scafolding to it would let Emacs' help system describe it, and it
might allow for a consistent way for Emacs to indicade they are active
--- similar to how the conventions under major and minor work for
"heavier" modes.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Mon, 15 Feb 2021 09:51:02 GMT) Full text and rfc822 format available.

Message #11 received at 46515 <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: Matt Armstrong <matt <at> rfc20.org>
Cc: 46515 <at> debbugs.gnu.org
Subject: Re: bug#46515: Repeat mode
Date: Mon, 15 Feb 2021 11:17:31 +0200
[Message part 1 (text/plain, inline)]
> First thing: neat idea, and don't listen to me.  :-)

Thanks, I really appreciate your help.

> Have you thought about making it more clear to the user that their keys
> are now doing different things?  Most successful "modal" interfaces I
> have seen have clear indicators.

I have already thought about using prefix-command-echo-keystrokes-functions,
but failed to do this, so abandoned this attempt.  I did not realize
it's possible to do this simply with messages like you pointed out here :-)

> One idea is to look at the other places in Emacs that already use
> `set-transient-map' in this way and try to be "at least as good" as
> those. `kmacro' and `indent-rigidly' are two reasonable examples. They
> print messages when active that describe the newly active key bindings.

I use `indent-rigidly' many times every day, but never noticed that
it prints the message

  Indent region with <left>, <right>, S-<left>, or S-<right>.

It goes unnoticed maybe because it's displayed only once at its activation.

> repeat.el doesn't describe the key binding, but it does say a repeat
> mode is active. Kmacro is so smart that it aranges for the repeat key to
> be based on whatever key the command was invoked with.

Unlike `indent-rigidly', `kmacro' message

  (Type e to repeat macro)

is displayed on every keypress, so it's a good example.
Now added in the following patch applied over the previous patch.

> As far as this general approach for creating small transient modes, I
> can't help but think it is too low level. An approach that had a bit
> more scafolding to it would let Emacs' help system describe it, and it
> might allow for a consistent way for Emacs to indicade they are active
> --- similar to how the conventions under major and minor work for
> "heavier" modes.

Currently I have no idea how this could be generalized.  But simply
describing it in the help system should be quite easy to do,
so e.g. 'C-h k C-x o' could check for the command's repeat keymap
and add a help string about its repeatability.

[repeat-message.patch (text/x-diff, inline)]
diff --git a/lisp/repeat.el b/lisp/repeat.el
index 896a95197a..3c8be63c84 100644
--- a/lisp/repeat.el
+++ b/lisp/repeat.el
@@ -360,6 +360,9 @@ repeat-post-hook
         (when (boundp repeat-map)
           (setq repeat-map (symbol-value repeat-map)))
         (let ((map (copy-keymap repeat-map)))
+          (let (keys)
+            (map-keymap (lambda (key _) (push (key-description (vector key)) keys)) map)
+            (message "To repeat type %s" (mapconcat #'identity keys ", ")))
           (when repeat-exit-key
             (define-key map repeat-exit-key 'ignore))
           (set-transient-map map))))))

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Mon, 15 Feb 2021 17:08:01 GMT) Full text and rfc822 format available.

Message #14 received at 46515 <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: Matt Armstrong <matt <at> rfc20.org>
Cc: 46515 <at> debbugs.gnu.org
Subject: Re: bug#46515: Repeat mode
Date: Mon, 15 Feb 2021 19:04:12 +0200
[Message part 1 (text/plain, inline)]
>> repeat.el doesn't describe the key binding, but it does say a repeat
>> mode is active. Kmacro is so smart that it aranges for the repeat key to
>> be based on whatever key the command was invoked with.
>
> Unlike `indent-rigidly', `kmacro' message
>
>   (Type e to repeat macro)
>
> is displayed on every keypress, so it's a good example.
> Now added in the following patch applied over the previous patch.

Here is another incremental patch that adds more messaging:

[repeat-message-2.patch (text/x-diff, inline)]
diff --git a/lisp/repeat.el b/lisp/repeat.el
index 3c8be63c84..a322afed1d 100644
--- a/lisp/repeat.el
+++ b/lisp/repeat.el
@@ -349,7 +349,16 @@ repeat-mode
   :global t :group 'convenience
   (if (not repeat-mode)
       (remove-hook 'post-command-hook 'repeat-post-hook)
-    (add-hook 'post-command-hook 'repeat-post-hook)))
+    (add-hook 'post-command-hook 'repeat-post-hook)
+    (let* ((keymaps nil)
+           (commands (all-completions
+                      "" obarray (lambda (s)
+                                   (and (commandp s)
+                                        (get s 'repeat-map)
+                                        (push (get s 'repeat-map) keymaps))))))
+      (message "Repeat mode is enabled for %d commands and %d keymaps"
+               (length commands)
+               (length (delete-dups keymaps))))))
 
 (defun repeat-post-hook ()
   "Function run after commands to set transient keymap."
@@ -362,7 +371,10 @@ repeat-post-hook
         (let ((map (copy-keymap repeat-map)))
           (let (keys)
             (map-keymap (lambda (key _) (push (key-description (vector key)) keys)) map)
-            (message "To repeat type %s" (mapconcat #'identity keys ", ")))
+            (message "To repeat type %s%s"
+                     (mapconcat #'identity keys ", ")
+                     (when repeat-exit-key
+                       (format ", or %s to exit" (key-description repeat-exit-key)))))
           (when repeat-exit-key
             (define-key map repeat-exit-key 'ignore))
           (set-transient-map map))))))

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Tue, 16 Feb 2021 16:50:01 GMT) Full text and rfc822 format available.

Message #17 received at 46515 <at> debbugs.gnu.org (full text, mbox):

From: Matt Armstrong <matt <at> rfc20.org>
To: Juri Linkov <juri <at> linkov.net>
Cc: 46515 <at> debbugs.gnu.org
Subject: Re: bug#46515: Repeat mode
Date: Tue, 16 Feb 2021 08:49:29 -0800
Juri Linkov <juri <at> linkov.net> writes:

> Here is another incremental patch that adds more messaging...

Hey Juri, I like these ideas but don't feel qualified to review them
beyond what I've already said.  I have about three lines of edits in
Emacs code to my credit.  ;-) Perhaps discuss this idea on emacs-devel?

Thinking long term, I think it would be interesting to consider a future
where all of the various third party "modal" packages (evil, hydra,
etc.) could use higher level facilities provided by the Emacs core.
This patch is a step in that direction.  I'm interested to see how it
progresses.

Another interesting question: how do we surface how to use these
transient modes in Emacs help, if at all?




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#46515; Package emacs. (Wed, 17 Feb 2021 18:07:02 GMT) Full text and rfc822 format available.

Message #20 received at 46515 <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: Matt Armstrong <matt <at> rfc20.org>
Cc: 46515 <at> debbugs.gnu.org
Subject: Re: bug#46515: Repeat mode
Date: Wed, 17 Feb 2021 20:05:30 +0200
tags 46515 fixed
close 46515 28.0.50
quit

> Perhaps discuss this idea on emacs-devel?

Actually, this have been already discussed recently at great length in
https://lists.gnu.org/archive/html/emacs-devel/2021-01/msg01120.html

And no objections were raised against adding this feature
as long as it's opt-in.  So now it's pushed to master.

> Thinking long term, I think it would be interesting to consider a future
> where all of the various third party "modal" packages (evil, hydra,
> etc.) could use higher level facilities provided by the Emacs core.
> This patch is a step in that direction.  I'm interested to see how it
> progresses.

It would be interesting to try using this feature in external packages.

> Another interesting question: how do we surface how to use these
> transient modes in Emacs help, if at all?

I wonder why the Help system currently doesn't show symbol properties?
Maybe because there are too many properties, and most of them are
uninteresting to most users?  I tried:

  (require 'data-debug)
  (data-debug-eval-expression ''(other-window))

and it shows:

 > #'other-window
   > repeat-map : 'other-window-repeat-map
   > event-symbol-element-mask : #<list o' stuff: 2 entries>
   > event-symbol-elements : #<list o' stuff: 1 entries>
   > modifier-cache : #<list o' stuff: 1 entries>

where the relevant property is only `repeat-map', whereas the remaining 3
are some low-level properties.

Maybe the Help could show the values only of such properties
that have a special property on it?  For example, when the symbol
`repeat-map' has a property `show-help', then show its value in Help?




Added tag(s) fixed. Request was from Juri Linkov <juri <at> linkov.net> to control <at> debbugs.gnu.org. (Wed, 17 Feb 2021 18:07:02 GMT) Full text and rfc822 format available.

bug marked as fixed in version 28.0.50, send any further explanations to 46515 <at> debbugs.gnu.org and Juri Linkov <juri <at> linkov.net> Request was from Juri Linkov <juri <at> linkov.net> to control <at> debbugs.gnu.org. (Wed, 17 Feb 2021 18:07:02 GMT) Full text and rfc822 format available.

bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Thu, 18 Mar 2021 11:24:05 GMT) Full text and rfc822 format available.

This bug report was last modified 3 years and 39 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.