[comp.emacs] keyboard macros

Ram-Ashwin@cs.yale.edu (Ashwin Ram) (02/17/89)

In article <36077@bbn.COM>, mesard@bbn.com (Wayne Mesard) writes:
> In article <690026@hpsemc.HP.COM> gph@hpsemc.HP.COM (Old run-down actor) writes:
> >Can anyone tell me how I can get gnuemacs to (with one keystroke) copy
> >the character on the line above the cursor to the current cursor
> >position?
> 
> For simple things like this, all you need is keyboard macros. [...]
>                                                        To bind it to
> another key, you must first name it with M-x name-last-kbd-macro.  After
> naming it you can insert it in, e.g., your ~/.emacs file, so it will be
> available in later Emacs sessions.  To do this use insert-kbd-macro.

I wish there was a way of "decompiling" the macro, i.e., a function that
inserted, not the raw keystrokes, but the names of the commands that those
keytrokes were bound to.  Keyboard macros are neat for simple things, but if
you want to modify one you're out of luck.

Another advantage of this is that it would be a good way to get a template of
an editing function that you're planning to write, by using keystrokes to do
what you want to do and then taking it from there.

While we're on the subject of keyboard macros, is there a way to tell Emacs
not to stop defining a macro when you do make an error?  I hate it when I'm
halfway through an enormously complicated keyboard macro and I have to do it
all over again because I hit the wrong key.

-- Ashwin.

tale@its.rpi.edu (David C Lawrence) (02/17/89)

In article <50884@yale-celray.yale.UUCP> Ram-Ashwin@cs.yale.edu (Ashwin Ram) writes:

   I wish there was a way of "decompiling" the macro, i.e., a function that
   inserted, not the raw keystrokes, but the names of the commands that those
   keytrokes were bound to.  Keyboard macros are neat for simple things, but if
   you want to modify one you're out of luck.

For most purposes I basically dislike any Emacs which isn't GNU; I'm
very used to using many, many features of GNU Emacs and know its
syntax and functions much better than MicroGnu or Prime's Emacs.

Prime Emacs, however, does what you want.  Rather than simply doing an
fset to the string which would execute tha macro if typed, it will
expand the macro to its PEEL (like LISP, but much worse) equivalent.
You can then make modifications as needed.  This of course helps
little for what you want from GNU.  

macros.el is not a very large file at all; I think it would be quite
possible to build a skeleton defun from the keymap definitions for the
macro.  The difficulty is in knowing how (interactive) is being called
by each function.  I would be willing to find a way to do this, but it
on't be any time real soon.  If no one else does it, I'll put it on my
list fof things to do (around April or so ... ugh).
 
Dave
--
      tale@rpitsmts.bitnet, tale%mts@rpitsgw.rpi.edu, tale@pawl.rpi.edu

raible@orville.nas.nasa.gov (Eric L. Raible) (02/17/89)

In article <50884@yale-celray.yale.UUCP> Ram-Ashwin@cs.yale.edu (Ashwin Ram) writes:
>I wish there was a way of "decompiling" the macro, i.e., a function that
>inserted, not the raw keystrokes, but the names of the commands that those
>keytrokes were bound to.  Keyboard macros are neat for simple things, but if
>you want to modify one you're out of luck.
>

See below.  I didn't write this code, but have occasionally found it to be
useful.

>While we're on the subject of keyboard macros, is there a way to tell Emacs
>not to stop defining a macro when you do make an error?  I hate it when I'm
>halfway through an enormously complicated keyboard macro and I have to do it
>all over again because I hit the wrong key.
>

Although not it is not exactly what you want, you can give a prefix arg
to start-kbd-macro to append to the last macro.  It then becomes useful
to build complicated macros in steps.

- Eric

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; module: 	generate.el	
;;;; version: 	2.0
;;;; author: 	Ciaran A Byrne ciaran@gec-rl-hrc.co.uk
;;;; date:	2:Sept:87
;;;;
;;;;;;;;;;;;;;;;;;;; macro lisp expansion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;;	user commands:
;;;;		start-generating	- replaces start-kbd-macro ^X(
;;;;		stop-generating		-     "	   end-kbd-macro   ^X)
;;;;		expand-macro		- produces REAL emacs lisp code
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; (global-set-key "\^X(" 'start-generating)
;; (global-set-key "\^X)" 'stop-generating)

(defun caar (x) (car (car x)))
(defun cadr (x) (car (cdr x)))
(defun cdar (x) (cdr (car x)))
(defun cddr (x) (cdr (cdr x)))

(defun caadr (x) (car (cadr x)))
(defun caddr (x) (car (cddr x)))
(defun cadar (x) (car (cdar x)))
(defun cdadr (x) (cdr (cadr x)))


(defvar gen-history '(first . last) "command-history subsection pair")
(defvar generate-on nil "true if recording commands")


(defun start-generating ()
  "Records commands issued until the command stop-generating is invoked.
The recorded commands can be turned into emacs lisp using
the command expand-macro.

Keystrokes are echoed in the minibuffer to remind you that
you are doing something weird"
  (interactive)
  (if generate-on (message "Already generating !")
    (progn
      (setq generate-on t)
      (message "Started generating")
      (rplaca gen-history command-history) ; note beginning of macro
      (unwind-protect
	  (command-loop-3)		; run soft command loop
	(stop-generating))
      (run-hooks 'generate-hook))))

(defun stop-generating ()
  "Ends command recording. See also: start-generating and expand-macro"

  (interactive)
  (rplacd gen-history  command-history)	; note end of macro
  (message "Stopped generating")
  (setq generate-on nil))


(defun expand-macro (buffer fname doc)
  "Expands the most recently recorded command sequence into emacs lisp.
Outputs into BUFFER and calls the function NAME with DOC string.

See also: start-generating, stop-generating"

  (interactive
    "BBuffer for expansion : \nSNew function name : \nsDoc string : ")
  (if generate-on (stop-generating))
  (let ((macro (rev-sub-list gen-history))) ; chop macro out
    (get-buffer-create buffer)
    (set-buffer buffer)
    (goto-char (point-max))
    (set-mark (point))
    (insert "\n(defun " (symbol-name fname) " () " ) ; function header
    (insert "\"" doc)
    (insert "\nmacroised by " (user-full-name))
    (insert " @ " (current-time-string)  "\"\n")
    (insert "\n(interactive)\n")
    (setq standard-output (get-buffer buffer))
    (mapcar 'print macro)
    (exchange-point-and-mark)
    (mapcar 'delete-matching-lines	; zap useless stuff
	    '(   "^$"
		 "start-generating"
		 "stop-generating"
		 "expand-macro"
		 "execute-extended-command nil"
					; etc ?
		 ))
    (narrow-to-region (point) (point-max))
    (emacs-lisp-mode)
    (indent-region (point) (point-max) nil) ; neaten it all up
    (mapcar 'merge-multiple-numeric-args
	    '(
	      previous-line
	      next-line
	      delete-backward-char
	      backward-delete-char-untabify
	      backward-kill-word
	      kill-word
	      forward-char
	      backward-char
					; etc ?
	      ))
    (goto-char (point-max))
    (insert "\n)\n")
    (widen)
    (run-hooks 'expand-macro-hook)))


(defun rev-sub-list (pp)
  "Returns sublist from INTERVAL eg. (beginning . end) ,
where beginning & end point into the same list.
The item at end should be nearer the front of the list.
The car of the result is the element at beginning."

  (let ((stop (car pp))
	(here (cdr pp))
	(result nil))
    (if (not (memq (car stop) here)) (message "bad arg to rev-sub-list")
      (while (not (eq here stop))
	(setq result (cons (car here) result)) ; build in reverse
	(setq here (cdr here))))
    result))

(defun command-loop-3 ()
  "Mimics the internal command_loop_1,
but locks the RECORD arg to command-execute to true.

Handles universal & prefix arguments, fakes self-insert-command.

Fixes up incremental searches in command-history so that the non-incremental
versions are used instead."
  (while generate-on			; global flag
    (if (null (input-pending-p)) (sit-for 2))
    (let* ((ks (read-key-sequence ""))
	   (last-command-char (string-to-char (substring ks -1)))
	   (kc (key-binding ks)) )
      (cond
	((eq kc 'universal-argument) (universal-argument))
	((eq kc 'digit-argument) (digit-argument prefix-arg))
	((eq kc 'self-insert-command) (log-self-insert prefix-arg))
	((eq kc 'stop-generating) (stop-generating))
	(t (command-execute kc 'record)))
      (cond				; now patch search commands
	((eq kc 'isearch-forward)
	 (rplaca command-history
		 (list 'search-forward search-last-string)))
	((eq kc 'isearch-backward)
	 (rplaca command-history
		 (list 'search-backward search-last-string)))
	((eq kc 'isearch-forward-regexp)
	 (rplaca command-history
		 (list 're-search-forward search-last-regexp)))
	((eq kc 'isearch-backward-regexp)
	 (rplaca command-history
		 (list 're-search-backward search-last-regexp)))))))


(defun string-copy (s n)
  "Returns STRING concatted N times."
  (let ((res ""))
    (while (> n 0)
      (setq res (concat res s))
      (setq n (1- n)))
    res))
	   
(defun log-self-insert (n)
  "Replaces self-insert-command (q.v.).
Adds an insert command to command-history, and amalgamates the current
insertion with a previous insert command in command-history, if there is one."
  (setq n (if (integerp n) n 1))
  (let ((ins (string-copy (char-to-string last-input-char) n)))
    (insert ins)
    (if (eq 'insert (caar command-history))
	(let* ((prev (cadar command-history))
	       (str (concat prev ins)))
	  (rplacd (car command-history) (list str)))
      (setq command-history
	    (cons (list 'insert ins) command-history)))))


(defconst numarg "[ \t]+\\([0-9]+\\)")

(defun merge-multiple-numeric-args (s)
  "Coalesces a pair of lisp lines invoking the same FUNCTION with
a numeric arg so that a single function with the 2 component args
added is used instead.
e.g. (previous-line 4), (previous-line 1) => (previous-line 5)"
  (goto-char (point-min))
  (if (symbolp s) (setq s (symbol-name s)))
  (while
      (re-search-forward 
	(concat s numarg ".*\n[ \t]*(" s numarg) (point-max) t)
    (let* ((md (match-data))
	   (arg1 (buffer-substring (nth 2 md) (nth 3 md)))
	   (arg2 (buffer-substring (nth 4 md) (nth 5 md)))  
	   (newarg (+ (string-to-int arg1) (string-to-int arg2))))
      (delete-region (nth 0 md) (nth 1 md))
      (insert s " " (int-to-string newarg))
      (goto-char (nth 0 md)))))

barmar@think.COM (Barry Margolin) (02/17/89)

In article <50884@yale-celray.yale.UUCP> Ram-Ashwin@cs.yale.edu (Ashwin Ram) writes:
>I wish there was a way of "decompiling" the macro, i.e., a function that
>inserted, not the raw keystrokes, but the names of the commands that those
>keytrokes were bound to.  Keyboard macros are neat for simple things, but if
>you want to modify one you're out of luck.

It's extremely hard to do this right.  Multics Emacs had such a
feature, but it made lots of mistakes.  The problem is that it's
difficult to know the binding of a keystroke, because previous
commands might have caused key bindings to change.  For example, after
a "c-x b return" command, what is the "d" key bound to?  If the
previous buffer at the time of invocation is a Dired buffer it would
be bound to dired-flag-file-deleted, but in other cases it would be
self-insert-command.

Another problem comes up if the command reads input from the
minibuffer.  The macro compiler would have to know that it does this,
figure out how much of the keyboard macro is actually minibuffer
input, not compile that portion, and arrange for it to be read as
minibuffer input by the command.  It could probably figure this out if
the input were arguments processed by the 'interactive' declaration,
but if the function just does minibuffer input on its own it has
little chance.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

Ram-Ashwin@cs.yale.edu (Ashwin Ram) (02/17/89)

In article <36476@think.UUCP>, barmar@think.COM (Barry Margolin) writes:
> In article <50884@yale-celray.yale.UUCP> Ram-Ashwin@cs.yale.edu (Ashwin Ram) writes:
> >I wish there was a way of "decompiling" the macro, i.e., a function that
> >inserted, not the raw keystrokes, but the names of the commands that those
> >keytrokes were bound to.  Keyboard macros are neat for simple things, but if
> >you want to modify one you're out of luck.
> 
> It's extremely hard to do this right.  Multics Emacs had such a
> feature, but it made lots of mistakes.  The problem is that it's
> difficult to know the binding of a keystroke, because previous
> commands might have caused key bindings to change.  For example, after
> a "c-x b return" command, what is the "d" key bound to?  If the
> previous buffer at the time of invocation is a Dired buffer it would
> be bound to dired-flag-file-deleted, but in other cases it would be
> self-insert-command.

Good point.  I think the function would have to look up the bindings for your
keystrokes as you typed them in the keymap for the current mode, and
construct the lisp expression as it goes along.  It is probably impossible to
decompile a sequence of keystrokes after the fact since there is no record of
the bindings of those keystrokes.

-- Ashwin.

blarson@skat.usc.edu (Bob Larson) (02/19/89)

In article <36476@think.UUCP> barmar@kulla.think.com.UUCP (Barry Margolin) writes:
>In article <50884@yale-celray.yale.UUCP> Ram-Ashwin@cs.yale.edu (Ashwin Ram) writes:
>>I wish there was a way of "decompiling" the macro, i.e., a function that
>>inserted, not the raw keystrokes, but the names of the commands that those
>>keytrokes were bound to.

>The problem is that it's
>difficult to know the binding of a keystroke, because previous
>commands might have caused key bindings to change.

I consider this a bug in the way Gnu Emacs handles keyboard macros.
Mg saves the functions that have been executed (and argument strings)
rather than the keys pressed to get the result.  (Other than minor
twiddling -- self-inserted characters are merged into a call to insert,
etc.)  Decompile keyboard macro will probably be in the same release
of mg as an extention languages.  (Most likely 3a.)

-- 
Bob Larson	Arpa: Blarson@Ecla.Usc.Edu	blarson@skat.usc.edu
Uucp: {sdcrdcf,cit-vax}!oberon!skat!blarson
Prime mailing list:	info-prime-request%ais1@ecla.usc.edu
			oberon!ais1!info-prime-request

tale@pawl.rpi.edu (David C Lawrence) (02/22/89)

while going through the directory of available elisp, I noticed that
apparently someone in the UK did write some "generate" code a little
over a year ago.  The description says that it is a macro-to-elisp
converter.  The someone is:
Ciaran A Byrne <ciaran@gec-rl-hrc.co.uk>
 
So, has anyone used this?  Can you give us any idea of how well it
works?  Thanks much.
 
(Posted rather than mailed to Ciaran because it is obviously of
general interest.)

Dave
--
      tale@rpitsmts.bitnet, tale%mts@rpitsgw.rpi.edu, tale@pawl.rpi.edu

jay@gdx.UUCP (Jay A. Snyder) (12/05/90)

Is there a way to bind a keyboard macro (the "C-X(" type) to another
key and save it?

J

nat%DRAO.NRC.CA@VM.TCS.TULANE.EDU (Natalie Prowse) (12/07/90)

> Is there a way to bind a keyboard macro (the "C-X(" type) to another
> key and save it?

Jay, I have the following function defined to do just that. (I like the
way EVE-PLUS does it on the VAX, so I tried to emulate it. Map the
following function to a function key, and then use that key to end
your keyboard macro.

(defun map-keyboard-macro (key-input)
" End a keyboard macro definition and map it to KEY-INPUT key."
 (end-kbd-macro)
 (interactive "kPress key to map macro to: ")
 (define-key global-map key-input last-kbd-macro))

If you have multiple keyboard macros mapped to multiple keys, then the
following function might also be useful:

(defun repeat (key-input)
" repeat the KEY-INPUT defined keyboard macro."
 (interactive "kPress key to repeat: ")
 (setq last-kbd-macro key-input)
 (setq numtimes (read-from-minibuffer "Number of times to repeat: "))
 (repeat-last-kbd-macro (string-to-int numtimes)))

-Hope this helps. (no flames lease on the kludgey lisp code...;-)
 Natalie

=========================================
Natalie Prowse
Dominion Radio Astrophysical Observatory,        nat@drao.nrc.ca
National Research Council,                       (604) 497-5321
Penticton, BC, Canada