;;; elmacro.el --- Convert keyboard macros to emacs lisp -*- lexical-binding: t -*- ;; Author: Philippe Vaucher ;; URL: https://github.com/Silex/elmacro ;; Package-Version: 1.1.1 ;; Package-Commit: 5bf9ba6009226b95e5ba0f50489ccced475753e3 ;; Keywords: macro, elisp, convenience ;; Version: 1.1.1 ;; Package-Requires: ((s "1.11.0") (dash "2.13.0")) ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;;; Code: (require 's) (require 'dash) (defgroup elmacro nil "Show macros as emacs lisp." :group 'keyboard :group 'convenience) (defvar elmacro-command-history '() "Where elmacro process commands from variable `command-history'.") (defcustom elmacro-processors '(elmacro-processor-filter-unwanted elmacro-processor-prettify-inserts elmacro-processor-concatenate-inserts elmacro-processor-handle-special-objects) "List of processors functions used to improve code listing. Each function is passed the list of commands meant to be displayed and is expected to return a modified list of commands." :group 'elmacro :type '(repeat symbol)) (defcustom elmacro-show-last-commands-default 30 "Number of commands shown by default in `elmacro-show-last-commands'." :group 'elmacro :type 'integer) (defcustom elmacro-additional-recorded-functions '(copy-file copy-directory rename-file delete-file make-directory) "List of non-interactive functions that you also want to be recorded. For example, `dired-copy-file' (the C key in dired) doesn't reads its arguments as an interactive specification, and thus the file name is never stored." :group 'elmacro :type '(repeat symbol)) (defcustom elmacro-unwanted-commands-regexps '("^(ido.*)$" "^(smex)$") "Regexps used to filter unwanted commands." :group 'elmacro :type '(repeat regexp)) (defcustom elmacro-special-objects '(("#" ",(elmacro-get-frame \"\\1\")") ("#" ",(elmacro-get-window \\1)") ("#" ",(get-buffer \"\\1\")")) "List of (regexp replacement) for special objects. This will be used as arguments for `replace-regexp-in-string'." :group 'elmacro :type '(repeat (list regexp string))) (defcustom elmacro-debug nil "Set to true to turn debugging in buffer \"* elmacro debug *\"." :group 'elmacro :type 'boolean) (defun elmacro-process-commands (history) "Apply `elmacro-processors' to HISTORY." (let ((commands (reverse history))) (--each elmacro-processors (setq commands (funcall it commands))) commands)) (defun elmacro-pp-to-string (object) "Like `pp-to-string', but make sure all options are set like desired. Also handles nil as parameter for defuns." (let ((pp-escape-newlines t) (print-quoted t) (print-length nil) (print-level nil)) (replace-regexp-in-string "\\((defun +[^ ]+\\) +nil" "\\1 ()" (pp-to-string object)))) (defun elmacro-processor-filter-unwanted (commands) "Remove unwanted commands using `elmacro-unwanted-commands-regexps'" (--remove (let ((str (elmacro-pp-to-string it))) (--any? (s-matches? it str) elmacro-unwanted-commands-regexps)) commands)) (defun elmacro-processor-prettify-inserts (commands) "Transform all occurences of `self-insert-command' into `insert'." (let (result) (--each commands (-let (((previous-command previous-arg1 previous-arg2) (car result)) ((current-command current-arg) it)) (if (and (eq 'setq previous-command) (eq 'last-command-event previous-arg1) (eq 'self-insert-command current-command)) (setcar result `(insert ,(make-string current-arg previous-arg2))) (!cons it result)))) (reverse result))) (defun elmacro-processor-concatenate-inserts (commands) "Concatenate multiple inserts together" (let (result) (--each commands (-let (((previous-command previous-args) (car result)) ((current-command current-args) it)) (if (and (eq 'insert current-command) (eq 'insert previous-command)) (setcar result `(insert ,(concat previous-args current-args))) (!cons it result)))) (reverse result))) (defun elmacro-processor-handle-special-objects (commands) "Turn special objects into usable objects." (--map (let ((str (elmacro-pp-to-string it))) (--each elmacro-special-objects (-let (((regex rep) it)) (setq str (replace-regexp-in-string regex rep str)))) (condition-case nil (car (read-from-string (s-replace "'(" "`(" str))) (error `(ignore ,str)))) commands)) (defun elmacro-get-frame (name) "Return the frame named NAME." (--first (s-matches? (format "^#$" name) (elmacro-pp-to-string it)) (frame-list))) (defun elmacro-get-window (n) "Return the window numbered N." (--first (s-matches? (format "^#