Subject: bug#12232: 24.1.50; regression: repeat.el commit 2012-05-05 breaks repeating commands

From: "Drew Adams" <[email-masked]>
Date: Sat, 18 Aug 2012 19:55:42 -0700

I believe this regression was introduced by the following commit:
 
Stefan Monnier2012-05-05 02:50:20Revision ID: [email-masked]=
0505025020-ihjvoqehkrxkfxw8
 
Use set-temporary-overlay-map.
* lisp/repeat.el: Use lexical-binding.
(repeat-last-self-insert, repeat-num-input-keys-at-self-insert)
(repeat-undo-count): Remove.
(repeat):
* lisp/progmodes/octave-mod.el (octave-abbrev-start):
* lisp/progmodes/f90.el (f90-abbrev-start):
* lisp/face-remap.el (text-scale-adjust):
* lisp/kmacro.el (kmacro-call-macro): Use set-temporary-overlay-map.

----
 
I use the following idiom in more than one library, to create repeatable
versions of commands - it is very handy.  I use such commands to cycle
through bookmarks, to move forward over thing-at-point things repeatedly,
etc.
 
The idea is this: use `repeat' to create a repeatable command that can be
bound to a key sequence with a prefix key.
 
For example, I have `C-x p n' bound to a command that cycles through
bookmarks, and I have `C-x <down>' bound to a command that repeatedly
moves forward over thing-at-point things of different kinds.
 
Example:
 
(defun repeat-command (command)
  "Repeat COMMAND.  (more explanation needed)"
 (interactive)
 (let ((repeat-previous-repeated-command  command)
       (last-repeatable-command           'repeat))
   (repeat nil)))
 
(defun backward-char-repeat ()
  "Like `backward-char'.  (more explanation needed)"
  (interactive)
  (repeat-command 'backward-char))
 
(define-key ctl-x-map [up] 'backward-char-repeat)
 
Now do C-x <up> <up> <up> <up>... to repeat the command `backward-char'.
In this case, the particular command that is repeated is not very
interesting, and it already has a binding that does not use a prefix key
- a key (`C-b') that is itself repeatable.
 
But you get the idea: the idiom works with any command, and it lets me
put such commands along with other commands on a common prefix for a
given mode.
 
For example, I put `bookmark-map' on a prefix key `C-x p', so all
bookmark commands are available on the same prefix.  This includes
bookmark navigation/cycling commands, which are repeatable.  There are
several kinds of cycling, with different bookmark navigation lists:
 
C-x p f, C-x p b: cycle bookmarks from the current navigation list
                  (which can be any set of bookmarks, in a particular
                  sort order)
 
C-x p n, C-x p p: cycle the bookmarks of the current file/buffer
 
C-x p C-<down>, C-x p C-<up>: cycle highlighted bookmarks in file
 
E.g., C-x p n n n n n n... cycles bookmarks in the current file.
 
This technique works in all Emacs versions since repeat.el was
introduced, which was Emacs 22, I believe.  It works in Emacs 24 also,
up through a build of 2012-04-30.  It does not work in Emacs 24 builds I
have, starting with 2012-05-07.
 
This is an important feature for me.  I really hope it will be fixed.
 
The symptom of the breakage is this: It seems to work at first: it can
be repeated twice (always, it seems), and then I get this when I hit
the repeatable key the third time:
 
Debugger entered--Lisp error: (error "Lisp nesting exceeds `max-lisp-eval-d=
epth'")
  repeat(nil)
  call-interactively(repeat)
  #[0 "..." [ignore repeat-message-function this-command repeat call-intera=
ctively] 2 "\n\n(fn)" nil]()
  call-interactively(#[0 "..." [ignore repeat-message-function this-command=
 repeat call-interactively] 2 "\n\n(fn)" nil])
  repeat(nil)
  call-interactively(repeat)
  #[0 "..." [ignore repeat-message-function this-command repeat call-intera=
ctively] 2 "\n\n(fn)" nil]()
  call-interactively(#[0 "..." [ignore
  repeat-message-function this-command repeat call-interactively] 2
  "\n\n(fn)" nil])
...
  repeat(nil)
  call-interactively(repeat)
  #[0 "..." [ignore repeat-message-function this-command repeat call-intera=
ctively] 2 "\n\n(fn)" nil]()
  call-interactively(#[0 "..." [ignore
  repeat-message-function this-command repeat call-interactively] 2
  "\n\n(fn)" nil] nil nil)
 
Here is the backtrace when I load repeat.el instead of repeat.elc:
 
Debugger entered--Lisp error: (error "Lisp nesting exceeds `max-lisp-eval-d=
epth'")
  (when (eq last-repeatable-command (quote repeat)) (setq last-repeatable-c=
ommand repeat-previous-repeated-command))
  repeat(nil)
  call-interactively(repeat)
  (let ((repeat-message-function fun)) (setq this-command (quote repeat)) (=
call-interactively (quote repeat)))
  (closure ((fun . ignore) (map keymap (down closure #1 nil (interactive) (=
let ((repeat-message-function fun)) (setq this-command (quote repeat)) (cal=
l-interactively (quote repeat))))) (repeat-repeat-char . down) (repeat-arg)=
 t) nil (interactive) (let ((repeat-message-function fun)) (setq this-comma=
nd (quote repeat)) (call-interactively (quote repeat))))()
  call-interactively((closure ((fun . ignore) (map keymap (down closure #1 =
nil (interactive) (let ((repeat-message-function fun)) (setq this-command (=
quote repeat)) (call-interactively (quote repeat))))) (repeat-repeat-char .=
 down) (repeat-arg) t) nil (interactive) (let ((repeat-message-function fun=
)) (setq this-command (quote repeat)) (call-interactively (quote repeat)))))
  (if (or (stringp indirect) (vectorp indirect)) (let ((last-repeatable-com=
mand last-repeatable-command)) (execute-kbd-macro last-repeatable-command))=
 (call-interactively last-repeatable-command))
  (let ((indirect (indirect-function last-repeatable-command))) (if (or (st=
ringp indirect) (vectorp indirect)) (let ((last-repeatable-command last-rep=
eatable-command)) (execute-kbd-macro last-repeatable-command)) (call-intera=
ctively last-repeatable-command)))
  (if (memq last-repeatable-command (quote (exit-minibuffer minibuffer-comp=
lete-and-exit self-insert-and-exit))) (let ((repeat-command (car command-hi=
story))) (repeat-message "Repeating %S" repeat-command) (eval repeat-comman=
d)) (if (null repeat-arg) (repeat-message "Repeating command %S" last-repea=
table-command) (setq current-prefix-arg repeat-arg) (repeat-message "Repeat=
ing command %S %S" repeat-arg last-repeatable-command)) (when (eq last-repe=
atable-command (quote self-insert-command)) (setq last-command-event (char-=
before))) (let ((indirect (indirect-function last-repeatable-command))) (if=
 (or (stringp indirect) (vectorp indirect)) (let ((last-repeatable-command =
last-repeatable-command)) (execute-kbd-macro last-repeatable-command)) (cal=
l-interactively last-repeatable-command))))
  (let ((repeat-repeat-char (if (eq repeat-on-final-keystroke t) last-comma=
nd-event (car (memq last-command-event (listify-key-sequence repeat-on-fina=
l-keystroke)))))) (if (memq last-repeatable-command (quote (exit-minibuffer=
 minibuffer-complete-and-exit self-insert-and-exit))) (let ((repeat-command=
 (car command-history))) (repeat-message "Repeating %S" repeat-command) (ev=
al repeat-command)) (if (null repeat-arg) (repeat-message "Repeating comman=
d %S" last-repeatable-command) (setq current-prefix-arg repeat-arg) (repeat=
-message "Repeating command %S %S" repeat-arg last-repeatable-command)) (wh=
en (eq last-repeatable-command (quote self-insert-command)) (setq last-comm=
and-event (char-before))) (let ((indirect (indirect-function last-repeatabl=
e-command))) (if (or (stringp indirect) (vectorp indirect)) (let ((last-rep=
eatable-command last-repeatable-command)) (execute-kbd-macro last-repeatabl=
e-command)) (call-interactively last-repeatable-command)))) (when repeat-re=
peat-char (set-temporary-overlay-map (let ((map (make-sparse-keymap))) (def=
ine-key map (vector repeat-repeat-char) (if (null repeat-message-function) =
(quote repeat) (let (...) (lambda nil ... ...)))) map))))
  repeat(nil)
  call-interactively(repeat)
  (let ((repeat-message-function fun)) (setq this-command (quote repeat)) (=
call-interactively (quote repeat)))
  (closure ((fun . ignore) (map keymap (down closure #1 nil (interactive) (=
let ((repeat-message-function fun)) (setq this-command (quote repeat)) (cal=
l-interactively (quote repeat))))) (repeat-repeat-char . down) (repeat-arg)=
 t) nil (interactive) (let ((repeat-message-function fun)) (setq this-comma=
nd (quote repeat)) (call-interactively (quote repeat))))()
  call-interactively((closure ((fun . ignore) (map keymap (down closure #1 =
nil (interactive) (let ((repeat-message-function fun)) (setq this-command (=
quote repeat)) (call-interactively (quote repeat))))) (repeat-repeat-char .=
 down) (repeat-arg) t) nil (interactive) (let ((repeat-message-function fun=
)) (setq this-command (quote repeat)) (call-interactively (quote repeat)))))
  (if (or (stringp indirect) (vectorp indirect)) (let ((last-repeatable-com=
mand last-repeatable-command)) (execute-kbd-macro last-repeatable-command))=
 (call-interactively last-repeatable-command))
  (let ((indirect (indirect-function last-repeatable-command))) (if (or (st=
ringp indirect) (vectorp indirect)) (let ((last-repeatable-command last-rep=
eatable-command)) (execute-kbd-macro last-repeatable-command)) (call-intera=
ctively last-repeatable-command)))
  (if (memq last-repeatable-command (quote (exit-minibuffer minibuffer-comp=
lete-and-exit self-insert-and-exit))) (let ((repeat-command (car command-hi=
story))) (repeat-message "Repeating %S" repeat-command) (eval repeat-comman=
d)) (if (null repeat-arg) (repeat-message "Repeating command %S" last-repea=
table-command) (setq current-prefix-arg repeat-arg) (repeat-message "Repeat=
ing command %S %S" repeat-arg last-repeatable-command)) (when (eq last-repe=
atable-command (quote self-insert-command)) (setq last-command-event (char-=
before))) (let ((indirect (indirect-function last-repeatable-command))) (if=
 (or (stringp indirect) (vectorp indirect)) (let ((last-repeatable-command =
last-repeatable-command)) (execute-kbd-macro last-repeatable-command)) (cal=
l-interactively last-repeatable-command))))
  (let ((repeat-repeat-char (if (eq repeat-on-final-keystroke t) last-comma=
nd-event (car (memq last-command-event (listify-key-sequence repeat-on-fina=
l-keystroke)))))) (if (memq last-repeatable-command (quote (exit-minibuffer=
 minibuffer-complete-and-exit self-insert-and-exit))) (let ((repeat-command=
 (car command-history))) (repeat-message "Repeating %S" repeat-command) (ev=
al repeat-command)) (if (null repeat-arg) (repeat-message "Repeating comman=
d %S" last-repeatable-command) (setq current-prefix-arg repeat-arg) (repeat=
-message "Repeating command %S %S" repeat-arg last-repeatable-command)) (wh=
en (eq last-repeatable-command (quote self-insert-command)) (setq last-comm=
and-event (char-before))) (let ((indirect (indirect-function last-repeatabl=
e-command))) (if (or (stringp indirect) (vectorp indirect)) (let ((last-rep=
eatable-command last-repeatable-command)) (execute-kbd-macro last-repeatabl=
e-command)) (call-interactively last-repeatable-command)))) (when repeat-re=
peat-char (set-temporary-overlay-map (let ((map (make-sparse-keymap))) (def=
ine-key map (vector repeat-repeat-char) (if (null repeat-message-function) =
(quote repeat) (let (...) (lambda nil ... ...)))) map))))
 
...
  repeat(nil)
  call-interactively(repeat)
  (let ((repeat-message-function fun)) (setq this-command (quote repeat)) (=
call-interactively (quote repeat)))
  (closure ((fun . ignore) (map keymap (down closure #1 nil (interactive) (=
let ((repeat-message-function fun)) (setq this-command (quote repeat)) (cal=
l-interactively (quote repeat))))) (repeat-repeat-char . down) (repeat-arg)=
 t) nil (interactive) (let ((repeat-message-function fun)) (setq this-comma=
nd (quote repeat)) (call-interactively (quote repeat))))()
  call-interactively((closure ((fun . ignore) (map keymap (down closure #1 =
nil (interactive) (let ((repeat-message-function fun)) (setq this-command (=
quote repeat)) (call-interactively (quote repeat))))) (repeat-repeat-char .=
 down) (repeat-arg) t) nil (interactive) (let ((repeat-message-function fun=
)) (setq this-command (quote repeat)) (call-interactively (quote repeat))))=
 nil nil)
 
As you no doubt know, it is very difficult to debug uses of repeat.el.
It is impossible to use the debugger, and even printing `message's is
problematic.  Hopefully this info will help.  You should be able to
reproduce the problem using the code above.  If you try it in an older
version you will see what it should do.
 
In GNU Emacs 24.1.50.1 (i386-mingw-nt5.1.2600)
 of 2012-08-13 on MARVIN
Bzr revision: 109584 [email-masked]=
0u
Windowing system distributor `Microsoft Corp.', version 5.1.2600
Configured using:
 `configure --with-gcc (4.6) --no-opt --enable-checking --cflags
 -ID:/devel/emacs/libs/libXpm-3.5.8/include
 -ID:/devel/emacs/libs/libXpm-3.5.8/src
 -ID:/devel/emacs/libs/libpng-dev_1.4.3-1/include
 -ID:/devel/emacs/libs/zlib-dev_1.2.5-2/include
 -ID:/devel/emacs/libs/giflib-4.1.4-1/include
 -ID:/devel/emacs/libs/jpeg-6b-4/include
 -ID:/devel/emacs/libs/tiff-3.8.2-1/include
 -ID:/devel/emacs/libs/gnutls-3.0.9/include
 -ID:/devel/emacs/libs/libiconv-1.13.1-1-dev/include
 -ID:/devel/emacs/libs/libxml2-2.7.8/include/libxml2'