Initial commit

This commit is contained in:
Case Duckworth 2021-09-06 22:36:49 -05:00
commit 902b4c5aa8
2 changed files with 217 additions and 0 deletions

90
README.md Normal file
View File

@ -0,0 +1,90 @@
# define-repeat-map.el
Easily define repeat-maps
Emacs 28 comes built-in with repeat.el (which see), which allows users to
define their own maps to repeat common commands easily. This package
attempts to make the definition of those maps a one-sexp affair, through the
macro `define-repeat-map'. See its docstring for details.
## The problem
Most of the time, defining a repeat-map and adding commands to keys is a
multi-step affair, involving something like the following:
```lisp
(defvar other-window-repeat-map (make-sparse-keymap)
"A map for repeating `other-window' keys.")
(define-key other-window-repeat-map "o" #'other-window)
(put 'other-window 'repeat-map 'other-window-repeat-map))
```
With more keys, it gets more complex; there has to be a better way!
## Enter `define-repeat-map`
With this macro, you can easily define these maps, and bind keys to them, using
only one form. The code above looks like this with `define-repeat-map`:
```lisp
(define-repeat-map other-window
("o" other-window))
```
Which isn't *so* much more convenient, but the convenience compounds with more
complex maps. For example, here's a snippet from my `init.el`,
pre-`define-repeat-map`:
```lisp
(defalias 'forward-word-with-case 'forward-word
"Alias for `forward-word' for use in `case-repeat-map'.")
(defalias 'backward-word-with-case 'backward-word
"Alias for `backward-word for use in `case-repeat-map'.")
(defvar case-repeat-map
(let ((map (make-sparse-keymap)))
(define-key map "c" #'capitalize-word)
(define-key map "u" #'upcase-word)
(define-key map "l" #'downcase-word)
;; movement
(define-key map "f" #'forward-word-with-case)
(define-key map "b" #'backward-word-with-case)
map)
"A map to repeat word-casing commands. For use with `repeat-mode'.")
(dolist (command '(capitalize-word
capitalize-dwim
upcase-word
upcase-dwim
downcase-word
downcase-dwim
forward-word-with-case
backward-word-with-case))
(put command 'repeat-map 'case-repeat-map))
```
And here it is using this macro:
```lisp
(define-repeat-map case
("c" capitalize-word
"u" upcase-word
"l" downcase-word)
(:continue "f" forward-word
"b" backward-word)
(:enter downcase-dwim
upcase-dwim
capitalize-dwim))
```
Much easier, in this author's humble opinion.
## Similar packages
I've found one other package that tries to simplify repeat-map definition, but
it's not quite as flexible as this. In fact, that's why I wrote
`define-repeat-map`. But here's the other:
- [repeaters.el](https://github.com/mmarshall540/repeaters)

127
define-repeat-map.el Normal file
View File

@ -0,0 +1,127 @@
;;; define-repeat-map.el --- Easy-define repeat-maps -*- lexical-binding: t -*-
;; Copyright (C) 2021 Case Duckworth
;; Author: Case Duckworth <acdw@acdw.net>
;; Keywords: convenience
;; URL:
;;; License:
;; Everyone is permitted to do whatever with this software, without
;; limitation. This software comes without any warranty whatsoever,
;; but with two pieces of advice:
;; - Don't hurt yourself.
;; - Make good choices.
;;; Commentary:
;; Emacs 28 comes built-in with repeat.el (which see), which allows users to
;; define their own maps to repeat common commands easily. This package
;; attempts to make the definition of those maps a one-sexp affair, through the
;; macro `define-repeat-map'. See its docstring for details.
;;; Code:
(defun define-repeat-map--make-alias (cmd map)
(intern (concat (symbol-name cmd) "|"
(symbol-name map))))
(defun define-repeat-map--map-commands (map fn args)
(let (result)
(dolist (arg args)
(unless (stringp arg)
(push (funcall fn arg) result)))
(nreverse result)))
(defun define-repeat-map--define-keys (map fn args)
(unless (zerop (mod (length args) 2))
(error "Wrong number of args"))
(let (result)
(while args
(let ((key (pop args))
(cmd (funcall fn (pop args))))
(push `(define-key ,map (kbd ,key) #',cmd) result)))
(nreverse result)))
;;;###autoload
(defmacro define-repeat-map (name &rest args)
"Define a repeat-map, NAME-repeat-map, and bind keys to it.
Each ARG is a list of lists containing keybind definitions of
the form (KEY DEFINITION) KEY is anything `kbd' can recognize,
and DEFINITION is passed directly to `define-key'.
Optionally, the car of an arglist can contain the following
symbols, which changes the behavior of the key definitions in the
rest of the list:
:enter - Provided commands can enter the repeat-map, but aren't
bound in the map. They need to be bound elsewhere, however.
:exit - Keys are bound in the repeat-map, but can't enter the
map. Their invocation exits the repeat-map.
:continue - Keys are bound in the repeat-map, but can't enter the
map. However, their invocations keep the repeat-map active."
(declare (indent 1))
(let ((result)
(map (intern (concat (symbol-name name) "-repeat-map"))))
;; Create the keymap
(push `(defvar ,map (make-sparse-keymap)
"Defined by `define-repeat-map'.")
result)
;; Iterate through ARGS
(dolist (arg args)
(pcase (car arg)
(:enter
;; Add the map to the commands' repeat-map property.
(push `(progn
,@(define-repeat-map--map-commands
`,map
(lambda (cmd) `(put ',cmd 'repeat-map ',map))
(cdr arg)))
result))
(:exit
;; Bind the commands in the map.
(push `(progn
,@(define-repeat-map--define-keys
`,map #'identity (cdr arg)))
result))
(:continue
;; Make an alias for each command, and process that alias like the
;; default, below.
(push `(progn
,@(define-repeat-map--define-keys
`,map
(lambda (cmd) (define-repeat-map--make-alias cmd map))
(cdr arg))
,@(define-repeat-map--map-commands
`,map
(lambda (cmd)
(let ((alias (define-repeat-map--make-alias cmd map)))
`(progn
(defalias ',alias ',cmd
"Defined by `define-repeat-map'.")
(put ',alias
'repeat-map ',map))))
(cdr arg)))
result))
(_
;; Default: bind the commands in the map, and add the map to the
;; commands' repeat-map property.
(push `(progn
,@(define-repeat-map--define-keys `,map #'identity arg)
,@(define-repeat-map--map-commands
`,map
(lambda (cmd) `(put ',cmd 'repeat-map ',map))
arg))
result))))
`(with-eval-after-load 'repeat
,@(nreverse result))))
;;; define-repeat-map.el ends here