bug#46515: Repeat mode

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

bug#46515: Repeat mode

Juri Linkov-2
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
...


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
Reply | Threaded
Open this post in threaded view
|

bug#46515: Repeat mode

Matt Armstrong-3
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.



Reply | Threaded
Open this post in threaded view
|

bug#46515: Repeat mode

Juri Linkov-2
>> 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:


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))))))
Reply | Threaded
Open this post in threaded view
|

bug#46515: Repeat mode

Matt Armstrong-3
Juri Linkov <[hidden email]> 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?



Reply | Threaded
Open this post in threaded view
|

bug#46515: Repeat mode

Juri Linkov-2
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?