jwiegley / use-package

A use-package declaration for simplifying your .emacs
https://jwiegley.github.io/use-package
GNU General Public License v3.0
4.39k stars 259 forks source link

feature request: keyword to handle repeat-mode maps #964

Closed Hugo-Heagren closed 2 years ago

Hugo-Heagren commented 2 years ago

Emacs recently gained repeat-mode, which allows users to attach small transient keymaps to commands, active only immediately after that command is used (including the command itself in the keymap makes it possible to chain uses together and is thus very common, but is not necessary). This is a great feature and I already use it a lot, but defining and attaching the maps is very cumbersome. Most of what I define is specific to some package or other, so this seems exactly the sort of thing use-package is for solving: syntactically simple, package-specific customisation.

I'm not entirely sure what this would look like. It could be an extension to the current :bind syntax, or it could be a new keyword altogether (:repeat). At the very least, each element in the repeat section of a use-package call would need to include a list of char-command pairs, and some kind of information about which ones were to be active after which commands. Given that there is a lot of information, I think it would probably be better to make it a separate keyword, but others might have good ideas about cleaner syntax within the existing keywords.

If others think this might be useful, I would be happy to try implementing it.

markhepburn commented 2 years ago

I'd be keen!

Hugo-Heagren commented 2 years ago

@markhepburn I'm hoping to eventually get up to something with more features like this, but for now I've got a basic working implementation over of my fork. It's always great to have other people test work, so if you want to have a go and give me any feedback or ideas that would be great!

Example usage; the following two are equivalent (actual examples from my own init.el):

(use-package git-gutter+
  :demand t
  :config
  (global-git-gutter+-mode)
  (setq git-gutter+-modified-sign "!"
    git-gutter+-deleted-sign  "-")
  :bind
  ("C-c n" . git-gutter+-next-hunk)
  ("C-c p" . git-gutter+-previous-hunk)
  ("C-c s" . git-gutter+-stage-hunks)
  ("C-c r" . git-gutter+-revert-hunk)
  (:repeat-map my/git-gutter+-repeat-map
           ("n" . git-gutter+-next-hunk)
           ("p" . git-gutter+-previous-hunk)
           ("s" . git-gutter+-stage-hunks)
           ("r" . git-gutter+-revert-hunk)
           :repeat-docstring
               "Keymap to repeat git-gutter+-* commands."))
(use-package git-gutter+
  :demand t
  :config
  (global-git-gutter+-mode)
  (setq git-gutter+-modified-sign "!"
    git-gutter+-deleted-sign  "-")
  (defvar my/git-gutter+-repeat-map
    (let ((map (make-sparse-keymap)))
      (define-key map "n" 'git-gutter+-next-hunk)
      (define-key map "p" 'git-gutter+-previous-hunk)
      (define-key map "s" 'git-gutter+-stage-hunks)
      (define-key map "r" 'git-gutter+-revert-hunk)
      map)
    "Keymap to repeat git-gutter+-* commands.")
  (mapc (lambda (cmd) (put cmd 'repeat-map 'my/git-gutter+-repeat-map))
    '(git-gutter+-next-hunk
      git-gutter+-previous-hunk
      git-gutter+-stage-hunks
      git-gutter+-revert-hunk))
  :bind
  ("C-c n" . git-gutter+-next-hunk)
  ("C-c p" . git-gutter+-previous-hunk)
  ("C-c s" . git-gutter+-stage-hunks)
  ("C-c r" . git-gutter+-revert-hunk))
markhepburn commented 2 years ago

Nice work, thank you! (and much simpler than what I had in mind; I forgot that you could set the repeat-map property, I was looking at the example from other-window where the O binding sets a transient variable instead a lambda.

I'll check it out (may not be immediate). My use-case is replacing my window-management hydra.

markhepburn commented 2 years ago

First impressions it works quite well.

For reference, this is my use:

(defun other-window-reverse ()
  (interactive)
  (other-window -1))
(use-package window
  :ensure nil                           ; built-in
  :bind
  (:repeat-map other-window-repeat-map
               ;; Defaults:
               ("o" . other-window)
               ("O" . other-window-reverse)
               ("h" . windmove-left)
               ("j" . windmove-down)
               ("k" . windmove-up)
               ("l" . windmove-right)
               ;; Resizing:
               ("K" . enlarge-window)
               ("J" . shrink-window)
               ("H" . enlarge-window-horizontally)
               ("L" . shrink-window-horizontally)
               ("+" . balance-windows)
               ;; Adding/Deleting:
               ("-" . split-window-vertically)
               ("|" . split-window-horizontally)
               ("0" . delete-window)
               ("1" . delete-other-windows)
               ("u" . winner-undo)))
Hugo-Heagren commented 2 years ago

@markhepburn see #974.

QiangF commented 5 months ago

We have :continue and :exit, what about add an :enter key for repeat map?

    :repeat-map easy-kill-repeat-map
    :enter
    ("SPC" . kill-whole-line)
    :continue
    ("n" . next-line)
    ("p" . previous-line)
    :exit
    ("ESC" . keyboard-quit)

Obviously we don't want next-line and previous-line to trigger the repeat-map.

Hugo-Heagren commented 5 months ago

We have :continue and :exit, what about add an :enter key for repeat map?

@QiangF so you're aware, here's why it wasn't originally implemented.

It's worth mentioning that I tried to implement an :enter keyword as well, which would set the repeat-map property of a command, but without binding that command in the keymap. But I discovered that commands like this (with a repeat-map property pointing at a keymap in which they are not themselves bound) don't seem to actually invoke the repeat map. I tried it with the standard, manual way of building a repeat map and that didn't work either, so I think this is a bug with repeat-mode (or a feature which I don't understand). :enter would probably be the rarest used keyword though, so I don' think this is a big problem, and it would be simple to add in future if need be.

But I agree it would be a good idea!

QiangF commented 5 months ago

There is a package that have :enter. :enter will be quite useful for such kind of local modal editing, without it you have to carefully pick which commands should be in the repeat map.

https://tildegit.org/acdw/define-repeat-map.el/

QiangF commented 5 months ago

define-repeat-map works as expected

 (define-repeat-map easy-kill-repeat-map
   ;; commands that triggers the repeat-map should be put under :enter
   (:enter kill-whole-line)
   ;; conmmands in the repeat map is put under :continue, they do not trigger the repeat-map
   (:continue
    "SPC" kill-whole-line
    "n" next-line
    "p" previous-line
    "f" forward-word
    "b" backward-word)
   ;; conmmands in :exit leaves the repeat-map
   (:exit
    "ESC" keyboard-quit))

The following doesn't work:

 (define-repeat-map easy-kill-repeat-map
   "SPC" kill-whole-line
   (:enter kill-whole-line)
   (:continue
    "n" next-line
    "p" previous-line
    "f" forward-word
    "b" backward-word)
   (:exit
    "ESC" keyboard-quit))