[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/devil e52262afdd 01/49: Add Devil
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/devil e52262afdd 01/49: Add Devil |
Date: |
Mon, 15 May 2023 12:59:30 -0400 (EDT) |
branch: elpa/devil
commit e52262afdd963f75fa31a6b21d46216efc947b94
Author: Susam Pal <susam@susam.net>
Commit: Susam Pal <susam@susam.net>
Add Devil
---
CHANGES.md | 22 +++
LICENSE.md | 23 +++
README.md | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
devil.el | 385 +++++++++++++++++++++++++++++++++++++++++++++++
meta/Makefile | 9 ++
meta/README.md | 36 +++++
meta/example.el | 22 +++
meta/example.md | 27 ++++
meta/global.el | 4 +
meta/god.el | 4 +
meta/left.el | 6 +
meta/local.el | 4 +
meta/smiley.el | 46 ++++++
13 files changed, 1045 insertions(+)
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000000..cbf18a0541
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,22 @@
+Changelog
+=========
+
+0.1.0 (2023-05-07)
+------------------
+
+### Added
+
+- Devil global and local minor modes.
+- Default Devil key set to the comma (`,`).
+- Special key `, ,` to type a literal comma.
+- Special key `, SPC` to type a comma followed by a space.
+- Special key `, RET` to type a comma followed by return.
+- Translation rules that translate `,` and `, z` to `C-`.
+- Translation rules that translate `m` and `, m m` to `M-`.
+- Translation rule that translates `, ,` to `,`.
+- Repeatable key sequences for `, p`, `, n`, `, f`, `, b`, `, m m f`,
+ `, m m b`, and `, m x o`.
+- Key binding for `isearch-mode-map` to support Devil key sequences in
+ incremental search.
+- Key binding for `universal-argument-map` to support repeating the
+ universal argument with `u`.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000000..ee8f3b8fd1
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+=====================
+
+Copyright (c) 2022-2023 Susam Pal
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..fb98c65696
--- /dev/null
+++ b/README.md
@@ -0,0 +1,457 @@
+Devil Mode
+==========
+
+Devil mode trades your comma key in exchange for a modifier-free
+editing experience! Yes, the comma key! The key you would normally
+wield for punctuation in nearly every corner of text. Yes, this is
+twisted! It would not be called the Devil otherwise, would it? If it
+were any more rational, we might call it something divine, like, uh,
+the God mode? But alas, there's nothing divine to be found here.
+Welcome, instead, to the realm of the Devil! You will be granted the
+occassional use of the comma key for punctuation, but only if you can
+charm the Devil! But beware, for in this sinister domain, you must
+relinquish your comma key and embrace an editing experience that
+whisphers wicked secrets into your fingertips!
+
+
+Contents
+--------
+
+* [Introduction](#introduction)
+* [Notation](#notation)
+* [Get Started](#get-started)
+* [Use Devil](#use-devil)
+* [Typing Commas](#typing-commas)
+* [Devil Reader](#devil-reader)
+* [Translation Rules](#translation-rules)
+* [Translation Examples](#translation-examples)
+* [Extra Key Bindings](#extra-key-bindings)
+* [Local Mode](#local-mode)
+* [Custom Appearance](#custom-appearance)
+* [Custom Devil Key](#custom-devil-key)
+* [Why?](#why)
+* [Support](#support)
+* [Channels](#channels)
+* [More](#more)
+
+
+Introduction
+------------
+
+Devil mode intercepts our keystrokes and translates them to Emacs key
+sequences according to a configurable set of translation rules. For
+example, with the default translation rules, when we type `, x , f`,
+Devil translates it to `C-x C-f`.
+
+The choice of the comma key (`,`) to mean the control modifier key
+(`C-`) may seem outrageous. After all, the comma is a very important
+punctuation both in prose as well as in code. Can we really get away
+with using `,` to mean the `C-` modifier? It turns out, this terrible
+idea can be made to work without too much of a hassle. At least it
+works for me! It might work for you too. If it does not, Devil can be
+configured to use another key instead of `,` to mean the `C-`
+modifier. See the section [Custom Devil Key](#custom-devil-key) for an
+example.
+
+A skeptical reader may rightfully ask: If `,` is translated to `C-`,
+how on earth are we going to insert a literal `,` into the text when
+we need to? The section [Typing Commas](#typing-commas) answers this.
+But before we get there, we have some fundamentals to cover. Take the
+plunge and see what unfolds! Maybe you'll like this! Maybe you won't!
+If you don't like this, you can always retreat to God mode, Evil mode,
+the vanilla key bindings, or whatever piques your fancy!
+
+
+Notation
+--------
+
+A quick note about the notation used in the document: The previous
+example shows that `, x , f` is translated to `C-x C-f`. What this
+really means is that the key sequence
+<kbd>,</kbd><kbd>x</kbd><kbd>,</kbd><kbd>f</kbd> is translated to
+<kbd>ctrl</kbd>+<kbd>x</kbd> <kbd>ctrl</kbd>+<kbd>f</kbd>. We do not
+really type any space after the commas. The key <kbd>,</kbd> is
+directly followed by the key <kbd>x</kbd>. However, the key sequence
+notation used in this document contains spaces between each keystroke.
+This is consistent with how key sequences are represented in Emacs in
+general and how Emacs functions like `key-description`,
+`describe-key`, etc. represent key sequences. When we really need to
+type a space, it is represented as `SPC`.
+
+
+Get Started
+-----------
+
+To get started quickly with Devil, clone its Git repository to your
+system and load it in your Emacs initialization file with the
+following steps:
+
+ 1. Clone Devil to your system:
+
+ ```sh
+ git clone https://github.com/susam/devil.git
+ ```
+
+ 2. Add the following to your Emacs initialization file (i.e., to your
+ `~/.emacs` or `~/.emacs.d/init.el` or `~/.config/emacs/init.el`):
+
+ ```elisp
+ (add-to-list 'load-path "/path/to/devil/")
+ (require 'devil)
+ (global-devil-mode)
+ (global-set-key (kbd "C-,") 'global-devil-mode)
+ ```
+
+ 3. Start the Emacs editor. Devil mode should now be enabled in all
+ buffers. The modeline of each buffer should show the `Devil`
+ lighter.
+
+ 4. Type `, x , f` and watch Devil translate it to `C-x C-f` and
+ invoke the corresponding command.
+
+ 5. Type `C-,` to disable Devil mode. Type `C-,` again to enable it.
+
+
+Use Devil
+---------
+
+Assuming vanilla Emacs key bindings have not been changed and Devil
+has not been customized, here are some examples that demonstrate how
+Devil may be used:
+
+ 1. Type `, x , f` and watch Devil translate it to `C-x C-f` and
+ invoke the find file functionality.
+
+ 2. Type `, p` to move up one line.
+
+ 3. To move up multiple lines type `, p p p` and so on. Some Devil key
+ sequences are repeatable keys. The repeatable Devil key sequences
+ can be repeated by typing the last key of the Devil key sequence
+ over and over again.
+
+ 4. Another example of a repeatable Devil key sequence is `, f f f`
+ which moves the cursor word by multiple characters.
+
+ 5. Type `, s` and watch Devil translate it to `C-s` and invoke
+ incremental search.
+
+ 6. Type `, m s` and watch Devil translate it to `C-M-s` and invoke
+ regular-expression-based incremental search. Yes, `m` is
+ translated to `M-`.
+
+ 7. Type `, m m x` and watch Devil translate it to `M-x` and invoke
+ the corresponding command.
+
+ 8. Type `, u , f` and watch Devil translate it to `C-u C-f` and move
+ the cursor forward by 4 characters.
+
+ 9. Type `, u u , f` and the cursor moves forward by 16 characters.
+ Devil uses its translation rules and an additional keymap to make
+ the input key sequence behave like `C-u C-u C-f` which moves the
+ cursor forward by 16 characters.
+
+10. Type `, SPC` to type a comma followed by space. This is a special
+ key sequence to make it convenient to type comma in the text. Note
+ that this sacrifices the use of `, SPC` to mean `C-SPC` which
+ could have been a convenient way to set a mark.
+
+11. Type `, z SPC` and watch Devil translate it to `C-SPC` and set a
+ mark. Yes, `, z` is translated to `C-` too.
+
+12. Similarly, type `, RET` to type a comma followed by the return
+ key. This is another special key.
+
+13. Type `, ,` to type a single comma. This special key is useful for
+ cases when you really need to type a single literal comma.
+
+
+Typing Commas
+-------------
+
+Devil makes the questionable choice of using the comma as its trigger
+key. As illustrated in the previous section, typing `, x , f` produces
+the same effect as typing `C-x C-f`. One might naturally wonder how
+then are we supposed to type literal commas.
+
+Most often when we edit text, we don't really type a comma in
+isolation. Often we immediately follow the comma with a space or a
+newline. This assumption usually holds good while editing regular
+text. However, this assumption may not hold in some situations, like
+while working with code when we need to add a single comma at the end
+of an existing line.
+
+In scenarios where the above assumption holds good, typing `, SPC`
+inserts a comma and a space. Similarly, typing `, RET` inserts a comma
+and a newline.
+
+In scenarios, when we do need to type a single comma, type `, ,` instead.
+
+Also, it is worth mentioning here that if all this fiddling with the
+comma key feels clumsy, we could always customize the Devil key to
+something else that feels better. We could also disable Devil mode
+temporarily and renable it later with `C-,` as explained in section
+[Get Started](#get-started).
+
+
+Devil Reader
+------------
+
+The following points briefly describe how Devil reads Devil key
+sequences, translates them to Emacs key sequences, and runs commands
+bound to the key sequences:
+
+ 1. As soon as the Devil key is typed (which is `,` by default), Devil
+ wakes up and starts reading Devil key sequences. Type `C-h v
+ devil-key RET` to see the current Devil key.
+
+ 2. After each keystroke is read, Devil checks if the key sequence
+ accumulated is a special key. If it is, then the special command
+ bound to the special key is executed immediately. Note that this
+ step is performed before any translation rules are applied to the
+ input key sequence. This is how the Devil special key sequence `,
+ SPC` inserts a comma and a space. Type `C-h v
+ devil-special-keys RET` to see the list of special keys and
+ the commands bound to them.
+
+ 3. If the key sequence accumulated so far is not a special key, then
+ Devil translates the Devil key sequence to a regular Emacs key
+ sequence. If the regular Emacs key sequence turns out to be a
+ complete key sequence and some command is found to be bound to it,
+ then that command is executed immediately. This is how the Devil
+ key sequence `, x , f` is translated to `C-x C-f` and the
+ corresponding binding is executed. If the translated key sequence
+ is a complete key sequence but no command is bound to it, then
+ Devil displays a message that the key sequence is undefined. Type
+ `C-h v devil-translations RET` to see the list of translation
+ rules.
+
+ 4. After successfully translating a Devil key sequence to an Emacs
+ key sequence and executing the command bound to it, Devil checks
+ if the key sequence is a repeatable key sequence. If it is found
+ to be a repeatable key sequence, then Devil sets a transient map
+ so that the command can be repeated merely by typing the last
+ keystroke of the input key sequence. This is how `, p p p` moves
+ the cursor up by three lines. Type `C-h v devil-repeatable-keys
+ RET` to see the list of repeatable Devil key sequences.
+
+The variables `devil-special-keys`, `devil-translations`, and
+`devil-repeatable-keys` may contain keys or values with the string
+`%k` in them. This is a placeholder for `devil-key`. While applying
+the special keys, translation rules, or repeat rules, each `%k` is
+replaced with the actual value of `devil-key` before applying the
+rules.
+
+
+Translation Rules
+-----------------
+
+The following points provide an account of the translation rules that
+Devil follows in order to convert a Devil key sequence entered by the
+user to an Emacs key sequence:
+
+ 1. The input key vector read from the user is converted to a key
+ description (i.e., the string functions like `describe-key`,
+ `key-description`, produce). For example, if the user types
+ <kbd>,</kbd><kbd>x</kbd><kbd>,</kbd><kbd>f</kbd>, it is converted
+ to `, x , f`.
+
+ 2. Now the resulting key description is translated with simple string
+ replacements. If any part of the string matches a key in
+ `devil-translations`, then it is replaced with the corresponding
+ value. For example, `, x , f` is translated to `C- x C- f`. Then
+ Devil normalizes the result to `C-x C-f` by removing superfluous
+ spaces after the modifier keys.
+
+ 3. However, if the simple string based replacement leads to an
+ invalid Emacs key sequence, it skips the replacement that causes
+ the resulting Emacs key sequence to become invalid. For example `,
+ m ,` results in `C-M-C-` after the simple string replacement
+ because the default translation rules replace `,` with `C-` and
+ `m` with `M-`. However, `C-M-C-` is an invalid key sequence, so
+ the replacement of the second `,` to `C-` is skipped. Therefore,
+ the input `, m ,` is translated to `C-M-,` instead.
+
+
+Translation Examples
+--------------------
+
+By default, Devil supports a small but peculiar set of translation
+rules that can be used to avoid modifier keys while typing various
+types of key sequences. See `C-h v devil-translations RET` for the
+translation rules. Here are some examples that demonstrate the default
+translation rules. The obvious ones are shown first first. The more
+peculiar translations come later in the table.
+
+| Input | Translated | Remarks |
+|-----------|------------|------------------------------------|
+| `, s` | `C-s` | `,` is replaced with `C-` |
+| `, m s` | `C-M-s` | `m` is replaced with `M-` |
+| `, z s` | `C-SPC` | `, z` is replaced with `C-` too |
+| `, z z` | `C-z` | ditto |
+| `, m m x` | `M-x` | `, m m` is replaced with `M-` too |
+| `, c , ,` | `C-c ,` | `, ,` is replaced with `,` |
+
+Note how we cannot use `, SPC` to set a mark because that key sequence
+is already reserved as a special key sequence in `devil-special-keys`,
+so Devil translates `, z` to `C-` too, so that we can still type
+`C-SPC` using `, z s` and set a mark.
+
+Also, note how the translation of `, m m` to `M-` allows us to enter a
+key sequence that begins with the `M-` modifier key.
+
+
+Extra Key Bindings
+------------------
+
+Devil adds the following additional key bindings only when Devil is
+enabled globally with `global-devil-mode`:
+
+- Adds the Devil key to `isearch-mode-map`, so that Devil key
+ sequences work in incremental search too.
+
+- Adds `u` to `universal-argument-more` to allow repeating the
+ universal argument command `C-u` simply by repeating `u`.
+
+As mentioned before these features are available only when Devil is
+enabled globally with `global-devil-mode`. If Devil is enabled locally
+with `devil-mode`, then these features not available.
+
+
+Local Mode
+----------
+
+While the section [Get Started](#get-started) shows how we enable
+Devil mode globally, this section shows how we can enable it locally.
+Here is an example initialization code that enables Devil locally only
+in text buffers.
+
+```elisp
+(add-to-list 'load-path "/path/to/devil/")
+(require 'devil)
+(add-hook 'text-mode-hook 'devil-mode)
+(global-set-key (kbd "C-,") 'devil-mode)
+```
+
+This is not recommended though because this does not provide a
+seamless Devil experience. For example, with Devil enabled locally in
+a text buffer like this, although we can type `, x , f` to launch the
+find-file minibuffer, we cannot use Devil key sequences in the
+minibuffer. Further the special keymaps described in the previous
+section work only when Devil is enabled globally.
+
+
+Custom Appearance
+-----------------
+
+The following initialization code shows how we can customize Devil to
+show a Devil face in the modeline and the echo area.
+
+```elisp
+(add-to-list 'load-path "/path/to/devil/")
+(require 'devil)
+(setq devil-lighter " \U0001F608")
+(setq devil-prompt "\U0001F608 %t")
+(global-devil-mode)
+(global-set-key (kbd "C-,") 'global-devil-mode)
+```
+
+This is how Emacs may look if emojis are rendered correctly:
+
+[![Screenshot of Emacs with Devil face][horns-screenshot]][horns-screenshot]
+
+[horns-screenshot]: https://i.imgur.com/6Ly7IOs.png
+
+
+Custom Devil Key
+----------------
+
+The following initialization code shows how we can customize Devil to
+use a different Devil key.
+
+```elisp
+(add-to-list 'load-path "/path/to/devil/")
+(setq devil-key "<left>")
+(require 'devil)
+(global-devil-mode)
+(global-set-key (kbd "C-<left>") 'global-devil-mode)
+```
+
+The above example sets the Devil key to the left arrow key, perhaps
+another dubious choice for the Devil key. With this configuration, we
+can use `<left> x <left> f` and have Devil translate it to `C-x C-f`.
+
+To customize the special keys, translation rules, and repeatable keys,
+see the variables `devil-special-keys`, `devil-translations`, and
+`devil-repeatable-keys`, respectively.
+
+
+Why?
+----
+
+Why go to the trouble of creating and using something like this? Why
+not just remap <kbd>caps lock</kbd> to <code>ctrl</code> like every
+other sane person does? Or if it is so important to avoid modifier
+keys, why not use something like God mode?
+
+Well, this minor mode began as a tiny little experiment just for fun.
+From the outset, it was clear that using something as crucial as the
+comma for specifying modifier key is asking for trouble. However, I
+still wanted to see how far we can go with it. It turned out that in a
+matter of days, I was using it full-time for all of my Emacs usage.
+
+This experiment was partly motivated by Macbook keyboards which do not
+have a right <kbd>ctrl</kbd> key. Being a touch-typist myself, I found
+it inconvenient to type key combinations like `C-x`, `C-a`, `C-w`,
+`C-s`, etc. where both the modifier key and the modified key need to
+be pressed with the left hand fingers. I am not particularly fond of
+remapping <kbd>caps lock</kbd> to behave like <kbd>ctrl</kbd> because
+that still suffers from the problem that key combinations like `C-x`,
+`C-a` require pressing both the modifier key and the modified key with
+the left hand fingers. I know many people remap both their <kbd>caps
+lock</kbd> and <kbd>enter</kbd> to behave like <kbd>ctrl</kbd>
+modifier key. While I think that's a fine solution, I was not willing
+to put up with the work required to make that work seamlessly across
+all the various operating systems I work on.
+
+What began as a tiny whimsical experiment a few years ago turned out
+to be quite effective, at least to me. I like that this solution is
+implemented purely as Elisp and therefore does not have any external
+dependency. I am sharing this solution here in the form of a minor
+mode, just in case, there is someone out there who might find this
+useful too.
+
+
+Support
+-------
+
+To report bugs, suggest improvements, or ask questions,
+[create issues][ISSUES].
+
+[ISSUES]: https://github.com/susam/devil/issues
+
+
+Channels
+--------
+
+The author of this project hangs out at the following places online:
+
+ - Website: [susam.net](https://susam.net)
+ - Mastodon: [@susam@mastodon.social](https://mastodon.social/@susam)
+ - Twitter: [@susam](https://twitter.com/susam)
+ - GitHub: [@susam](https://github.com/susam)
+ - Matrix:
[#susam:matrix.org](https://app.element.io/#/room/#susam:matrix.org)
+ - IRC: [#susam:libera.chat](https://web.libera.chat/#susam)
+
+You are welcome to subscribe to, follow, or join one or more of the
+above channels to receive updates from the author or ask questions
+about this project.
+
+
+More
+----
+
+See [Emacs4CL](https://github.com/susam/emacs4cl), a DIY quick-starter
+kit to set up Emacs for Common Lisp programming.
+
+See [Emfy](https://github.com/susam/emfy), a DIY quick-starter kit to
+set up Emacs for general purpose editing and programming.
diff --git a/devil.el b/devil.el
new file mode 100644
index 0000000000..2e9faca8a5
--- /dev/null
+++ b/devil.el
@@ -0,0 +1,385 @@
+;;; devil.el --- Minor mode for Devil-like command entering -*-
lexical-binding: t; -*-
+
+;; Copyright (c) 2022-2023 Susam Pal
+
+;; Author: Susam Pal
+;; Version: 0.1.0pre1
+;; Package-Requires: ((emacs "24.4"))
+;; Keywords: convenience
+;; URL: https://github.com/susam/devil
+
+;; This file is not part of GNU Emacs.
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Devil intercepts your devil key (comma by default) to let you type
+;; key sequences without using modifier keys.
+
+;;; Code:
+(defvar devil-key ","
+ "The key sequence that begins Devil input.
+
+The key sequence must be specified in the format returned by `C-h
+k' (`describe-key'). This variable should be set before enabling
+Devil mode for it to take effect.")
+
+(defvar devil-lighter " Devil"
+ "String displayed on the mode line when Devil mode is enabled.")
+
+(defvar devil-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd devil-key) #'devil)
+ map)
+ "Keymap to wake up Devil when `devil-key' is typed.")
+
+;;;###autoload
+(define-minor-mode devil-mode
+ "Local minor mode to support Devil key sequences."
+ :lighter devil-lighter
+ (devil--log "Mode is %s in %s" devil-mode (buffer-name)))
+
+;;;###autoload
+(define-globalized-minor-mode
+ global-devil-mode devil-mode devil--on :group 'devil
+ (if global-devil-mode (devil-add-extra-keys) (devil-remove-extra-keys)))
+
+(defun devil--on ()
+ "Turn Devil mode on."
+ (devil-mode 1))
+
+(defvar devil-logging nil
+ "Non-nil if and only if Devil should print log messages.")
+
+(message "Devil loading ...")
+
+(defvar devil-special-keys
+ (list (cons "%k %k" (lambda () (interactive) (devil-run-key "%k")))
+ (cons "%k SPC" (lambda () (interactive) (devil-run-key "%k SPC")))
+ (cons "%k RET" (lambda () (interactive) (devil-run-key "%k RET"))))
+ "Special Devil keys that are triggered as soon as they are typed.
+
+The value of this variable is an alist where each key represents
+a Devil key sequence. If a Devil key sequence matches any key in
+this alist, the function or lambda in the corresponding value is
+invoked. The format control specifier `%k' may be used to
+represent `devil-key' in the keys.")
+
+(defvar devil-translations
+ (list (cons "%k z" "C-")
+ (cons "%k %k" "%k")
+ (cons "%k m m" "M-")
+ (cons "%k" "C-")
+ (cons "m" "M-"))
+ "Translation rules to convert Devil input to Emacs key sequence.
+
+The value of this variable is an alist where each item represents
+a translation rule that is applied on the Devil key sequence read
+from the user to obtain the Emacs key sequence to be executed.
+The translation rules are applied in the sequence they occur in
+the alist. For each rule, if the key occurs anywhere in the Devil
+key sequence, it is replaced with the corresponding value in the
+translation rule. The format control specifier `%k' may be used
+to represent `devil-key' in the keys.")
+
+(defvar devil-repeatable-keys
+ (list "%k p"
+ "%k n"
+ "%k f"
+ "%k b"
+ "%k m m f"
+ "%k m m b"
+ "%k x o")
+ "Devil mode repeatable key sequences.
+
+The value of this variable is a list where each item represents a
+key sequence that may be repeated merely by typing the last
+character in the key sequence. The format control specified `%k'
+may be used to represent `devil-key' in the keys.")
+
+(defun devil-run-key (key)
+ "Execute the given key sequence KEY.
+
+KEY must be in the format returned by `C-h k` (`describe-key').
+If the format control specifier `%k' occurs in KEY, for each such
+occurrence `devil-key' is inserted into the buffer."
+ (dolist (key (split-string key))
+ (if (string= key "%k") (insert devil-key) (execute-kbd-macro (kbd key)))))
+
+(defvar devil--saved-keys
+ "Original key bindings saved by Devil.")
+
+(defun devil-add-extra-keys ()
+ "Add key bindings to keymaps for Isearch and universal argument."
+ (devil--log "Adding extra key bindings")
+ (setq devil--saved-keys (devil--original-keys-to-be-saved))
+ (define-key isearch-mode-map (kbd devil-key) 'devil)
+ (define-key universal-argument-map (kbd "u") 'universal-argument-more))
+
+(defun devil-remove-extra-keys ()
+ "Remove Devil key bindings from Isearch and universal argument."
+ (devil--log "Removing extra keybindings")
+ (define-key isearch-mode-map (kbd ",")
+ (cdr (assoc 'isearch-comma devil--saved-keys)))
+ (define-key universal-argument-map (kbd "u")
+ (cdr (assoc 'universal-u devil--saved-keys))))
+
+(defun devil--original-keys-to-be-saved ()
+ "Return an alist of keys that will be modified by Devil."
+ (list (cons 'isearch-comma (lookup-key isearch-mode-map (kbd devil-key)))
+ (cons 'universal-u (lookup-key universal-argument-map (kbd "u")))))
+
+(defun devil ()
+ "Wake up Devil to read and translate Devil key sequences."
+ (interactive)
+ (devil--log "Devil waking up")
+ (devil--read-key (vconcat (kbd devil-key))))
+
+(defun devil--read-key (key)
+ "Read Devil key sequences.
+
+Key sequences are read until it is determined to be a valid Devil
+mode special key sequence, a valid complete key sequence after
+translation to Emacs key sequence, or an undefined key sequence
+after translation to Emacs key sequence.
+
+The argument KEY is a vector that represents the key sequence
+read so far. This function reads a new key from the user, appends
+it to KEY, and then checks if the result is a valid key sequence
+or an undefined key sequence. If the result is a valid key
+sequence for a special key command or an Emacs command, then the
+command is executed. Otherwise, this function calls itself
+recursively to read yet another key from the user."
+ (setq key (vconcat key (vector (read-key (devil--make-prompt key)))))
+ (unless (devil--run-command key)
+ (devil--read-key key)))
+
+(defvar devil-prompt "Devil: %t"
+ "A format control string that determines the Devil prompt.
+
+The following format control sequences are supported:
+
+%k - Devil key sequence read by Devil so far.
+%t - Emacs key sequence translated from Devil key sequence read so far.
+%% - The percent sign.")
+
+(defun devil--make-prompt (key)
+ "Create Devil prompt based on the given KEY."
+ (let ((result devil-prompt)
+ (controls (list (cons "%k" (key-description key))
+ (cons "%t" (devil-translate key))
+ (cons "%%" "%"))))
+ (dolist (control controls result)
+ (setq result (replace-regexp-in-string (car control)
+ (cdr control) result)))))
+
+(defun devil--run-command (key)
+ "Try running the command bound to the key sequence in KEY.
+
+KEY is a vector that represents a sequence of keystrokes. If KEY
+is found to be a special key in `devil-special-keys', the
+corresponding special command is executed immediately and t is
+returned.
+
+Otherwise, it is translated to an Emacs key sequence using
+`devil-translations'. If the resulting Emacs key sequence is
+found to be a complete key sequence, the command it is bound to
+is executed interactively and t is returned. If it is found to be
+an undefined key sequence, then t is returned. If the resulting
+Emacs key sequence is found to be an incomplete key sequence,
+then nil is returned."
+ (devil--log "Trying to execute key: %s" (key-description key))
+ (or (devil--run-special-command key)
+ (devil--run-regular-command key)))
+
+(defun devil--run-special-command (key)
+ "Run Devil mode special command defined for the Devil key sequence KEY.
+
+If the given key sequence KEY is found to be a special key in
+`devil-special-keys', the corresponding special command is
+executed, and t is returned. Otherwise nil is returned."
+ (catch 'break
+ (dolist (entry devil-special-keys)
+ (when (string= (key-description key) (devil-format (car entry)))
+ (devil--log "Running special command: %s => %s"
+ (key-description key) (cdr entry))
+ (funcall (cdr entry))
+ (throw 'break t)))))
+
+(defun devil--run-regular-command (key)
+ "Translate KEY and run command bound to it.
+
+After translating KEY to an Emacs key sequence, if the resulting
+key sequence turns out to be an incomplete key, then nil is
+returned. If it turns out to be a complete key sequence, the
+corresponding Emacs command is executed, and t is returned. If it
+turns out to be an undefined key sequence, t is returned. The
+return value t indicates to the caller that no more Devil key
+sequences should be read from the user."
+ (let* ((described-key (key-description key))
+ (translated-key (devil-translate key))
+ (parsed-key (condition-case nil (kbd translated-key) (error nil)))
+ (binding (when parsed-key (key-binding parsed-key))))
+ (cond ((string-match "[ACHMsS]-$" translated-key)
+ (devil--log "Ignoring incomplete key: %s => %s"
+ described-key translated-key)
+ nil)
+ ((keymapp binding)
+ (devil--log "Ignoring prefix key: %s => %s => %s"
+ described-key translated-key binding)
+ nil)
+ ((commandp binding)
+ (devil--update-command-loop-info key binding)
+ (devil--log-command-loop-info)
+ (devil--log "Executing key: %s => %s => %s"
+ described-key translated-key binding)
+ (call-interactively binding)
+ (when (devil--repetable-key-p described-key)
+ (devil--set-transient-map (substring described-key -1) binding))
+ t)
+ (t
+ (message "Devil: %s is undefined" translated-key)
+ t))))
+
+(defun devil-translate (key)
+ "Translate a given Devil KEY to Emacs key sequence.
+
+The argument KEY is a vector that represents the key sequence
+read so far."
+ (setq key (key-description key))
+ (let ((result "")
+ (index 0))
+ (while (< index (length key))
+ (catch 'break
+ ;; Try translating the current position in Devil key to Emacs key.
+ (dolist (entry devil-translations key)
+ (let* ((from-key (devil-format (car entry)))
+ (to-key (devil-format (cdr entry)))
+ (in-key (substring key index))
+ (try-key))
+ (when (string-prefix-p from-key in-key)
+ (setq try-key (devil--clean-key (concat result to-key)))
+ (when (devil--valid-key-p try-key)
+ (setq result try-key)
+ (setq index (+ index (length from-key)))
+ (throw 'break t)))))
+ ;; If no translation succeeded, advance current position.
+ (let ((char (substring key index (1+ index))))
+ (setq result (devil--clean-key (concat result char))))
+ (setq index (1+ index))))
+ result))
+
+(defun devil--update-command-loop-info (key binding)
+ "Update variables that maintain command loop information.
+
+The given KEY and BINDING is used to update variables that
+maintain command loop information. This allows the commands that
+depend on them behave as if they were being invoked directly with
+the original Emacs key sequence."
+ ;;
+ ;; Set `last-command-event' so that `digit-argument' can determine
+ ;; the correct digit for key sequences like , 5 (C-5). See M-x
+ ;; find-function RET digit-argument RET for details.
+ (setq last-command-event (aref key (- (length key) 1)))
+ ;;
+ ;; Set `this-command' to make several commands like , z SPC , z SPC
+ ;; (C-SPC C-SPC) and , p (C-p) work correctly. Emacs copies
+ ;; `this-command' to `last-command'. Both variables are used by
+ ;; `set-mark-command' to decide whether to activate/deactivate the
+ ;; current mark. The first variable is used by vertical motion
+ ;; commands to keep the cursor at the `temporary-goal-column'. There
+ ;; may be other commands too that depend on this variable.
+ (setq this-command binding)
+ ;;
+ ;; Set `real-this-command' to make , x z (C-x z) work correctly.
+ ;; Emacs copies it to `last-repeatable-command' which is then used
+ ;; by repeat. See the following for more details:
+ ;;
+ ;; - M-x find-function RET repeat RET
+ ;; - C-h v last-repeatable-command RET
+ ;; - grep kset_last_repeatable_command src/keyboard.c
+ (setq real-this-command binding))
+
+(defun devil--log-command-loop-info ()
+ "Log command loop information for debugging purpose."
+ (devil--log
+ (concat "Found "
+ (format "current-prefix-arg: %s; " current-prefix-arg)
+ (format "this-command: %s; " this-command)
+ (format "last-command: %s; " last-command)
+ (format "last-repeatable-command: %s" last-repeatable-command))))
+
+(defun devil--repetable-key-p (described-key)
+ "Return t iff DESCRIBED-KEY belongs to `devil-repeatable-keys'."
+ (catch 'break
+ (dolist (repeatable-key devil-repeatable-keys)
+ (when (string= described-key (devil-format repeatable-key))
+ (throw 'break t)))))
+
+(defun devil--set-transient-map (key binding)
+ "Set transient map to run BINDING with KEY."
+ (devil--log "Setting transient map: %s => %s" key binding)
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd key) binding)
+ (set-transient-map map t)))
+
+(defun devil--clean-key (translated-key)
+ "Clean up TRANSLATED-KEY to properly formatted Emacs key sequence."
+ (replace-regexp-in-string "\\([ACHMsS]\\)- " "\\1-" translated-key))
+
+(defun devil--valid-key-p (translated-key)
+ "Return nil iff TRANSLATED-KEY is an invalid Emacs key sequence."
+ (not (string-match-p (concat "A-[^ ]*A-\\|" "C-[^ ]*C-\\|" "H-[^ ]*H-\\|"
+ "M-[^ ]*M-\\|" "s-[^ ]*s-\\|" "S-[^ ]*S-")
+ translated-key)))
+
+(defun devil-format (string)
+ "Replace %k in STRING with `devil-key'."
+ (replace-regexp-in-string "%k" devil-key string))
+
+(defun devil--log (format-string &rest args)
+ "Write log message with the given FORMAT-STRING and ARGS."
+ (when devil-logging
+ (apply 'message (concat "Devil: " format-string) args)))
+
+(defmacro devil--assert (form)
+ "Evaluate FORM and cause error if the result is nil."
+ `(unless ,form
+ (error "Assertion failed: %s" ',form)))
+
+(defun devil--tests ()
+ "Test Devil functions assuming Devil has not been customized."
+ (interactive)
+ (devil--assert (string= (devil-translate (vconcat ",")) "C-"))
+ (devil--assert (string= (devil-translate (vconcat ",x")) "C-x"))
+ (devil--assert (string= (devil-translate (vconcat ",x,")) "C-x C-"))
+ (devil--assert (string= (devil-translate (vconcat ",x,f")) "C-x C-f"))
+ (devil--assert (string= (devil-translate (vconcat ",,")) "C-,"))
+ (devil--assert (string= (devil-translate (vconcat ",,,,")) "C-, C-,"))
+ (devil--assert (string= (devil-translate (vconcat ",mx")) "C-M-x"))
+ (devil--assert (string= (devil-translate (vconcat ",,mx")) "M-x"))
+ (devil--assert (string= (devil-translate (vconcat ",mmm")) "M-m"))
+ (devil--log "Tests completed"))
+
+(provide 'devil)
+
+;;; devil.el ends here
diff --git a/meta/Makefile b/meta/Makefile
new file mode 100644
index 0000000000..ca933a0b53
--- /dev/null
+++ b/meta/Makefile
@@ -0,0 +1,9 @@
+checks:
+ cd ~/git/melpa/ && make clean && rm -rf packages/devil* working/devil/
+ cd ~/git/melpa/ && make recipes/devil
+ -cd ~/git/melpa/ && STABLE=t make recipes/devil
+ cd ~/git/melpa/ && make sandbox INSTALL=devil
+ cd ~/git/melpa/ && ls -l packages/ packages-stable/ sandbox/elpa/
+
+smiley:
+ emacs -q -l smiley.el example.el example.md
diff --git a/meta/README.md b/meta/README.md
new file mode 100644
index 0000000000..80b9886d39
--- /dev/null
+++ b/meta/README.md
@@ -0,0 +1,36 @@
+Developer Notes
+===============
+
+Release Checklist
+-----------------
+
+Perform the following tasks for every release:
+
+ - Update version in devil.el.
+ - Update copyright notice in devil.el.
+ - Update copyright notice in LICENSE.md.
+ - Update CHANGES.md.
+ - Run checks:
+
+ ```sh
+ make checks
+ ```
+
+ - Commit changes:
+
+ ```sh
+ git status
+ git add -p
+ git commit
+ git push
+ ```
+
+ - Tag the release:
+
+ ```
+ VERSION=
+
+ git commit -em "Set version to $VERSION"
+ git tag $VERSION -m "Devil $VERSION"
+ git push origin main $VERSION
+ ```
diff --git a/meta/example.el b/meta/example.el
new file mode 100644
index 0000000000..7ba5f4c9ac
--- /dev/null
+++ b/meta/example.el
@@ -0,0 +1,22 @@
+;;; Emacs Lisp Examples.
+
+(defun hello-world ()
+ "Show 'hello, world' message."
+ (interactive)
+ (message "hello, world"))
+
+(defun show-current-time ()
+ "Show current time."
+ (interactive)
+ (message (current-time-string)))
+
+(defun fibonacci (n)
+ "Compute nth Fibonacci number."
+ (cond ((= n 0) 0)
+ ((= n 1) 1)
+ (t (+ (fibonacci (- n 1))
+ (fibonacci (- n 2))))))
+
+(global-set-key (kbd "C-c h") 'hello-world)
+(global-set-key (kbd "C-c t") 'show-current-time)
+(global-set-key (kbd "C-c d") 'delete-trailing-whitespace)
diff --git a/meta/example.md b/meta/example.md
new file mode 100644
index 0000000000..374b092f11
--- /dev/null
+++ b/meta/example.md
@@ -0,0 +1,27 @@
+Emacs Lisp
+==========
+
+Emacs Lisp is a dialect of the Lisp programming language. Most
+of the GNU Emacs text editor is written in this programming
+language. The Emacs Lisp manual describes the usefulness of
+Emacs Lisp as follows:
+
+> You can write new code in Emacs Lisp and install it as an
+> extension to the editor. However, Emacs Lisp is more than a
+> mere extension language; it is a full computer programming
+> language in its own right. You can use it as you would any
+> other programming language.
+
+Here is a simple Emacs Lisp example that prints the string
+"hello, world" in the echo area:
+
+```
+(message "hello, world")
+```
+
+Emacs Lisp supports both imperative and functional programming
+paradigms. The extension `.el` is used by convention to name
+files that contain Emacs Lisp programs.
+
+The GNU Emacs Lisp manual is available in various formats at
+<https://www.gnu.org/software/emacs/manual/elisp.html>.
diff --git a/meta/global.el b/meta/global.el
new file mode 100644
index 0000000000..e93114c099
--- /dev/null
+++ b/meta/global.el
@@ -0,0 +1,4 @@
+(add-to-list 'load-path "~/git/devil/")
+(require 'devil)
+(global-devil-mode)
+(global-set-key (kbd "C-,") 'global-devil-mode)
diff --git a/meta/god.el b/meta/god.el
new file mode 100644
index 0000000000..75d2e21271
--- /dev/null
+++ b/meta/god.el
@@ -0,0 +1,4 @@
+(add-to-list 'load-path "~/git/god-mode/")
+(require 'god-mode)
+(god-mode)
+(global-set-key (kbd "<escape>") 'god-mode-all)
diff --git a/meta/left.el b/meta/left.el
new file mode 100644
index 0000000000..ef519930f2
--- /dev/null
+++ b/meta/left.el
@@ -0,0 +1,6 @@
+(add-to-list 'load-path "~/git/devil/")
+(setq devil-logging t)
+(setq devil-key "<left>")
+(require 'devil)
+(global-devil-mode)
+(global-set-key (kbd "C-<left>") 'global-devil-mode)
diff --git a/meta/local.el b/meta/local.el
new file mode 100644
index 0000000000..aeaf9ca915
--- /dev/null
+++ b/meta/local.el
@@ -0,0 +1,4 @@
+(add-to-list 'load-path "~/git/devil/")
+(require 'devil)
+(add-hook 'text-mode-hook 'devil-mode)
+(global-set-key (kbd "C-,") 'devil-mode)
diff --git a/meta/smiley.el b/meta/smiley.el
new file mode 100644
index 0000000000..c3c83b92ef
--- /dev/null
+++ b/meta/smiley.el
@@ -0,0 +1,46 @@
+;; Customize user interface.
+(when (display-graphic-p)
+ (tool-bar-mode 0)
+ (scroll-bar-mode 0))
+(setq inhibit-startup-screen t)
+(column-number-mode)
+
+;; Do not display file icon or name on title bar.
+(add-to-list 'default-frame-alist '(ns-appearance . dark))
+(setq ns-use-proxy-icon nil)
+(setq frame-title-format nil)
+
+;; Dark theme colours.
+(load-theme 'wombat)
+(set-face-attribute 'menu nil :background "#444" :foreground "#eee")
+(set-face-attribute 'default nil :background "#111" :foreground "#eee")
+(set-face-attribute 'region nil :background "#354" :foreground "#eee")
+(set-face-attribute 'isearch nil :background "#ff0" :foreground "#000")
+(set-face-attribute 'lazy-highlight nil :background "#990" :foreground "#000")
+(set-face-attribute 'mode-line nil :background "#444" :foreground "#ccc")
+(set-face-attribute 'mode-line-inactive nil :background "#222" :foreground
"#999")
+(set-face-background 'cursor "#c96")
+(set-face-foreground 'font-lock-comment-face "#fc0")
+
+;; Dark theme attributes.
+(set-face-attribute 'menu nil :inverse-video nil)
+(set-face-attribute 'mode-line nil :box '(:style released-button))
+(set-face-attribute 'mode-line-inactive nil :box '(:style pressed-button))
+
+;; Packages.
+(require 'package)
+(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/";) t)
+(package-initialize)
+(unless package-archive-contents
+ (package-refresh-contents))
+(dolist (package '(markdown-mode paredit rainbow-delimiters))
+ (unless (package-installed-p package)
+ (package-install package)))
+
+;; Devil
+(add-to-list 'load-path "~/git/devil/")
+(require 'devil)
+(setq devil-lighter " \U0001F608")
+(setq devil-prompt "\U0001F608 %t")
+(global-devil-mode)
+(global-set-key (kbd "C-,") 'global-devil-mode)
- [nongnu] branch elpa/devil created (now 98064ffed4), ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 0c69796f26 02/49: Replace contractions with uncontracted forms, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil deddb5cade 04/49: Remove stray message call, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil e03a679297 06/49: Fix typos, spelling, and punctuation, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 943578f33f 07/49: Use a smaller screenshot to show Devil smiley, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil c11dd68394 08/49: Use the term activation to describe the Devil key, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil e52262afdd 01/49: Add Devil,
ELPA Syncer <=
- [nongnu] elpa/devil 7bf4345acc 05/49: Use <kbd> instead of <code> for "ctrl", ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 4924f32560 12/49: Describe how multiple Devil keys can be configured, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 331bf12bb2 17/49: Add more examples of repeatable keys to README, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil b32ded26d4 19/49: Add command devil-show-version, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil d98018356c 22/49: Add section to compare Devil mode with God mode, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil aaee73457d 23/49: Remove load calls in configuration examples, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 2cc21ac252 26/49: Set version to 0.2.0, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 83e96bcfce 15/49: Fix typo in function name: s/repetable/repeatable/, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 2ccbcb04f4 27/49: * devil.el: Update header with more information, ELPA Syncer, 2023/05/15
- [nongnu] elpa/devil 483ce72f48 35/49: Convert .md files to .org files, ELPA Syncer, 2023/05/15