emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

recording-elisp.el - try recording commands as elisp code


From: yzhh
Subject: recording-elisp.el - try recording commands as elisp code
Date: Fri, 02 Nov 2007 05:27:47 +0800
User-agent: KNode/0.10.5

Hi,

This recording.el is maybe some useful results from our discussion on
recording kbd macro as elisp results. I'm now posting it not because it is
perfectly usable, but to trigger some ideas on improvements.

I started from a patch Juri gave to me. In his code there are modifications
in the C code and in kmacro.el and isearch.el. I recognize that the C code
patch is a must to get command args, and left them as is. 

But I think it is better to implement the recording outside other elisp
source. So I pulled his code out of those el files and wrote
recording-elisp.el. I also made further modifications to handle isearch
commands better.

If anybody is interested to give it a try. it is quite simple: M-x
start-recording-commands, do something, M-x end-recording-commands, and
then M-x insert-recorded-commands into current buffer.

Currently it is just a proof o f concept patch. It does not handle multiple
query replace, multiple cursor movement, etc. But it do compress multiple
character input into one insert, and gives quite short code for a fairly
long isearch session.

I really need help from some old-hand on elisp to push it further. It's even
better if some experienced developer want to take it over.

The patch is here:

Index: src/keyboard.c
===================================================================
--- src/keyboard.c      (revision 4)
+++ src/keyboard.c      (revision 9)
@@ -371,6 +371,9 @@
    command is stored in this-original-command.  It is nil otherwise.  */
 Lisp_Object Vthis_original_command;
 
+/* The arguments of the command being executed by the command loop.  */
+Lisp_Object Vthis_command_args;
+
 /* The value of point when the last command was started.  */
 int last_point_position;
 
@@ -1765,6 +1768,7 @@
 
       Vthis_command = cmd;
       real_this_command = cmd;
+      Vthis_command_args = Qnil;
       /* Note that the value cell will never directly contain nil
         if the symbol is a local variable.  */
       if (!NILP (Vpre_command_hook) && !NILP (Vrun_hooks))
@@ -1798,6 +1802,7 @@
                    = window_display_table (XWINDOW (selected_window));
                  lose = FETCH_CHAR (PT_BYTE);
                  SET_PT (PT + 1);
+                 Vthis_command_args = Fcons (Vcurrent_prefix_arg, Qnil);
                  if (! NILP (Vpost_command_hook))
                    /* Put this before calling adjust_point_for_property
                       so it will only get called once in any case.  */
@@ -1838,6 +1843,7 @@
                    = window_display_table (XWINDOW (selected_window));
                  SET_PT (PT - 1);
                  lose = FETCH_CHAR (PT_BYTE);
+                 Vthis_command_args = Fcons (Vcurrent_prefix_arg, Qnil);
                  if (! NILP (Vpost_command_hook))
                    goto directly_done;
                  if (current_buffer == prev_buffer
@@ -1905,6 +1911,7 @@
                  if (value == 2)
                    nonundocount = 0;
 
+                 Vthis_command_args = Fcons (make_number (c), Fcons
(Vcurrent_prefix_arg, Qnil));
                  if (! NILP (Vpost_command_hook))
                    /* Put this before calling adjust_point_for_property
                       so it will only get called once in any case.  */
@@ -3183,7 +3190,8 @@
 #endif
       
       last_input_char = c;
-      Fcommand_execute (tem, Qnil, Fvector (1, &last_input_char), Qt);
+      Fcommand_execute (tem, Qnil, /* Qt, */
+                     Fvector (1, &last_input_char), Qt);
 
       if (CONSP (c) && EQ (XCAR (c), Qselect_window) && !end_time)
        /* We stopped being idle for this event; undo that.  This
@@ -12004,6 +12012,10 @@
 result of looking up the original command in the active keymaps.  */);
   Vthis_original_command = Qnil;
 
+  DEFVAR_LISP ("this-command-args", &Vthis_command_args,
+              doc: /* Arguments of the command being executed.  */);
+  Vthis_command_args = Qnil;
+
   DEFVAR_INT ("auto-save-interval", &auto_save_interval,
              doc: /* *Number of input events between auto-saves.
 Zero means disable autosaving due to number of characters typed.  */);
Index: src/callint.c
===================================================================
--- src/callint.c       (revision 4)
+++ src/callint.c       (revision 9)
@@ -42,6 +42,7 @@
 
 extern Lisp_Object Vhistory_length;
 extern Lisp_Object Vthis_original_command, real_this_command;
+extern Lisp_Object Vthis_command_args;
 
 Lisp_Object Vcommand_debug_status, Qcommand_debug_status;
 Lisp_Object Qenable_recursive_minibuffers;
@@ -364,6 +365,9 @@
             and turn them into things we can eval.  */
          values = quotify_args (Fcopy_sequence (specs));
          fix_command (input, values);
+
+         Vthis_command_args = values;
+
          Vcommand_history
            = Fcons (Fcons (function, values), Vcommand_history);
 
@@ -808,6 +812,9 @@
          else
            visargs[i] = quotify_arg (args[i]);
        }
+
+      Vthis_command_args = XCDR (Flist (count + 1, visargs));
+
       Vcommand_history = Fcons (Flist (count + 1, visargs),
                                Vcommand_history);
       /* Don't keep command history around forever.  */
@@ -832,6 +839,8 @@
   Vthis_original_command = save_this_original_command;
   real_this_command= save_real_this_command;
   current_kboard->Vlast_command = save_last_command;
+  if (NILP (Vthis_command_args))
+    Vthis_command_args = XCDR (Flist (count + 1, args));
 
   {
     Lisp_Object val;
Index: lisp/recording-elisp.el
===================================================================
--- lisp/recording-elisp.el     (revision 0)
+++ lisp/recording-elisp.el     (revision 9)
@@ -0,0 +1,174 @@
+;;; recording-elisp.el --- recording emacs commands
+
+;;; Commentary:
+
+;; This recording-elisp.el provides the user interface to record a
+;; sequence of commands executed (in response to a sequence of user
+;; interaction) by emacs, and to provide the recorded commands in the
+;; form of emacs-lisp code. 
+
+;; To start recording commands, use M-x start-recording-commands.  To
+;; end, use M-x end-recording-commands.  Then M-x
+;; insert-recorded-commands will insert the recorded commands into
+;; current buffer at current point.
+
+;; Note: the output of insert-recorded-commands will not always be the
+;; same as the sequence of commands actually recorded.  Post
+;; processing of the commands are performed to make the output more
+;; readable and useful for further editing.  But the functionality of
+;; the resulting code is kept the same as the original commands,
+;; whenever possible and reasonable.
+
+(defvar recorded-commands nil
+  "List of recorded commands executed during macro definition.
+Each command is represented as a form to evaluate.")
+
+(defvar recording-commands nil
+  "Bool value indicating whether commands are being recorded.")
+
+(defun recording-isearch-push-state-function ()
+  "Function to be set as value of isearch-push-state-fuction.
+It returns value of isearch-regexp when called, which is then
+Stored in isearch state stack. We need this value to correctly
+recognize isearch operations given (only) its state stack."
+  isearch-regexp)
+  
+(defun start-recording-commands ()
+  "Start recording commands executed by emacs."
+  (interactive)
+  (add-hook 'post-command-hook 'recording-command t)
+  ; Set isearch-push-state-fuction only when it is not already set.
+  (if (not isearch-push-state-function)
+      (setq
isearch-push-state-function 'recording-isearch-push-state-function))
+  (setq recorded-commands nil)
+  (setq recording-commands t))
+
+(defun end-recording-commands ()
+  "End recording commands."
+  (interactive)
+  (remove-hook 'post-command-hook 'recording-command)
+  ; Clear isearch-push-state-fuction only when it has not been
+  ; set during the recording.
+  (if (eq
isearch-push-state-function 'recording-isearch-push-state-function)
+      (setq isearch-push-state-function nil))
+  (setq recording-commands nil))
+
+(defun recording-command ()
+  "This function is added to post-command-hook to perform the recording."
+  (unless (active-minibuffer-window)
+  (cond 
+   ((eq this-command 'isearch-exit)
+    (setq this-command-args isearch-cmds))) ; a fake isearch-exit command
+  
+  (setq recorded-commands (cons (cons this-command this-command-args)
+                               recorded-commands))))
+
+(defun insert-recorded-commands ()
+  (interactive)
+  (insert (pp-to-string
+          `(defun recorded-commands ()
+             "Commands recorded during the last 'recording commands' cycle."
+             (interactive)
+             ,@(convert-recorded-commands recorded-commands)))))
+
+(defun convert-recorded-commands (commands)
+  (let ((cmds commands) cmd name ret)
+    (while cmds
+      (setq cmd (car cmds))
+      (setq name (car cmd))
+      (cond
+       ;; skip next commands
+       ((memq name '(start-recording-commands
+                    universal-argument universal-argument-other-key
+                    digit-argument
+                    isearch-forward isearch-backward
+                    isearch-forward-regexp isearch-backward-regexp
+                    isearch-printing-char isearch-other-control-char
+                    isearch-repeat-forward isearch-repeat-backward
+                    isearch-delete-char isearch-del-char
+                    isearch-yank-word-or-char isearch-yank-char
+                    isearch-yank-line isearch-yank-kill
+                    isearch-edit-string 
+                    isearch-query-replace isearch-query-replace-regexp
+                    isearch-ring-advance isearch-ring-retreat
+                    isearch-complete 
+                    isearch-toggle-input-method
+                    isearch-toggle-specified-input-method
+                    isearch-abort
+                    )))
+
+       ((eq name 'isearch-exit) 
+       (setq ret (append (convert-isearch-cmds (cdr cmd)) ret)))
+
+       ((eq name 'self-insert-command)
+       (let* ((prev-cmd (if ret (car ret) nil))
+              (prev-cmd-name (if prev-cmd (car prev-cmd) nil)))
+         (if (eq prev-cmd-name 'insert)
+           (setq ret (cons
+                      `(insert ,(concat (nth 1 prev-cmd) (string (nth 1 cmd))))
+                      (cdr ret)))
+         (push `(insert ,(string (nth 1 cmd))) ret))))
+
+       (t (push cmd ret)))
+
+      (setq cmds (cdr cmds)))
+    ret))
+
+(defun convert-isearch-cmds (isearch-state-stack)
+  (let ((isch-state-list (reverse isearch-state-stack))
+       (result '()))
+    (when (and isch-state-list
+              (cdr isch-state-list))
+      (let ((prev-state nil)
+           (this-state (car isch-state-list))
+           isch-string regexp forward)
+       (setq isch-state-list (cdr isch-state-list))     
+       (while isch-state-list
+         (setq prev-state this-state)
+         (setq this-state (car isch-state-list))
+         (setq isch-state-list (cdr isch-state-list))
+         (cond 
+          ; 1. Ignore no-movement steps.
+          ((eq (isearch-point-state this-state)
+               (isearch-point-state prev-state)))
+
+          ; 2. The point moved but barrier didn't, real incremental
+          ; search happened.  We only record the last one of such a 
+          ; series, in 3.  Here we just pass.
+          ((eq (isearch-barrier-state this-state)
+               (isearch-barrier-state prev-state)))
+
+          ; 3. Other cases: repeated search or search with modified
+          ; string.  We record the previous-state, this-state may get
+          ; ignored on next loop, in 1 or 2.
+          (t
+           (setq regexp (isearch-pop-fun-state prev-state))
+           (setq forward (isearch-forward-state prev-state))
+           (setq isch-string (isearch-string-state prev-state))
+           (setq result 
+                 (append result 
+                         `(,(make-search-command
+                             forward regexp isch-string)))))))
+ 
+       ;; Recording the last state is always necessary.
+       (setq result 
+             (append result
+                     `(,(make-search-command
+                          (isearch-forward-state this-state)
+                          (isearch-pop-fun-state this-state) ; regexp flag
+                          (isearch-string-state this-state)))))))
+    result))
+        
+(defun make-search-command (forward regexp search-string)
+  "Return a quoted search command. The args sets the search type and
string."
+  (cond (forward
+        (cond (regexp
+               `(search-forward-regexp ,search-string))
+              (t
+               `(search-forward ,search-string))))
+       (t
+        (cond (regexp
+               `(search-backward-regexp ,search-string))
+              (t
+               `(search-backward ,search-string))))))
+

-- 
   regards,
yzhh





reply via email to

[Prev in Thread] Current Thread [Next in Thread]