summaryrefslogtreecommitdiff
path: root/modules/keyboard-macros.el
blob: 63b4692708cc3e344b7aef2b262b17ce08437664 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
;;; keyboard-macros.el --- Keyboard Macro Management  -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; This library provides a simple, end-user–focused interface for
;; creating, naming, saving, and replaying keyboard macros in Emacs.
;; All commands are built on top of the built-in =kmacro= machinery, but
;; add a lightweight workflow and persistence across sessions.
;;
;; Workflow:
;;
;; 1. Start recording with C-F3 (or M-x cj/kbd-macro-start-or-end)
;;    This toggles macro recording on.
;;    Now you can perform all the edits you want recorded in the macro.
;;
;; 2. Stop recording with C-F3 (or M-x cj/kbd-macro-start-or-end)
;;    This stops recording and the macro becomes the "last keyboard macro."
;;
;; 3. Replay your macro <f3> (or M-x call-last-kbd-macro)
;;
;; 4. Name your macro with M-<F3>
;;    You will be prompted for a short name (e.g. =align-comments=,
;;    =cleanup-trail-spaces=).  This name is how you'll refer to it later.
;;
;; 5. Recall that macro later with M-x [the name you gave the macro]
;;
;; 6. View all your saved macros with s-<f3> (super-f3)
;;
;; 7. All macros are lazy-loaded when needed.
;;    When you first use keyboard macro functionality or open the macros file,
;;    all your previously saved macros are loaded into the current session.
;;
;;; Code:

(require 'user-constants) ;; definitions of org-dir and macros-file

(defvar cj/macros-loaded nil
  "Whether saved keyboard macros have been loaded from file.")

(defun cj/ensure-macros-loaded ()
  "Load keyboard macros from file if not already loaded.
This function is idempotent and fast when macros are already loaded."
  (when (and (not cj/macros-loaded)
             (file-exists-p macros-file))
    (condition-case err
        (progn
          (load macros-file)
          (setq cj/macros-loaded t))
      (error
	   (message "Error loading keyboard macros file: %s"
                (error-message-string err))))))

(defun ensure-macros-file (file)
  "Ensure FILE exists and its first line enables lexical-binding."
  (unless (file-exists-p file)
    (with-temp-file file
      (insert ";;; -*- lexical-binding: t -*-\n"))))

(defun cj/kbd-macro-start-or-end ()
  "Toggle start/end of keyboard macro definition."
  (interactive)
  ;; Lazy load macros on first use
  (cj/ensure-macros-loaded)
  (if defining-kbd-macro
      (end-kbd-macro)
    (start-kbd-macro nil)))

(defun cj/save-maybe-edit-macro (name)
  "Save last macro as NAME in `macros-file'; edit if prefix arg."
  (interactive "SName of macro: ")
  (kmacro-name-last-macro name)
  (ensure-macros-file macros-file)
  (find-file macros-file)
  (goto-char (point-max))
  (newline)
  (insert-kbd-macro name)
  (newline)
  (save-buffer)
  (switch-to-buffer (other-buffer (current-buffer) 1))
  (when current-prefix-arg
    (find-file macros-file)
    (goto-char (point-max)))
  name)

(defun cj/open-macros-file ()
  "Open the keyboard macros file."
  (interactive)
  ;; Ensure macros are loaded before opening the file
  (cj/ensure-macros-loaded)
  (ensure-macros-file macros-file)
  (find-file macros-file))

;; Set up key bindings
(global-set-key (kbd "C-<f3>") #'cj/kbd-macro-start-or-end)
(global-set-key (kbd "<f3>")   #'call-last-kbd-macro)
(global-set-key (kbd "M-<f3>") #'cj/save-maybe-edit-macro)
(global-set-key (kbd "s-<f3>") #'cj/open-macros-file)

;; Add hook to save any unnamed macros on exit if desired
(defun cj/save-last-kbd-macro-on-exit ()
  "Save the last keyboard macro before exiting Emacs if it's not saved."
  (when (and last-kbd-macro (not (kmacro-name-last-macro)))
    (when (y-or-n-p "Save last keyboard macro before exiting? ")
      (call-interactively #'cj/save-maybe-edit-macro))))

(add-hook 'kill-emacs-hook #'cj/save-last-kbd-macro-on-exit)

(provide 'keyboard-macros)
;;; keyboard-macros.el ends here