unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#46515: Repeat mode
@ 2021-02-14 18:27 Juri Linkov
  2021-02-14 23:59 ` Matt Armstrong
  0 siblings, 1 reply; 6+ messages in thread
From: Juri Linkov @ 2021-02-14 18:27 UTC (permalink / raw)
  To: 46515

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

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
...


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: repeat-mode.patch --]
[-- Type: text/x-diff, Size: 4760 bytes --]

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 ************************* ;;;;;
 
+\f
+;;; 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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* bug#46515: Repeat mode
  2021-02-14 18:27 bug#46515: Repeat mode Juri Linkov
@ 2021-02-14 23:59 ` Matt Armstrong
  2021-02-15  9:17   ` Juri Linkov
  0 siblings, 1 reply; 6+ messages in thread
From: Matt Armstrong @ 2021-02-14 23:59 UTC (permalink / raw)
  To: Juri Linkov, 46515

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.





^ permalink raw reply	[flat|nested] 6+ messages in thread

* bug#46515: Repeat mode
  2021-02-14 23:59 ` Matt Armstrong
@ 2021-02-15  9:17   ` Juri Linkov
  2021-02-15 17:04     ` Juri Linkov
  0 siblings, 1 reply; 6+ messages in thread
From: Juri Linkov @ 2021-02-15  9:17 UTC (permalink / raw)
  To: Matt Armstrong; +Cc: 46515

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

> 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.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: repeat-message.patch --]
[-- Type: text/x-diff, Size: 616 bytes --]

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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* bug#46515: Repeat mode
  2021-02-15  9:17   ` Juri Linkov
@ 2021-02-15 17:04     ` Juri Linkov
  2021-02-16 16:49       ` Matt Armstrong
  0 siblings, 1 reply; 6+ messages in thread
From: Juri Linkov @ 2021-02-15 17:04 UTC (permalink / raw)
  To: Matt Armstrong; +Cc: 46515

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

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


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: repeat-message-2.patch --]
[-- Type: text/x-diff, Size: 1566 bytes --]

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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* bug#46515: Repeat mode
  2021-02-15 17:04     ` Juri Linkov
@ 2021-02-16 16:49       ` Matt Armstrong
  2021-02-17 18:05         ` Juri Linkov
  0 siblings, 1 reply; 6+ messages in thread
From: Matt Armstrong @ 2021-02-16 16:49 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 46515

Juri Linkov <juri@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?





^ permalink raw reply	[flat|nested] 6+ messages in thread

* bug#46515: Repeat mode
  2021-02-16 16:49       ` Matt Armstrong
@ 2021-02-17 18:05         ` Juri Linkov
  0 siblings, 0 replies; 6+ messages in thread
From: Juri Linkov @ 2021-02-17 18:05 UTC (permalink / raw)
  To: Matt Armstrong; +Cc: 46515

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?





^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-02-17 18:05 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-14 18:27 bug#46515: Repeat mode Juri Linkov
2021-02-14 23:59 ` Matt Armstrong
2021-02-15  9:17   ` Juri Linkov
2021-02-15 17:04     ` Juri Linkov
2021-02-16 16:49       ` Matt Armstrong
2021-02-17 18:05         ` Juri Linkov

Code repositories for project(s) associated with this public inbox

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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).