daemon.de

Emacs: comfortable macro recording

;; var must be global so it can be shared across functions (setq my-macro-name “last-macro”)

;; ignore some certain events (add-to-list ’elmacro-unwanted-commands-regexps "^(mouse.*)$") (add-to-list ’elmacro-unwanted-commands-regexps "^(my-start-or-stop-macro)$")

;; called when saving a macro (defun my-get-macro-name() “Ask for a macro name, check for duplicates. If the given name is alreadydefined, ask again (and again until unique). If a buffer with the given nameexists, kill it (that is, the buffer is there but has not been saved or evaluatedyet). Return the name as string." (interactive) (let ((done nil) (name nil) (mbuf nil) (err ”")) (while (not done) (setq name (read-string (format "%s - enter macro name (last-macro): “ err) nil nil “last-macro”)) (if (fboundp (intern name)) (setq err (format “macro ‘%s is already defined” name)) (setq mbuf (format ”* elmacro - %s *" name)) (if (get-buffer mbuf) (with-current-buffer mbuf (kill-buffer mbuf))) (setq done t))) name))

;; interactive macro prompt with completion (defun my-get-exec-macro-name() “Ask for a macro name to be executed” (interactive) (let ((macros ()) (N 1) (S nil) (T "")) (dolist (entry (cdr (assoc my-macro-file load-history ))) (setq S (cdr entry)) (setq T (symbol-name S)) (push (list T N) macros) (setq N (1+ N))) (completing-read “enter macro name: “ macros nil t nil)))

;; the heart of my elmacro stuff: starts macro recording or;; stops it if emacs is already recording. This way I can ;; use 1 keybinding for start+stop (defun my-start-or-stop-macro() “start macro or stop if started” (interactive) (if (eq defining-kbd-macro nil) (progn (elmacro-clear-command-history) (start-kbd-macro nil) (message “Recording macro. Finish with <shift-F6> …")) (progn (call-interactively ’end-kbd-macro) (setq my-macro-name (my-get-macro-name)) (elmacro-show-last-macro my-macro-name) (message “Recording done. Execute with <C-F6>, save or <C-x C-e> buffer…"))))

;; better than the default function(defun my-exec-last-macro(&optional ARG) “execute last macro (or ARG, if given) repeatedly after every <ret>, abort withC-g or q, and repeat until EOF after pressing a. If macro defun is known(i.e. because you evaluated the elmacro buffer containing the generateddefun), it will be executed. Otherwise the last kbd-macro will be executed." (interactive) (let ((melm-count 0) (melm-all nil) (melm-abort nil) (melm-beg (eobp)) (melm-code (or ARG my-macro-name))) (if (eobp) (if (yes-or-no-p "(point) is at end of buffer. Jump to top?") (goto-char (point-min)))) (while (and (not melm-abort) (not (eobp))) (when (not melm-all) (message (concat (format “Executing last macro ‘%s (%d). Keys:\n” melm-code melm-count) "<enter> repeat once\n” “a repeat until EOF\n” “e enter macro name to execute\n” "<C-g> or q abort ..\n “)) (setq K (read-event)) (cond ((or (eq K ‘return) (eq K ‘C-f6)) t) ((equal (char-to-string K) “q”) (setq melm-abort t)) ((equal (char-to-string K) “a”) (message “Repeating until EOF”)(setq melm-all t)) ((equal (char-to-string K) “e”) (setq my-macro-name (my-get-exec-macro-name))) (t (setq melm-abort t)))) (if (not melm-abort) (progn (if (fboundp (intern melm-code)) (call-interactively (intern melm-code)) (call-interactively ‘call-last-kbd-macro)) (setq melm-count (1+ melm-count))))) (if (and (eq melm-count 0) (eq (point) (point-max))) (message "(point) is at end of buffer, aborted”) (message (format “executed ‘%s %d times” melm-code melm-count)))))

;; I use my own macro file, my-lisp is defined somewhere else(setq my-macro-file (concat my-lisp "/macros.el”))

;; load if it exists(if (file-exists-p my-macro-file) (load-file my-macro-file))

;; store the macro defun generated by elmacro-mode to disk (defun my-macro-store() “store current macro to emacs config” (interactive) (copy-region-as-kill (point-min) (point-max)) (if (not (get-buffer “macros.el”)) (find-file my-macro-file)) (with-current-buffer “macros.el” (goto-char (point-max)) (newline) (insert ”;;") (newline) (insert (format ”;; elmacro added on %s” (current-time-string))) (newline) (yank) (newline) (save-buffer)) (switch-to-buffer nil) (delete-window))

;; add a repeating variant of the generated function, ;; evaluate and store both of them to disk (defun my-macro-gen-repeater-and-save() “generate repeater and save the defun’s Runs when (point)is at 0,0 of generated defun." (next-line) (goto-char (point-max)) (newline) (insert (format "(defun %s-repeat()\n” my-macro-name)) (insert " (interactive)\n") (insert (format " (my-exec-last-macro "%s"))\n" my-macro-name)) (newline) (eval-buffer) (my-macro-store))

;; so, always evaluate generated macro code, store it to disk and;; close the temp buffer(advice-add ’elmacro-show-defun :after ‘(lambda (&rest args) (my-macro-gen-repeater-and-save)))

;; workflow: shift-F6 … do things … shift-F6, enter a name, new;; buffer with macro defun appears. C-x C-e evals it. C-F6 (repeatedly);; executes it.(global-set-key (kbd "<f6>") ‘my-start-or-stop-macro) (global-set-key (kbd "<C-f6>") ‘my-exec-last-macro)

;; face used to indicate ongoing macro recording on the mode line(defface rec-face ‘((t (:background “red” :foreground “white” :weight bold))) “Flag macro recording in mode-line” :group ‘my-mode-line-faces)

;; custom modeline including recording marker(setq-default mode-line-format (list "%e" mode-line-front-space mode-line-mule-info mode-line-modified mode-line-remote " “ mode-line-buffer-identification ” “ mode-line-position ” (%m) “

           '(<span style="color: #8a2be2;">:eval</span> (propertize
                    (<span style="color: #0000ff;">if</span> (eq defining-kbd-macro t)
                        <span style="color: #ff0000;">"[REC]"</span>)
                    'face 'rec-face))
           
           mode-line-end-spaces))</pre>

#Emacs