emacs-elpa-diffs
[Top][All Lists]
Advanced

[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)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]