├── CHANGELOG.md ├── README.md └── elmacro.el /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.1 (2019-08-23) 4 | 5 | - Use lexical binding. 6 | - Fix typos. 7 | 8 | ## 1.1.0 (2017-03-12) 9 | 10 | - Handle `self-insert-command` interceptors. 11 | - Add changelog 12 | 13 | ## 1.0.1 (2016-04-10) 14 | 15 | - Reintroduce defun name for elmacro-show-last-macro 16 | 17 | ## 1.0.0 (2016-02-10) 18 | 19 | - Big refactoring of code 20 | - Implement processors 21 | - Add inserts concatenator processor 22 | - Add elmacro-show-last-commands-default variable 23 | - Update dependencies 24 | - Ensure elmacro is turned on before using it 25 | - Improve documentation 26 | 27 | ## 0.3.0 (2014-09-11) 28 | 29 | - New interactive command `elmacro-clear-recorded-commands'. 30 | - Correct bug with symbols quoting (#11). 31 | - Filter unwanted commands using a regexp. 32 | - README improvements. 33 | 34 | ## 0.2.0 (2014-07-07) 35 | 36 | - interactive function `elmacro-show-last-commands` 37 | - add `#`, `#` and `#` support 38 | 39 | ## 0.1.0 (2014-28-08) 40 | 41 | - interactive function `elmacro-show-last-macro` 42 | - interactive function `elmacro-show-lossage` 43 | - option `elmacro-filters` 44 | - option `elmacro-custom-recorded-functions` 45 | - option `elmacro-concatenate-multiple-inserts` 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](http://melpa.org/packages/elmacro-badge.svg)](http://melpa.org/#/elmacro) 2 | [![MELPA Stable](http://stable.melpa.org/packages/elmacro-badge.svg)](http://stable.melpa.org/#/elmacro) 3 | 4 | # elmacro 5 | 6 | Shows keyboard macros or latest interactive commands as emacs lisp. 7 | 8 | ## Examples 9 | 10 | ### upcase-last-word 11 | 12 | Say you have the following text: 13 | 14 | violets are blue 15 | roses are red 16 | 17 | With the cursor somewhere on the first line. Press the following keys: 18 | 19 | F3 C-e M-b M-u C-a C-n F4 20 | 21 | Then doing M-x elmacro-show-last-macro upcase-last-word RET produces a buffer with: 22 | 23 | ``` emacs-lisp 24 | (defun upcase-last-word () 25 | (interactive) 26 | (move-end-of-line 1) 27 | (backward-word 1) 28 | (upcase-word 1) 29 | (move-beginning-of-line 1) 30 | (next-line 1 1)) 31 | ``` 32 | 33 | You can now do M-x eval-buffer followed by M-x upcase-last-word 34 | or call it from your emacs lisp code. 35 | 36 | ## Table of Contents 37 | 38 | * [Installation](#installation) 39 | * [Commands](#commands) 40 | * [elmacro-show-last-macro](#elmacro-show-last-macro) 41 | * [elmacro-show-last-commands](#elmacro-show-last-commands) 42 | * [elmacro-clear-command-history](#elmacro-clear-command-history) 43 | * [Customization](#customization) 44 | * [elmacro-processors](#elmacro-processors) 45 | * [elmacro-show-last-commands-default](#elmacro-show-last-commands-default) 46 | * [elmacro-additional-recorded-functions](#elmacro-additional-recorded-functions) 47 | * [elmacro-unwanted-commands-regexps](#elmacro-unwanted-commands-regexps) 48 | * [elmacro-special-objects](#elmacro-special-objects) 49 | * [elmacro-debug](#elmacro-debug) 50 | * [Processors](#processors) 51 | * [elmacro-processor-filter-unwanted](#elmacro-processor-filter-unwanted) 52 | * [elmacro-processor-prettify-inserts](#elmacro-processor-prettify-inserts) 53 | * [elmacro-processor-concatenate-inserts](#elmacro-processor-concatenate-inserts) 54 | * [elmacro-processor-handle-special-objects](#elmacro-processor-handle-special-objects) 55 | * [FAQ](#faq) 56 | * [org-mode, smartparens, etc](#org-mode-smartparens-etc) 57 | * [Mouse events](#mouse-events) 58 | * [Contributions welcome!](#contributions-welcome) 59 | 60 | ## Installation 61 | 62 | The recommended way to install elmacro is through [MELPA](https://github.com/milkypostman/melpa). 63 | 64 | Otherwise, simply add `elmacro.el` to your load-path and then `(require 'elmacro)`. 65 | 66 | To enable elmacro, do M-x elmacro-mode or enable it from your config file like this: 67 | 68 | ``` emacs-lisp 69 | (elmacro-mode) 70 | ``` 71 | 72 | ## Commands 73 | 74 | ### elmacro-show-last-macro 75 | 76 | M-x elmacro-show-last-macro shows your latest macro as emacs lisp. 77 | 78 | In order to use this, you must first record a 79 | [keyboard macro](https://www.gnu.org/software/emacs/manual/html_node/emacs/Keyboard-Macros.html). 80 | Then, when you do M-x elmacro-show-last-macro it will ask 81 | you for a defun name and show the latest macro as emacs lisp. 82 | 83 | ### elmacro-show-last-commands 84 | 85 | M-x elmacro-show-last-commands shows your latest emacs activity as emacs lisp. 86 | 87 | This is basically a better version of `kmacro-edit-lossage`. 88 | 89 | The default number of commands shown is modifiable in variable 90 | [elmacro-show-last-commands-default](#elmacro-show-last-commands-default). 91 | 92 | You can also modify this number by using a numeric prefix argument or 93 | by using the universal argument, in which case it’ll ask for how many 94 | in the minibuffer. 95 | 96 | ### elmacro-clear-command-history 97 | 98 | Clears the list of recorded commands. 99 | 100 | ## Customization 101 | 102 | ### elmacro-processors 103 | 104 | _Default value:_ `(elmacro-processor-filter-unwanted elmacro-processor-prettify-inserts elmacro-processor-concatenate-inserts elmacro-processor-handle-special-objects)` 105 | 106 | List of [processors](#processors) functions used to improve code listing. 107 | 108 | Each function is passed the list of commands meant to be displayed and 109 | is expected to return a modified list of commands. 110 | 111 | ### elmacro-show-last-commands-default 112 | 113 | _Default value:_ `30` 114 | 115 | Number of commands shown by default in [elmacro-show-last-commands](#elmacro-show-last-commands). 116 | 117 | ### elmacro-additional-recorded-functions 118 | 119 | _Default value:_ `(copy-file copy-directory rename-file delete-file make-directory)` 120 | 121 | List of non-interactive functions that you also want to be recorded. 122 | 123 | For example, `dired-copy-file` (`C` key in dired) 124 | doesn't reads its arguments as an interactive specification, and 125 | thus the file name is never stored. 126 | 127 | ### elmacro-unwanted-commands-regexps 128 | 129 | _Default value:_ `("^(ido.*)$" "^(smex)$")` 130 | 131 | Regexps used to filter unwanted commands. 132 | 133 | ### elmacro-special-objects 134 | 135 | _Default value:_ 136 | 137 | ``` emacs-lisp 138 | '(("#" ",(elmacro-get-frame \"\\1\")") 139 | ("#]+>" ",(elmacro-get-window \\1)") 140 | ("#]+\\)>" ",(get-buffer \"\\1\")")) 141 | ``` 142 | 143 | List of `(regexp replacement)` for special objects. 144 | 145 | This will be used as arguments for `replace-regexp-in-string`. 146 | 147 | ### elmacro-debug 148 | 149 | _Default value:_ `nil` 150 | 151 | Set to true to turn debugging in buffer `* elmacro debug *`. 152 | 153 | ## Processors 154 | 155 | The way elmacro processes commands can be modified using *processors*. 156 | 157 | A processor is an emacs lisp function that takes a list the commands 158 | meant to be displayed and is expected to return a modified list of 159 | commands. 160 | 161 | For example, a simple processor that filters anything you insert in a buffer: 162 | 163 | ``` emacs-lisp 164 | (defun filter-insert-processor (commands) 165 | (--remove (eq 'insert (car it)) commands)) 166 | ``` 167 | 168 | ### elmacro-processor-filter-unwanted 169 | 170 | Remove unwanted commands using [elmacro-unwanted-commands-regexps](#elmacro-unwanted-commands-regexps). 171 | 172 | ### elmacro-processor-prettify-inserts 173 | 174 | Transform all occurences of `self-insert-command` into `insert`. 175 | This filter should be not be enabled with packages that 176 | advice `self-insert-command`, see the [FAQ](#org-mode-smartparens-etc) for more information. 177 | 178 | Before: 179 | 180 | ``` emacs-lisp 181 | (setq last-command-event 97) 182 | (self-insert-command 1) 183 | (setq last-command-event 98) 184 | (self-insert-command 1) 185 | (setq last-command-event 99) 186 | (self-insert-command 3) 187 | ``` 188 | 189 | After: 190 | 191 | ``` emacs-lisp 192 | (insert "a") 193 | (insert "b") 194 | (insert "ccc") 195 | ``` 196 | 197 | ### elmacro-processor-concatenate-inserts 198 | 199 | Concatenate multiple text insertion together. 200 | 201 | Before: 202 | 203 | ``` emacs-lisp 204 | (insert "a") 205 | (insert "b") 206 | (insert "c") 207 | ``` 208 | 209 | After: 210 | 211 | ``` emacs-lisp 212 | (insert "abc") 213 | ``` 214 | 215 | ### elmacro-processor-handle-special-objects 216 | 217 | Turn special objects into usable objects using [elmacro-special-objects](#elmacro-special-objects). 218 | 219 | ## FAQ 220 | 221 | ### org-mode, smartparens, etc 222 | 223 | Normally `elmacro` works reasonably well with these, but if you want to ensure the most accurate experience you should 224 | disable the [elmacro-processor-prettify-inserts](#elmacro-processor-prettify-inserts) processor (see [elmacro-processors](#elmacro-processors)). 225 | 226 | This is necessary because these packages usually advice `self-insert-command`, and by transforming 227 | it into an `insert` the advice does not run and we miss functionnality. 228 | 229 | ### Mouse events 230 | 231 | A nice addition to normal macros is that mouse events (clicks / scroll) 232 | are also recorded and elmacro can figure which emacs window / frame was the target. 233 | 234 | For example, by default clicking in a window will generate code like: 235 | 236 | ``` emacs-lisp 237 | (mouse-set-point '(mouse-1 (# 913 (90 . 286) 185432429 nil 913 (10 . 15) nil (90 . 1) (9 . 19)))) 238 | ``` 239 | 240 | We see that the `<#window 75 on foo.el>` part is not very useful. 241 | Thanks to the processor [elmacro-processor-handle-special-objects](#elmacro-processor-handle-special-objects), the following code is generated 242 | instead (`elmacro-get-window` is a helper that returns the correct emacs window object): 243 | 244 | ``` emacs-lisp 245 | (mouse-set-point `(mouse-1 (,(elmacro-get-window 75) 913 (90 . 286) 185432429 nil 913 (10 . 15) nil (90 . 1) (9 . 19)))) 246 | ``` 247 | 248 | ## Contributions welcome! 249 | 250 | Either as suggestions or as pull requests by opening tickets on the 251 | [issue tracker](https://github.com/Silex/elmacro/issues). 252 | -------------------------------------------------------------------------------- /elmacro.el: -------------------------------------------------------------------------------- 1 | ;;; elmacro.el --- Convert keyboard macros to emacs lisp -*- lexical-binding: t -*- 2 | 3 | ;; Author: Philippe Vaucher 4 | ;; URL: https://github.com/Silex/elmacro 5 | ;; Keywords: macro, elisp, convenience 6 | ;; Version: 1.1.1 7 | ;; Package-Requires: ((s "1.11.0") (dash "2.13.0")) 8 | 9 | ;; This file is NOT part of GNU Emacs. 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;;; Code: 27 | 28 | (require 's) 29 | (require 'dash) 30 | 31 | (defgroup elmacro nil 32 | "Show macros as Emacs Lisp." 33 | :group 'keyboard 34 | :group 'convenience) 35 | 36 | (defvar elmacro-command-history '() 37 | "Where elmacro process commands from variable `command-history'.") 38 | 39 | (defcustom elmacro-processors '(elmacro-processor-filter-unwanted 40 | elmacro-processor-prettify-inserts 41 | elmacro-processor-concatenate-inserts 42 | elmacro-processor-handle-special-objects) 43 | "List of processors functions used to improve code listing. 44 | 45 | Each function is passed the list of commands meant to be displayed and 46 | is expected to return a modified list of commands." 47 | :group 'elmacro 48 | :type '(repeat symbol)) 49 | 50 | (defcustom elmacro-show-last-commands-default 30 51 | "Number of commands shown by default in `elmacro-show-last-commands'." 52 | :group 'elmacro 53 | :type 'integer) 54 | 55 | (defcustom elmacro-additional-recorded-functions '(copy-file 56 | copy-directory 57 | rename-file 58 | delete-file 59 | make-directory) 60 | "List of non-interactive functions that you also want to be recorded. 61 | 62 | For example, `dired-copy-file' (the C key in dired) doesn't reads its 63 | arguments as an interactive specification, and thus the file name is 64 | never stored." 65 | :group 'elmacro 66 | :type '(repeat symbol)) 67 | 68 | (defcustom elmacro-unwanted-commands-regexps '("^(ido.*)$" "^(smex)$" "^(amx)$" "^(file-notify-.*)$") 69 | "Regexps used to filter unwanted commands." 70 | :group 'elmacro 71 | :type '(repeat regexp)) 72 | 73 | (defcustom elmacro-special-objects '(("#" ",(elmacro-get-frame \"\\1\")") 74 | ("#" ",(elmacro-get-window \\1)") 75 | ("#" ",(get-buffer \"\\1\")")) 76 | "List of (regexp replacement) for special objects. 77 | 78 | This will be used as arguments for `replace-regexp-in-string'." 79 | :group 'elmacro 80 | :type '(repeat (list regexp string))) 81 | 82 | (defcustom elmacro-debug nil 83 | "Set to true to turn debugging in buffer \"* elmacro debug *\"." 84 | :group 'elmacro 85 | :type 'boolean) 86 | 87 | (defun elmacro-process-commands (history) 88 | "Apply `elmacro-processors' to HISTORY." 89 | (let ((commands (reverse history))) 90 | (--each elmacro-processors 91 | (setq commands (funcall it commands))) 92 | commands)) 93 | 94 | (defvar pp-escape-newlines) 95 | 96 | (defun elmacro-pp-to-string (object) 97 | "Like `pp-to-string', but make sure all options are set like desired. 98 | 99 | Also handles nil as parameter for defuns." 100 | (let ((pp-escape-newlines t) 101 | (print-quoted t) 102 | (print-length nil) 103 | (print-level nil)) 104 | (replace-regexp-in-string "\\((defun +[^ ]+\\) +nil" "\\1 ()" (pp-to-string object)))) 105 | 106 | (defun elmacro-processor-filter-unwanted (commands) 107 | "Remove unwanted commands using `elmacro-unwanted-commands-regexps'." 108 | (--remove (let ((str (elmacro-pp-to-string it))) 109 | (--any? (s-matches? it str) elmacro-unwanted-commands-regexps)) 110 | commands)) 111 | 112 | (defun elmacro-processor-prettify-inserts (commands) 113 | "Transform all occurences of `self-insert-command' into `insert'." 114 | (let (result) 115 | (--each commands 116 | (-let (((previous-command previous-arg1 previous-arg2) (car result)) 117 | ((current-command current-arg) it)) 118 | (if (and (eq 'setq previous-command) 119 | (eq 'last-command-event previous-arg1) 120 | (eq 'self-insert-command current-command)) 121 | (setcar result `(insert ,(make-string current-arg previous-arg2))) 122 | (!cons it result)))) 123 | (reverse result))) 124 | 125 | (defun elmacro-processor-concatenate-inserts (commands) 126 | "Concatenate multiple inserts together." 127 | (let (result) 128 | (--each commands 129 | (-let (((previous-command previous-args) (car result)) 130 | ((current-command current-args) it)) 131 | (if (and (eq 'insert current-command) (eq 'insert previous-command)) 132 | (setcar result `(insert ,(concat previous-args current-args))) 133 | (!cons it result)))) 134 | (reverse result))) 135 | 136 | (defun elmacro-processor-handle-special-objects (commands) 137 | "Turn special objects into usable objects." 138 | (--map (let ((str (elmacro-pp-to-string it))) 139 | (--each elmacro-special-objects 140 | (-let (((regex rep) it)) 141 | (setq str (replace-regexp-in-string regex rep str)))) 142 | (condition-case nil 143 | (car (read-from-string (s-replace "'(" "`(" str))) 144 | (error `(ignore ,str)))) 145 | commands)) 146 | 147 | (defun elmacro-get-frame (name) 148 | "Return the frame named NAME." 149 | (--first (s-matches? (format "^#$" name) (elmacro-pp-to-string it)) 150 | (frame-list))) 151 | 152 | (defun elmacro-get-window (n) 153 | "Return the window numbered N." 154 | (--first (s-matches? (format "^#