2
votes

There are already some questions on the topic of repeatable emacs commands (i.e. the behaviour of C-x z [repeat-command], where each subsequent z repeats the last command), however, none of the automatic solutions can cope with non-prefix keybindings (My terminology: C-c p is a prefix keybinding with prefix C-c, the keystroke M-s-+ on the other hand is a non-prefix keybinding).

In my setup I have bound M-s-+ to text-scale-increase and M-s-- to text-scale-decrease. It would be nice to just hit + or - for repeated zooming after the initial command. This can be achieved by the following piece of elisp:

(defvar text-scale-temp-keymap (make-sparse-keymap))
(define-key text-scale-temp-keymap (kbd "+") 'text-scale-increase)
(define-key text-scale-temp-keymap (kbd "-") 'text-scale-decrease)

(defun text-scale-increase-rep (inc)
  (interactive "p")
  (text-scale-increase inc)
  (set-temporary-overlay-map text-scale-temp-keymap t))

(defun text-scale-decrease-rep (inc)
  (interactive "p")
  (text-scale-decrease inc)
  (set-temporary-overlay-map text-scale-temp-keymap t))

(global-set-key (kbd "M-s-+") 'text-scale-increase-rep)
(global-set-key (kbd "M-s--") 'text-scale-decrease-rep)

However, to repeat this code every time I want to create a repeatable keybinding is cumbersome and unnecessary. I'm asking for a way to automatise the task. Image this code

(make-repeatable-command 'text-scale-increase '(("+" . text-scale-increase) 
                                                ("-" . text-scale-decrease)))

would create the command named text-scale-increase-rep and an overlay keymap named text-scale-increase-temporary-map with the corresponding keys.

I assume this is possible, but how?

2

2 Answers

2
votes

Try this:

(defun defrepeatable (alist)
  (lexical-let ((keymap (make-sparse-keymap))
                (func (cdar alist)))
    (mapcar (lambda(x) (define-key keymap (car x) (cdr x))) alist)
    (lambda (arg)
      (interactive "p")
      (funcall func arg)
      (set-temporary-overlay-map keymap t))))

(global-set-key (kbd "C-z")
                (defrepeatable
                    '(("+" . text-scale-increase) 
                      ("-" . text-scale-decrease)))) 
2
votes

An alternative --

You do not need all of that, if all you want to do is (a) define a repeatable command and (b) bind it to a (repeatable) key.

The solution I presented in the pages (1, 2) that you linked to, applies here too. The point of that solution was not that it is limited to use with a prefix key but that you can also use it to repeat a key that is on a prefix key.

Here is the same solution, applied to your example. You only need to define repeat-command once -- you can use it to define any number of repeatable commands.

(defun repeat-command (command)
  "Repeat COMMAND."
 (interactive)
 (let ((repeat-previous-repeated-command  command)
       (last-repeatable-command           'repeat))
   (repeat nil)))

(defun text-scale-increase-rep (inc)
  (interactive "p")
  (repeat-command 'text-scale-increase))

(defun text-scale-decrease-rep (inc)
  (interactive "p")
  (repeat-command 'text-scale-decrease))

(global-set-key (kbd "M-s-+") 'text-scale-increase-rep)
(global-set-key (kbd "M-s--") 'text-scale-decrease-rep)