2

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?

Community
  • 1
  • 1
elemakil
  • 3,681
  • 28
  • 53

2 Answers2

2

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)))) 
abo-abo
  • 20,038
  • 3
  • 50
  • 71
  • As far as I see this does the same as the linked solutions: it can only be used for _prefix style_ keystrokes a la `C-z +` `+` `+` and can not be used for the desired `M-s-+` `+` `+` case. – elemakil Feb 19 '14 at 13:44
  • Works with `C-M-+ + -` and `M-+ + -` and `M-s-+ + -` although with the latter is a bit ridiculous to press 4 keys at once. – abo-abo Feb 19 '14 at 13:56
  • If I replace `C-z` (your code) by `M-s` and enter `M-s-+` I get the error message `M-s-+ is undefined` (tried in `emacs -Q` only added `(require 'cl)`). – elemakil Feb 19 '14 at 14:02
  • You should put `M-s-+`, not `M-s`. – abo-abo Feb 19 '14 at 14:05
  • Aha! Ok, did that, now I get the error message: `funcall: Symbol's value as variable is void: alist` [Still in `emacs -Q`]. – elemakil Feb 19 '14 at 14:07
  • Now it's working! For educational purposes: Can you explain why it wasn't working before? – elemakil Feb 19 '14 at 14:31
  • 1
    Before `alist` was in lambda but not in lexical-let, so the lambda did not see it. It was working in my instance of Emacs because I've bound `alist` as a global variable during debugging. In summary: if you want lambda to see anything that's not its arg or global, use `lexical-let` – abo-abo Feb 19 '14 at 14:35
2

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)
Community
  • 1
  • 1
Drew
  • 29,895
  • 7
  • 74
  • 104