├── 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 | [](http://melpa.org/#/elmacro)
2 | [](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 "^#