[
  {
    "path": "CHANGES.org",
    "content": "#+options: num:nil\n\n* Changelog\n\n** Version 0.7.0 (2023-08-12)\n:PROPERTIES:\n:CUSTOM_ID: 0.7.0\n:END:\n\n*** Added\n\n- Repeatable key sequence =, m e=.\n- Repeatable key sequence =, x u=.\n- Repeatable key sequence group for =, m @= and =, m h=.\n- Repeatable key sequence group for =, x [= and =, x ]=.\n- Repeatable key sequence group for =, x ^= and =, x {= and =, x }=.\n- Repeatable key sequences =, a= and =, e= added to group for =, p=.\n- Repeatable key sequences =, m a= and =, m e= added to group for =, m b=.\n- Customisable variable =devil-global-sets-buffer-default= that, when\n  set to =t=, makes enabling =global-devil-mode= also enable Devil in\n  all new buffers.\n\n*** Changed\n\n- Support for repeatable key groups.  When a Devil key sequence in a\n  repeatable group is typed, then that key sequence or any another key\n  sequence in the same group can be executed and repeated merely by\n  typing the last character of that key sequence.\n- Repeatable key groups defined in =devil-repeatable-keys= is not\n  ignored anymore when =devil-all-keys-repeatable= is set to non-nil.\n- Devil key sequences =, p= and =, n= and =, b= and =, f= have been\n  grouped together into a repeatable key group.\n- Devil key sequences =, m f= and =, m b= have been grouped together\n  into a repeatable key group.\n\n*** Fixed\n\n- Prevent special key sequences from being repeatable.\n\n** Version 0.6.0 (2023-07-30)\n:PROPERTIES:\n:CUSTOM_ID: 0.6.0\n:END:\n\n*** Changed\n\n- Devil key sequence =, m= now translates to =M-= instead of =C-M-=.\n- Devil key sequence =, m z= now translates to =M-= instead of =C-M-=.\n- Devil key sequence =, m ,= now translates to =M-,= instead of =C-M-,=.\n- Devil key sequence =, m m= now translates to =C-M-= instead of =M-=.\n- Repeatable key sequence =, m m f= changed to =, m f=.\n- Repeatable key sequence =, m m b= changed to =, m b=.\n- Repeatable key sequence =, m m y= changed to =, m y=.\n- Repeatable key sequence =, m m ^= changed to =, m ^=.\n\n*** Removed\n\n- Devil key translation from =m z= to =M-=.\n- Devil key translation from =m m= to =m=.\n- Devil key translation from =m= to =M-=.\n\n** Version 0.5.0 (2023-06-15)\n:PROPERTIES:\n:CUSTOM_ID: 0.5.0\n:END:\n\n*** Added\n\n- Function =devil-set-key= to set a new Devil key and update the\n  mode's keymap.\n- Reinstate variable =devil-version= since it is useful in determining\n  the source version conveniently.  It helps during troubleshooting\n  the package when installed from MELPA which sets the package version\n  to a version derived from the current date and time.\n- Command =devil-describe-key= to describe Devil key sequences.\n- Command =devil-toggle-logging= to toggle logging.\n- Special key =, h , k= to execute =devil-describe-key=.\n- Special key =, h , l= to execute =devil-toggle-logging=.\n\n*** Changed\n\n- Customising =devil-key= also updates the mode's keymap.\n- When no binding exists for the translated key sequence, convert the\n  key sequence to a fallback key sequence for terminal Emacs according\n  to =local-function-key-map= and execute any command bound to the\n  fallback key sequence.  For example, when the Devil key sequence =,\n  x <tab>= is converted to =C-x <tab>=, since no command is bound to\n  this key sequence, it is further translated to =C-x TAB= and the\n  command =indent-rigidly= bound to it is executed.\n- Format control sequence to show the Devil key sequence read by Devil\n  has changed from =%k= to =%r=.\n- The default special key sequences no longer merely insert literal\n  characters into the buffer.  That behaviour was problematic in\n  =isearch-mode= because typing special keys like =, ,= and =, SPC= in\n  =isearch-mode= inserted the literal characters in the buffer as\n  opposed to appending these characters to the search pattern.  The\n  default special key sequences now invoke =devil-execute-key= instead\n  which carefully look up the current binding for the current special\n  key and executes it.  This produces the correct behaviour of special\n  keys even in =isearch-mode=.\n\n*** Fixed\n\n- Fix special key sequence =, <return>= which was broken by the\n  previous release of version 0.4.0.\n- Fix special key =, ,= in =isearch-mode=.\n\n** Version 0.4.0 (2023-05-27)\n:PROPERTIES:\n:CUSTOM_ID: 0.4.0\n:END:\n\n*** Added\n\n- Customisable variable =devil-all-keys-repeatable= that makes all\n  Devil key sequences repeatable when set to =t=.\n- Key =, s= to the default list of repeatable keys.\n- Key =, d= to the default list of repeatable keys.\n- Key =, m m ^= to the default list of repeatable keys.\n- Translate =m m= to =m= to support typing key sequences like =C-c m=.\n- Translate =m z= to =M-= to support typing key sequences like =C-c\n  M-m= and =C-M-m=.\n\n*** Changed\n\n- When a Devil key sequence translates to an Emacs key sequence with\n  both the control key and an uppercase letter, the uppercase letter\n  is further translated to its shifted form, e.g., =C-M-V= is\n  translated to =C-M-S-v=.\n\n*** Fixed\n\n- Fix key translation when the Devil key is a key vector, e.g., =(kbd\n  \"<left>\")=\n- Fix key translation of key sequences involving function keys, e.g.,\n  =, <tab>=, =, <backspace>=, etc.  For example, earlier =, <tab>=\n  translated to =C-TAB= and caused \"undefined\" error even if there was\n  a command bound to =C-<tab>=.  With this fix, =, <tab>= is now\n  translated to =C-<tab>= and invokes the command bound to it, if any.\n\n\n** Version 0.3.0 (2023-05-11)\n:PROPERTIES:\n:CUSTOM_ID: 0.3.0\n:END:\n\n*** Added\n\n- Add customisation group =devil=.\n\n*** Changed\n\n- Move tests out to a separate file.\n\n*** Fixed\n\n- Fix spacing in documentation strings.\n- Remove =devil-version= and =devil-show-version=.\n\n\n** Version 0.2.0 (2023-05-09)\n:PROPERTIES:\n:CUSTOM_ID: 0.2.0\n:END:\n\n*** Added\n\n- Key =, k= to the default list of repeatable keys.\n- Key =, /= to the default list of repeatable keys.\n- Key =, m m y= to the default list of repeatable keys.\n- Command =devil-show-version= to display Devil version.\n\n*** Changed\n\n- Automatically detect the activation key and accumulate it in order to\n  support =devil-mode-map= with multiple activation keys.\n\n*** Fixed\n\n- Remove a stray =message= call.\n- Make the function =dev--tests= non-interactive.\n- Translation issue that caused invalid Emacs key sequences on mapping\n  =-=.  For example, mapping =-= to =C-x= and typing =- C-f= produced\n  =C-x CC-xf=.  This has been fixed so that =- C-f= is now translated\n  to =C-x C-f=.\n\n\n** Version 0.1.0 (2023-05-07)\n:PROPERTIES:\n:CUSTOM_ID: 0.1.0\n:END:\n\n*** Added\n\n- Devil global and local minor modes.\n- Default Devil key set to the comma (=,=).\n- Special key =, ,= to type a literal comma.\n- Special key =, SPC= to type a comma followed by a space.\n- Special key =, RET= to type a comma followed by return.\n- Translation rules that translate =,= and =, z= to =C-=.\n- Translation rules that translate =m= and =, m m= to =M-=.\n- Translation rule that translates =, ,= to =,=.\n- Repeatable key sequences for =, p=, =, n=, =, f=, =, b=, =, m m f=,\n  =, m m b=, and =, m x o=.\n- Key binding for =isearch-mode-map= to support Devil key sequences in\n  incremental search.\n- Key binding for =universal-argument-map= to support repeating the\n  universal argument with =u=.\n"
  },
  {
    "path": "CONTRIBUTING.org",
    "content": "* Contribution Guidelines\n\nWhile contributing changes, bug fixes, pull requests, etc. please\nfollow the contribution guidelines laid out in the following sections.\n\n** Commit Messages\n:PROPERTIES:\n:CUSTOM_ID: commit-messages\n:END:\n\n1. Begin the commit message with a short title.  The commit title must\n   not exceed 50 characters in length.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Introduce a new configuration option to control logging level\n   #+end_example\n\n2. The commit title must be a single line.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Add configuration option:\n   logging_level\n   #+end_example\n\n3. The commit title must be written in imperative mood.  Begin the\n   commit message with words like \"Add\", \"Fix\", \"Remove\", etc.  Do not\n   use words like \"Added\", \"Fixes\", \"Removal\", etc. to begin commit\n   summary lines.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Added configuration option to control logging level\n   #+end_example\n\n4. Do not end the commit title with a punctuation.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Add configuration option to control logging level.\n   #+end_example\n\n5. Do not start commit title with prefixes like =fix:=, =feat:=, etc.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   feat: Add configuration option to control logging level\n   #+end_example\n\n6. Optionally, add one or more paragraphs that describe the change in\n   detail.  This is known as the commit description.  There must be a\n   blank line between the commit title and the description.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n\n   This change introduces a new configuration option named logging_level\n   to control the logging level of this application.  The logging levels\n   supported are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Add configuration option to control logging level\n   This change introduces a new configuration option named logging_level\n   to control the logging level of this application.  The logging levels\n   supported are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n7. When a commit description is present, ensure that no line exceeds\n   72 characters in length.  A decent editor should let you\n   automatically format your paragraphs such that each line is less\n   than or equal to 72 characters.  In Vim the commands\n   =:set textwidth=72= followed by =gqap= formats the current\n   paragraph in this manner.  In Emacs, the key sequence =M-q=\n   automatically formats the current paragraph so that no line exceeds\n   a certain number of characters (the default is 70 which is fine).\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n\n   This change introduces a new configuration option named logging_level\n   to control the logging level of this application.  The logging levels\n   supported are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Add configuration option to control logging level\n\n   This change introduces a new configuration option named logging_level to\n   control the logging level of this application.  The logging levels supported\n   are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n8. When a commit description is present and it has multiple sentences,\n   the sentences must be separated by two spaces, i.e., there must be\n   two spaces after each sentence-terminating punctuation.\n\n   Good:\n\n   #+begin_example\n   Add configuration option to control logging level\n\n   This change introduces a new configuration option named logging_level\n   to control the logging level of this application.  The logging levels\n   supported are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n   Bad:\n\n   #+begin_example\n   Add configuration option to control logging level\n\n   This change introduces a new configuration option named logging_level\n   to control the logging level of this application. The logging levels\n   supported are: DEBUG, INFO, WARN, and ERROR.\n   #+end_example\n\n** Content\n:PROPERTIES:\n:CUSTOM_ID: content\n:END:\n\n1. A proposed change should not add major features or increase the\n   scope of the project without prior discussion.  Bug fixes and minor\n   improvements are okay.  However adding a new feature that\n   significantly increases the scope of this project requires prior\n   discussion.\n\n2. While updating =Makefile=, shell scripts, etc., use syntax,\n   commands, and options that are specified in [[https://pubs.opengroup.org/onlinepubs/9699919799/][POSIX]] as much as\n   possible.  Avoid Bash-specific and GNU-specific features.  See\n   documentation on [[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html][POSIX Shell Command Language]], [[https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html][POSIX Utilities]], and\n   [[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html][POSIX Make]] for reference.\n"
  },
  {
    "path": "LICENSE.org",
    "content": "* The MIT License (MIT)\n:PROPERTIES:\n:CUSTOM_ID: the-mit-license\n:END:\nCopyright (c) 2022-2023 Susam Pal\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANUAL.org",
    "content": "#+title:                Devil Mode\n#+author:               Susam Pal\n#+email:                susam@susam.net\n#+language:             en\n#+options:              ':t toc:nil author:t email:t num:t\n#+texinfo_dir_category: Emacs misc features\n#+texinfo_dir_title:    Devil: (devil)\n#+texinfo_dir_desc:     Minor mode for Devil-like command entering\n#+export_file_name:     devil\n\n#+texinfo: @insertcopying\n\nDevil mode trades your comma key in exchange for a modifier-free\nediting experience in Emacs.  Yes, the comma key!  The key you would\nnormally wield for punctuation in nearly every corner of text.  Yes,\nthis is twisted!  It would not be called the Devil otherwise, would\nit?  If it were any more rational, we might call it something divine,\nlike, uh, the God mode?  But alas, there is nothing divine to be found\nhere.  Welcome, instead, to the realm of the Devil!  You will be\ngranted the occasional use of the comma key for punctuation, but only\nif you can charm the Devil.  But beware, for in this sinister domain,\nyou must relinquish your comma key and embrace an editing experience\nthat whispers wicked secrets into your fingertips!\n\n* Introduction\n:PROPERTIES:\n:CUSTOM_ID: introduction\n:END:\nDevil mode intercepts our keystrokes and translates them to Emacs key\nsequences according to a configurable set of translation rules.  For\nexample, with the default translation rules, when we type =, x , f=,\nDevil translates it to =C-x C-f=.\n\nThe choice of the comma key (=,=) to mean the control modifier key\n(=C-=) may seem outrageous.  After all, the comma is a very important\npunctuation both in prose as well as in code.  Can we really get away\nwith using =,= to mean the =C-= modifier?  It turns out, this terrible\nidea can be made to work without too much of a hassle.  At least it\nworks for me.  It might work for you too.  If it does not, Devil can\nbe configured to use another key instead of =,= to mean the =C-=\nmodifier.  See the section [[*Custom Devil Key]] and the subsequent\nsections for a few examples.\n\nA sceptical reader may rightfully ask: If =,= is translated to =C-=,\nhow on earth are we going to insert a literal =,= into the text when\nwe need to?  The section [[*Typing Commas]] answers this.  But before we\nget there, we have some fundamentals to cover.  Take the plunge and\nsee what unfolds.  Maybe you will like this.  Maybe you will not.  If\nyou do not like this, you can always retreat to God mode, Evil mode,\nthe vanilla key bindings, or whatever piques your fancy.\n\n* Notation\n:PROPERTIES:\n:CUSTOM_ID: notation\n:END:\nA quick note about the notation used in the document: The previous\nexample shows that =, x , f= is translated to =C-x C-f=.  What this\nreally means is that the keystrokes =,x,f= is translated to =ctrl+x\nctrl+f=.  We do not really type any space after the commas.  The key\n=,= is directly followed by the key =x=.  However, the key sequence\nnotation used in this document contains spaces between each keystroke.\nThis is consistent with how key sequences are represented in Emacs in\ngeneral and how Emacs functions like =key-description=,\n=describe-key=, etc.  represent key sequences.  When we really need to\ntype a space, it is represented as =SPC=.\n\n* Install\n:PROPERTIES:\n:CUSTOM_ID: install\n:END:\nDevil is available via [[https://elpa.nongnu.org/nongnu/devil.html][NonGNU ELPA]] and [[https://melpa.org/#/devil][MELPA]].  You may already have a\npreferred way of installing packages from ELPA/MELPA.  If so, install\nthe package named =devil= to get Devil.  If you have Emacs 28.1 or a\nmore recent version, it has NonGNU ELPA enabled by default, so you can\ninstall Devil quite easily with =M-x package-install RET devil RET=\nwithout having to perform any further steps.  However, for the sake of\ncompleteness, a few very different and basic ways of installing Devil\nare presented in the next few subsections.\n\n** Install Interactively from MELPA\n:PROPERTIES:\n:CUSTOM_ID: install-interactively-from-melpa\n:END:\nTo install the latest version of Devil from MELPA, perform the\nfollowing steps:\n\n1. Add the following to the Emacs initialization file (i.e.,\n   =~/.emacs= or =~/.emacs.d/init.el= or =~/.config/emacs/init.el=):\n\n   #+begin_src elisp\n     (require 'package)\n     (add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)\n     (package-initialize)\n   #+end_src\n\n2. Start Emacs with the updated initialization file.  Then type these\n   commands:\n\n   #+begin_example\n     M-x package-refresh-contents RET\n     M-x package-install RET devil RET\n   #+end_example\n\n3. Confirm that Devil is installed successfully with this command:\n\n   #+begin_example\n     C-h f devil RET\n   #+end_example\n\n4. Enable Devil mode with this command:\n\n   #+begin_example\n     M-x global-devil-mode RET\n   #+end_example\n\n5. Type =, x , f= and watch Devil translate it to =C-x C-f= and invoke\n   the corresponding command.\n\n** Install Automatically from MELPA\n:PROPERTIES:\n:CUSTOM_ID: install-automatically-from-melpa\n:END:\nHere is yet another basic way to install and enable Devil using Elisp:\n\n#+begin_src elisp\n  (require 'package)\n  (add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)\n  (package-initialize)\n  (unless package-archive-contents\n    (package-refresh-contents))\n  (unless (package-installed-p 'devil)\n    (package-install 'devil))\n  (global-devil-mode)\n  (global-set-key (kbd \"C-,\") 'global-devil-mode)\n#+end_src\n\nNow type =, x , f= and watch Devil translate it to =C-x C-f= and\ninvoke the corresponding command.  Type =C-,= to disable Devil mode.\nType =C-,= again to enable it.\n\n** Install from Git Source\n:PROPERTIES:\n:CUSTOM_ID: install-from-git-source\n:END:\nIf you prefer obtaining Devil from its Git repository, follow these\nsteps:\n\n1. Clone Devil to your system:\n\n   #+begin_src shell\n     git clone https://github.com/susam/devil.git\n   #+end_src\n\n2. Add the following to your Emacs initialization:\n\n   #+begin_src elisp\n     (add-to-list 'load-path \"/path/to/devil/\")\n     (require 'devil)\n     (global-devil-mode)\n     (global-set-key (kbd \"C-,\") 'global-devil-mode)\n   #+end_src\n\n3. Start the Emacs editor.  Devil mode should now be enabled in all\n   buffers.  The modeline of each buffer should show the =Devil=\n   lighter.\n\n4. Type =, x , f= and watch Devil translate it to =C-x C-f= and invoke\n   the corresponding command.  Type =C-,= to disable Devil mode.  Type\n   =C-,= again to enable it.\n\n* Use Devil\n:PROPERTIES:\n:CUSTOM_ID: use-devil\n:END:\nAssuming vanilla Emacs key bindings have not been changed and Devil\nhas not been customised, here are some examples that demonstrate how\nDevil may be used:\n\n1. Type =, x , f= and watch Devil translate it to =C-x C-f= and invoke\n   the =find-file= command.\n\n2. Type =, p= to move up one line.\n\n3. To move up multiple lines, type =, p p p= and so on.  Some Devil\n   key sequences are repeatable keys by default.  The repeatable Devil\n   key sequences can be repeated by typing the last key of the Devil\n   key sequence over and over again.\n\n4. Each repeatable key sequence belongs to a repeatable key sequence\n   groups.  Like before, type =, p p p= to move the cursor up by a few\n   lines.  But then immediately type =n n= to move the cursor down by\n   a couple of lines.  Then immediately type =p n b f= to move the\n   cursor up, down, left, and right.  The key sequences =, p= and =,\n   n= and =, f= and =, b= form a single repeatable key sequence group.\n   Therefore after we type any one of them, we can repeat that key\n   sequence or any other key sequence in the same group over and over\n   again merely by typing the last character of that key sequence.\n   Typing any other key stops the repetition and the default behaviour\n   of that other key is then observed.  Type =C-h v\n   devil-repeatable-keys RET= to see the complete list of all\n   repeatable key sequence groups.\n\n5. Sometimes a repeatable key sequence may be the only key sequence in\n   a repeatable key sequence group.  An example of such a key sequence\n   is =, m ^= which translates to =M-^= and joins the current line to\n   the previous line.  In a text buffer with multiple lines type =, m\n   ^= to join the current line to the previous line.  Then type =^=\n   repeatedly to continue joining lines.  Typing any other key stops\n   the repetition.\n\n6. Type =, s= and watch Devil translate it to =C-s= and invoke\n   incremental search.\n\n7. Type =, m x= and watch Devil translate it to =M-x= and invoke the\n   corresponding command.  Yes, =, m= is translated to =M-=.\n\n8. Type =, m m s= and watch Devil translate it to =C-M-s= and invoke\n   regular-expression-based incremental search.  The key sequence =, m\n   m= is translated to =C-M-=.\n\n9. Type =, u , f= and watch Devil translate it to =C-u C-f= and move\n   the cursor forward by 4 characters.\n\n10. Type =, u u , f= and the cursor moves forward by 16 characters.\n    Devil uses its translation rules and an additional keymap to make\n    this input key sequence behave like =C-u C-u C-f= which moves the\n    cursor forward by 16 characters.\n\n11. Type =, SPC= to type a comma followed by space.  This is a special\n    key sequence to make it convenient to type a comma in the text.\n    Note that this sacrifices the use of =, SPC= to mean =C-SPC= which\n    could have been a convenient way to set a mark.  See the section\n    [[*Reclaim , SPC to Set Mark]] if you do not want to make this\n    sacrifice.\n\n12. Type =, z SPC= and watch Devil translate it to =C-SPC= and set a\n    mark.  Yes, =, z= is translated to =C-= too.\n\n13. Similarly, type =, RET= to type a comma followed by the =enter=\n    key.  This is another special key.\n\n14. Type =, ,= to type a single comma.  This special key is useful for\n    cases when you really need to type a single literal comma.\n\n15. Type =, h , k= to invoke =devil-describe-key=.  This is a special\n    key that invokes the Devil variant of =describe-key= included in\n    vanilla Emacs.  When the key input prompt appears, type the Devil\n    key sequence =, x , f= and Devil will display the documentation of\n    the function invoked by this Devil key sequence.  Note: The key\n    sequence =, h k= translates to =C-h k= and invokes the vanilla\n    =describe-key=.  It is the Devil key sequence =, h , k= that\n    invokes =devil-describe-key=.\n\n* Typing Commas\n:PROPERTIES:\n:CUSTOM_ID: typing-commas\n:END:\nDevil makes the questionable choice of using the comma as its\nactivation key.  As illustrated in the previous section, typing =, x ,\nf= produces the same effect as typing =C-x C-f=.  One might naturally\nwonder how then we are supposed to type literal commas.\n\nMost often when we edit text, we do not really type a comma in\nisolation.  Often we immediately follow the comma with a space or a\nnewline.  This assumption usually holds good while editing regular\ntext.  However, this assumption may not hold in some situations, like\nwhile working with code when we need to add a single comma at the end\nof an existing line.\n\nIn scenarios where the above assumption holds good, typing =, SPC=\ninserts a comma and a space.  Similarly, typing =, RET= inserts a\ncomma and a newline.\n\nIn scenarios where we do need to type a single comma, type =, ,=\ninstead.\n\nNote that you could also type a single comma with =, q ,= which\ntranslates to =C-q ,= and inserts a literal comma.  The Emacs key\nsequence =C-q= invokes the command =quoted-insert= which inserts the\nnext input character.  The =, ,= special key sequence is probably\neasier to type than this.\n\nAlso, it is worth mentioning here that if all this fiddling with the\ncomma key feels clumsy, we could always customise the Devil key to\nsomething else that feels better.  We could also disable Devil mode\ntemporarily and enable it again later with =C-,= as explained in\nsection [[*Install]].\n\n* Devil Reader\n:PROPERTIES:\n:CUSTOM_ID: devil-reader\n:END:\nThe following points briefly describe how Devil reads Devil key\nsequences, translates them to Emacs key sequences, and runs commands\nbound to the key sequences:\n\n1. As soon as the Devil key is typed (which is =,= by default), Devil\n   wakes up and starts reading Devil key sequences.  Type =C-h v\n   devil-key RET= to see the current Devil key.\n\n2. After each keystroke is read, Devil checks if the key sequence\n   accumulated is a special key.  If it is, then the special command\n   bound to the special key is executed immediately.  Note that this\n   step is performed before any translation rules are applied to the\n   input key sequence.  This is how the Devil special key sequence =,\n   SPC= inserts a comma and a space.  Type =C-h v devil-special-keys\n   RET= to see the list of special keys and the commands bound to\n   them.\n\n3. If the key sequence accumulated so far is not a special key, then\n   Devil translates the Devil key sequence to a regular Emacs key\n   sequence.  If the regular Emacs key sequence turns out to be a\n   complete key sequence and some command is found to be bound to it,\n   then that command is executed immediately.  This is how the Devil\n   key sequence =, x , f= is translated to =C-x C-f= and the\n   corresponding binding is executed.  If the translated key sequence\n   is a complete key sequence but no command is bound to it, then\n   Devil displays a message that the key sequence is undefined.  Type\n   =C-h v devil-translations RET= to see the list of translation\n   rules.\n\n4. After successfully translating a Devil key sequence to an Emacs key\n   sequence and executing the command bound to it, Devil checks if the\n   key sequence is a repeatable key sequence.  If it is found to be a\n   repeatable key sequence, then Devil sets a transient map so that\n   the repeatable key sequences that belong to the same group as the\n   typed Devil key sequence can be invoked merely by typing the last\n   character of the input key sequence.  This is how =, p p p f f=\n   moves the cursor up by three lines and then by two characters\n   forward.  Type =C-h v devil-repeatable-keys RET= to see the list of\n   repeatable Devil key sequences.\n\nThe variables =devil-special-keys=, =devil-translations=, and\n=devil-repeatable-keys= may contain keys or values with the string\n=%k= in them.  This is a placeholder for =devil-key=.  While applying\nthe special keys, translation rules, or repeat rules, each =%k= is\nreplaced with the actual value of =devil-key= before applying the\nrules.\n\n* Translation Mechanism\n:PROPERTIES:\n:CUSTOM_ID: translation-mechanism\n:END:\nThe following points provide an account of the translation mechanism\nthat Devil uses in order to convert a Devil key sequence entered by\nthe user to an Emacs key sequence:\n\n1. The input key vector read from the user is converted to a key\n   description (like the string produced by functions like\n   =describe-key= and =key-description=).  For example, if the user\n   types =,x,f= it is converted to =, x , f=.\n\n2. Now the resulting key description is translated with simple string\n   replacements.  If any part of the string matches a key in\n   =devil-translations=, then it is replaced with the corresponding\n   value.  For example, =, x , f= is translated to =C- x C- f=.  Then\n   Devil normalises the result to =C-x C-f= by removing the stray\n   spaces after the modifier keys.\n\n3. If the simple string based replacement discussed in the previous\n   point leads to an invalid Emacs key sequence, it skips the\n   replacement that causes the resulting Emacs key sequence to become\n   invalid.  For example =, m m ,= results in =C-M-C-= after the\n   simple string replacement because the default translation rules\n   replace the leading =, m m= with =C-M-= and the trailing =,= with\n   =C-=.  However, =C-M-C-= is an invalid key sequence, so the\n   replacement of the trailing =,= to =C-= is skipped.  Therefore, the\n   input =, m m ,= is translated to =C-M-,= instead.\n\n4. Finally, Devil looks for key chords in the key sequence that\n   contain both the =C-= modifier and an uppercase letter.  If such a\n   key chord occurs, then it replaces the uppercase letter with its\n   shifted form, e.g., =, m m V= first translates to =C-M-V= according\n   to the previous points and then the result is translated to\n   =C-M-S-v= according to this point.\n\n* Default Translation Rules\n:PROPERTIES:\n:CUSTOM_ID: default-translation-rules\n:END:\nBy default, Devil supports a small but peculiar set of translation\nrules that can be used to avoid modifier keys while typing various\ntypes of key sequences.  See =C-h v devil-translations RET= for the\ntranslation rules.  Here are some examples that demonstrate the\ndefault translation rules.  The obvious ones are shown first.  The\nmore peculiar translations come later in the table.  The concluding\nparagraph of this subsection offers a guide on how to gradually and\ngently adopt these key sequences into your daily routine.\n\n| Input     | Translated | Remarks                                   |\n|-----------+------------+-------------------------------------------|\n| =, s=     | =C-s=      | Rule 1: =,= is replaced with =C-=         |\n| =, m x=   | =M-x=      | Rule 2: =, m= is replaced with =M-=       |\n| =, [ x=   | =C-[ x=    | equivalent to =M-x=                       |\n| =, m m s= | =C-M-s=    | Rule 3: =, m m= is replaced with =C-M-=   |\n| =, m ,=   | =M-,=      | Rule 4: =, m ,= is replaced with =M-,=    |\n| =, m z m= | =M-m=      | Rule 5: =, m z= is replaced with =M-= too |\n| =, c , ,= | =C-c ,=    | Rule 6: =, ,= is replaced with =,=        |\n| =, z SPC= | =C-SPC=    | Rule 7: =, z= is replaced with =C-= too   |\n| =, z z=   | =C-z=      | ditto                                     |\n| =, z ,=   | =C-,=      | ditto                                     |\n\nNote how we cannot use =, SPC= to set a mark because that key sequence\nis already reserved as a special key sequence in =devil-special-keys=.\nIn order to conveniently set a mark, Devil translates =, z= to =C-=\ntoo, so that we can type =, z SPC= and have Devil translate it to\n=C-SPC=.\n\nAlso, note that while =, m= may be used to type =M-= we have =, [= as\nyet another way to type a key sequence that contains =M-= because =,\n[= translates to =C-[= and =C-[ <key>= is equivalent to =ESC <key>=\nwhich in turn is equivalent to =M-<key>=.\n\nThe default translation examples presented in the table above look\nweirder and weirder as we go down the table.  But they are not as\narbitrary as they might initially appear to be.  They are arranged in\nsuch a way that overall, we get the following effect:\n\n- Devil translates the input =,= to =C-=.  Similarly it translates =,\n  m= to =M-= and =, m m= to =C-M-=.\n\n- When we really want to type the Devil key =,= we need to double type\n  it in the Devil key sequence.  Doubling the special character serves\n  as an escape mechanism to avoid the special meaning of the Devil key\n  and get its literal form instead.\n\n- Now since =, ,= translates to =,= we need another escape mechanism\n  to type =C-,=.  Typing =z= in between serves as this escape\n  mechanism, i.e., within a Devil key sequence =, z ,= translates to\n  =C-,=.\n\n- Similarly since =, m m= translates to =C-M-= we need an escape\n  mechanism to type =M-m=.  Again, typing =z= in between serves as\n  this escape mechanism, i.e., =, m z m= translates to =M-m=.\n\nHere is a gentle guide to adopting these key sequences: For beginners\nusing Devil, it is not necessary to memorise all of them right away.\nUnderstanding that =,= translates to =C-= and =, m= translates to =M-=\nis sufficient to begin.  Subsequently, learning that =, m m=\ntranslates to =C-M-= unlocks several more key sequences like =, m m s=\n(=C-M-s=), =, m m f= (=C-M-f=), etc.  As you encounter more key\nsequences that are not covered by these initial rules, revisit the\nabove table to pick up new translation rules and adopt them in your\nday-to-day usage of Devil.\n\n* Describe Devil Key\n:PROPERTIES:\n:CUSTOM_ID: devil-describe-key\n:END:\nDevil offers a command named =devil-describe-key= that can be used to\ndescribe a Devil key sequence.  It works similarly to the\n=describe-key= command of vanilla Emacs that can be invoked with =C-h\nk=.  The =devil-describe-key= command can be invoked with the special\nkey sequence =, h , k=.  Type =, h , k= and a prompt appears to read a\nkey sequence.  Type any Devil key sequence, say, =, x , f= and Devil\nimmediately shows the documentation for the function invoked by this\nkey sequence.\n\nNote that =, x , f= (=devil-describe-key=) can also be used to look up\ndocumentation for vanilla Emacs key sequences like =C-x C-f=.\n\nAlso note that the Devil key sequence is =, h k= is still free to\ninvoke =C-h k= (=describe-key= of vanilla Emacs).\n\n* Bonus Key Bindings\n:PROPERTIES:\n:CUSTOM_ID: bonus-key-bindings\n:END:\nDevil adds the following additional key bindings only when Devil is\nenabled globally with =global-devil-mode=:\n\n- Adds the Devil key to =isearch-mode-map=, so that Devil key\n  sequences work in incremental search too.\n\n- Adds =u= to =universal-argument-more= to allow repeating the\n  universal argument command =C-u= simply by repeating =u=.\n\nAs mentioned before these features are available only when Devil is\nenabled globally with =global-devil-mode=.  If Devil is enabled\nlocally with =devil-mode=, then these features are not available.\n\n* Custom Configuration Examples\n:PROPERTIES:\n:CUSTOM_ID: custom-configuration-examples\n:END:\nIn the examples presented below, the =(require 'devil)= calls may be\nomitted if Devil has been installed from a package archive like ELPA\nor MELPA.  There are appropriate autoloads in place in the Devil\npackage that would ensure that it is loaded automatically on enabling\nDevil mode.  However, the =require= calls have been included in the\nexamples below for the sake of completeness.\n\n** Local Mode\n:PROPERTIES:\n:CUSTOM_ID: local-mode\n:END:\nWhile the section [[*Install]] shows how we enable Devil mode globally,\nthis section shows how we can enable it locally.  Here is an example\ninitialization code that enables Devil locally only in text buffers.\n\n#+begin_src elisp\n  (require 'devil)\n  (add-hook 'text-mode-hook 'devil-mode)\n  (global-set-key (kbd \"C-,\") 'devil-mode)\n#+end_src\n\nThis is not recommended though because this does not provide a\nseamless Devil experience.  For example, with Devil enabled locally in\na text buffer like this, although we can type =, x , f= to launch the\n=find-file= minibuffer, we cannot use Devil key sequences in the\nminibuffer.  Further the special keymaps described in the previous\nsection work only when Devil is enabled globally.\n\n** Mode as a buffer default\n:PROPERTIES:\n:CUSTOM_ID: buffer-default\n:END:\nWhile enabling =global-devil-mode= is typically enough for using Devil\nin every buffer the user encounters, buffer creation which bypasses\nthe traditional setup hooks will not have the mode enabled by default.\nThe most likely place to encounter this problem is with the default\nEmacs startup screen, which is created after the user's init file has\nbeen processed but does not run any hooks which will enable Devil.\n\nTo solve this particular problem it is recommended to either customise\nthe startup screen to one which does run the required hooks, or to\nadvise the =display-startup-screen= function to enable Devil in the\nbuffer once created.  Here is an example of enabling\n=global-devil-mode= and ensuring it is activated in the default Emacs\nstartup screen.\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (advice-add 'display-startup-screen\n              :after (lambda (&optional _) (devil-mode 1)))\n#+end_src\n\nThis solves the problem most likely to be encountered.  But what if\nyou do genuinely need Devil active in *every* buffer?  When\n=devil-global-sets-buffer-default= is set to =t= and\n=global-minor-mode= is called, Devil will be enabled in all new\nbuffers by default.  Enabling this functionality may seem appealing,\nbut consider that not all buffers are intended to be interacted with,\nand the decision to opt-out of hooks which could apply a minor-mode is\nlikely to have been intentional.  Even though it is not recommended,\nthe following example demonstrates how to have Devil active in all\nbuffers by default.\n\n#+begin_src elisp\n  (require 'devil)\n  (setq devil-global-sets-buffer-default t)\n  (global-devil-mode)\n#+end_src\n\n** Custom Appearance\n:PROPERTIES:\n:CUSTOM_ID: custom-appearance\n:END:\nThe following initialization code shows how we can customise Devil to\nshow a Devil smiley (😈) in the modeline and in the Devil prompt.\n\n#+begin_src elisp\n  (require 'devil)\n  (setq devil-lighter \" \\U0001F608\")\n  (setq devil-prompt \"\\U0001F608 %t\")\n  (global-devil-mode)\n  (global-set-key (kbd \"C-,\") 'global-devil-mode)\n#+end_src\n\n** Reclaim , SPC to Set Mark\n:PROPERTIES:\n:CUSTOM_ID: reclaim-comma-space-to-set-mark\n:END:\nThe default configuration for special keys reserves =, SPC= to insert\na literal comma followed by space.  This default makes it easy to type\ncomma in various contexts.  However, this means that =, SPC= does not\ntranslate to =C-SPC=.  Therefore =, SPC= cannot be used to set mark.\nInstead, the default translation rules offer =, z SPC= as a way to set\nmark.\n\nIf you would rather set mark using =, SPC= and you are happy with\ntyping the special key =, ,= to insert a literal comma, then use the\nfollowing configuration:\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (global-set-key (kbd \"C-,\") 'global-devil-mode)\n  (assoc-delete-all \"%k SPC\" devil-special-keys)\n#+end_src\n\nThis removes the special key =, SPC= from =devil-special-keys= so that\nit is now free to be translated to =C-SPC= and invoke =set-mark-command=.\n\n** Custom Devil Key\n:PROPERTIES:\n:CUSTOM_ID: custom-devil-key\n:END:\nThe following initialization code shows how we can customise Devil to\nuse a different Devil key.\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (global-set-key (kbd \"C-;\") 'global-devil-mode)\n  (devil-set-key (kbd \";\"))\n#+end_src\n\nThe above example sets the Devil key to the semicolon, perhaps another\ndubious choice for the Devil key.  With this configuration, we can use\n=; x ; f= and have Devil translate it to =C-x C-f=.\n\n** Yet Another Custom Devil Key\n:PROPERTIES:\n:CUSTOM_ID: yet-another-custom-devil-key\n:END:\nThe following initialization code shows how we can customise Devil to\nuse yet another different Devil key.\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (global-set-key (kbd \"C-<left>\") 'global-devil-mode)\n  (devil-set-key (kbd \"<left>\"))\n  (dolist (key '(\"%k SPC\" \"%k RET\" \"%k <return>\"))\n    (assoc-delete-all key devil-special-keys))\n#+end_src\n\nThe above example sets the Devil key to the left arrow key.  With this\nconfiguration, we can use =<left> x <left> f= and have Devil translate\nit to =C-x C-f=.  We can type the special key =<left> <left>= to\nproduce the same effect as the original =<left>=.\n\nThe above example removes some special keys that are no longer useful.\nIn particular, =<left> SPC= is no longer reserved as a special key, so\nwe can use it now to set a mark.\n\n** Multiple Devil Keys\n:PROPERTIES:\n:CUSTOM_ID: multiple-devil-keys\n:END:\nWhile this package provides the comma (=,=) as the default and the\nonly Devil key, nothing stops you from extending the mode map to\nsupport multiple Devil keys.  Say, you decide that in addition to\nactivating Devil with =,= which also plays the role of =C-=, you also\nwant to activate Devil with =.= which must now play the role of =M-=.\nTo achieve such a result, you could use this initialization code as a\nstarting point and then customise it further based on your\nrequirements:\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (define-key devil-mode-map (kbd \".\") #'devil)\n  (add-to-list 'devil-special-keys `(\". .\" . ,(devil-key-executor \".\")))\n  (setq devil-translations '((\", z\" . \"C-\")\n                             (\". z\" . \"M-\")\n                             (\", ,\" . \",\")\n                             (\". .\" . \".\")\n                             (\",\" . \"C-\")\n                             (\".\" . \"M-\")))\n#+end_src\n\nWith this configuration, we can type =, x , f= for =C-x C-f= like\nbefore.  But now we can also type =. x= for =M-x=.  Similarly, we can\ntype =, . s= for =C-M-s= and so on.  Also =, ,= inserts a literal\ncomma and =. .= inserts a literal dot.  Further we can type =, z ,= to\nget =C-,= and =. z .= to get =M-.=.\n\nNote that by default Devil configures only one activation key (=,=)\nbecause the more activation keys we add, the more intrusive Devil\nbecomes during regular editing tasks.  Every key that we reserve for\nactivating Devil loses its default function and then we need\nworkarounds to somehow invoke the default function associated with\nthat key (like repeating =.= twice to insert a single =.= in the above\nexample).  Therefore, it is a good idea to keep the number of Devil\nkeys as small as possible.\n\n** Make All Keys Repeatable\n:PROPERTIES:\n:CUSTOM_ID: make-all-keys-repeatable\n:END:\nBy default Devil has a small list of key sequences that are considered\nrepeatable.  This list is defined in the variable\n=devil-repeatable-keys=.  Type =C-h v devil-repeatable-keys RET= to\nview this list.  For example, consider the repeatable key sequence\ngroup =(\"%k p\" \"%k n\" \"%k f\" \"%k b\")= in this list.  Assuming that the\ndefault Devil and Emacs key bindings have not been changed, this means\nthat after we type =, p= and move the cursor to the previous line, we\ncan repeat this operation by typing =p= over and again.  We can also\nimmediately type =f= to move the cursor right by one character.  The\nrepetition occurs as long as the last character of any repeatable key\nsequence in the group is typed again.  Typing any other key stops the\nrepetition and the default behaviour of the other key is then\nobserved.\n\nIt is possible to make all key sequences repeatable by setting the\nvariable =devil-all-keys-repeatable= to =t=.  Here is an example\nconfiguration:\n\n#+begin_src elisp\n  (require 'devil)\n  (setq devil-all-keys-repeatable t)\n  (global-devil-mode)\n#+end_src\n\nWith this configuration, the repeatable key sequence groups still\nfunction as described above.  However, in addition to that now all\nother Devil key sequences that end up executing Emacs commands also\nbecome repeatable, i.e., any Devil key sequence that does not belong\nto =devil-all-keys-repeatable= but invokes an Emacs command is now\nrepeatable and it can be repeated by merely repeating the last\ncharacter of the key sequence.\n\nNote that only Devil key sequences that get translated to a regular\nEmacs key sequence and result in the execution of an Emacs command can\nbe repeatable.  The special keys defined in =devil-special-keys= are\nnever repeatable.\n\n** Interaction with Repeat Mode\n:PROPERTIES:\n:CUSTOM_ID: interaction-with-repeat-mode\n:END:\nRepeatable keys in Devil function somewhat like =repeat-mode=\nintroduced in Emacs 28.1.  Here is an example configuration that\ndisables repeatable keys in Devil and shows how to use =repeat-mode=\ninstead to define repeatable commands.\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (setq devil-repeatable-keys nil)\n\n  (defvar movement-repeat-map\n    (let ((map (make-sparse-keymap)))\n      (define-key map (kbd \"p\") #'previous-line)\n      (define-key map (kbd \"n\") #'next-line)\n      (define-key map (kbd \"b\") #'backward-char)\n      (define-key map (kbd \"f\") #'forward-char)\n      map))\n\n  (dolist (cmd '(previous-line next-line backward-char forward-char))\n    (put cmd 'repeat-map 'movement-repeat-map))\n\n  (repeat-mode)\n#+end_src\n\nNow if we type =C-p= to move the cursor up by one line, we can repeat\nit by merely typing =p= again and we can also type or repeat =n=, =b=,\nor =f=, to move the cursor down, left, or right respectively.\n\nRepeat mode works fine with Devil too, so with the above\nconfiguration, when we type =, p= to move the cursor to the previous\nline, we can type or repeat =p=, =n=, =b=, or =f= to move the cursor\nup, down, left, or right again.\n\nWe do not really need to disable Devil's repeatable keys while using\nrepeat mode.  Both can be enabled together.  However, the results can\nbe surprising due to certain differences between the two.  For\nexample, consider the following configuration:\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n\n  (defvar movement-repeat-map\n    (let ((map (make-sparse-keymap)))\n      (define-key map (kbd \"p\") #'previous-line)\n      (define-key map (kbd \"n\") #'next-line)\n      map))\n\n  (dolist (cmd '(previous-line next-line))\n    (put cmd 'repeat-map 'movement-repeat-map))\n\n  (repeat-mode)\n#+end_src\n\nNow both Devil repeatable keys and repeat mode are active.  If we now\ntype =, p= we can repeat =p= and =n= to move the cursor up and down.\nRepeat mode makes this repetition possible.  Additionally, after\ntyping =, p= we can also type or repeat =b= and =f= to move the cursor\nleft and right.  Devil makes this repetition possible.  We can tell\nthe difference between repeat mode handling repeatable commands and\nDevil mode handling repeatable keys by looking at the echo area.  When\nwe repeat =p= which is handled by repeat mode, we see a message\n\"Repeat with p, n\" in the echo area.  But when we repeat =b= which is\nhandled by Devil, we see no such message; Devil sets up repeatable\nkeys silently.\n\n** Comparison with Repeat Mode\n:PROPERTIES:\n:CUSTOM_ID: comparison-with-repeat-mode\n:END:\nThe previous section demonstrates how much of what Devil accomplishes\nwith its support for repeatable key sequences can also be accomplished\nwith =repeat-mode= that comes out of the box in Emacs 28.1 and later\nversions.\n\nHowever, there is a crucial difference between Devil's repeatable keys\nand =repeat-mode=.  Repeat mode provides repeatable /commands/ but\nDevil supports repeatable /keys/.  This different is crucial and\narguably makes repeatable key sequences easier to configure in Devil.\nTo demonstrate the difference, let us consider the key sequence =M-e=.\nThe command =forward-sentence= is bound to it by default in the global\nmap.  However, in Org mode, the command =org-forward-sentence= is\nbound to it.  The corresponding Devil key sequence is =, m e= and this\nis a repeatable key sequence in Devil.  Therefore, we can type =, m e=\nfollowed by =e e e= and so on to move the cursor forward by multiple\nsentences in text mode as well as in Org mode.\n\nTo emulate the same behaviour using repeat mode, we need a\nconfiguration like this:\n\n#+begin_src elisp\n  (require 'devil)\n  (global-devil-mode)\n  (setq devil-repeatable-keys nil)\n\n  (defvar forward-sentence-repeat-map\n    (let ((map (make-sparse-keymap)))\n      (define-key map (kbd \"e\") #'forward-sentence)\n      map))\n\n  (defvar org-forward-sentence-repeat-map\n    (let ((map (make-sparse-keymap)))\n      (define-key map (kbd \"e\") #'org-forward-sentence)\n      map))\n\n  (put #'forward-sentence 'repeat-map 'forward-sentence-repeat-map)\n  (put #'org-forward-sentence 'repeat-map 'org-forward-sentence-repeat-map)\n\n  (repeat-mode)\n#+end_src\n\nNote how we need to configure repeat mode for both commands that are\nbound to =M-e=.  With the above configuration, we can now type =, m e=\nfollowed by =e e e= to move forward by multiple sentences in both text\nmode as well as Org mode.  However, we can never be sure if we missed\nconfiguring repeat mode for some other command that might be bound to\n=M-e= in some mode.  For example, in C mode, the command\n=c-end-of-statement= is bound to =M-e=.  The above configuration is no\ngood for repeating this command by typing =e e e=.\n\nDevil, however, can repeat the command bound to =M-e= in any mode.\nDevil does not merely make the command bound to it in a particular\nmode repeatable.  Instead Devil makes the key sequence =, m e= itself\nrepeatable.  Therefore, with Devil's own support for repeatable key\nsequences, we can type =, m e= and then =e e e= to repeat the command\nbound to =M-e= regardless of which mode is active or which command is\nbound to this key sequence.\n\n* Why?\n:PROPERTIES:\n:CUSTOM_ID: why\n:END:\nWhy go to the trouble of creating and using something like this?  Why\nnot just remap =caps lock= to =ctrl= like every other sane person\ndoes?  Or if it is so important to avoid modifier keys, why not use\nsomething like God mode or Evil mode?\n\nWell, for one, both God mode and Evil mode are modal editing modes.\nDevil, on the other hand, retains the non-modal editing experience of\nEmacs.\n\nDevil mode began as a fun little experiment.  From the outset, it was\nclear that using something as crucial as the comma for specifying the\nmodifier key is asking for trouble.  However, I still wanted to see\nhow far I could go with it.  It turned out that in a matter of days, I\nwas using it full-time for all of my Emacs usage.\n\nThis experiment was partly motivated by Macbook keyboards which do not\nhave a =ctrl= key on the right side of the keyboard.  Being a\ntouch-typist myself, I found it inconvenient to type key combinations\nlike =C-x=, =C-s=, =C-r=, =C-d=, =C-f=, =C-w=, =C-a=, =C-e=,\netc. where both the modifier key and the modified key need to be\npressed with the left hand fingers.  I am not particularly fond of\nremapping =caps lock= to behave like =ctrl= because that still suffers\nfrom the problem that key combinations like =C-x=, =C-a= require\npressing both the modifier key and the modified key with the left hand\nfingers.  I know many people remap both their =caps lock= and =enter=\nto behave like =ctrl=.  While I think that is a fine solution, I was\nnot willing to put up with the work required to make that work\nseamlessly across all the various operating systems I work on.\n\nWhat began as a tiny whimsical experiment a few years ago turned out\nto be quite effective, at least to me.  I like that this solution is\nimplemented purely as Elisp and therefore does not have any external\ndependency.  I am sharing this solution in the form of a minor mode,\njust in case, there is someone out there who might find this useful\ntoo.\n\n* Comparison with God Mode\n:PROPERTIES:\n:CUSTOM_ID: comparison-with-god-mode\n:END:\nGod mode provides a modal editing experience but Devil does not.\nDevil has the same underlying philosophy as that of God mode, i.e.,\nthe user should not have to learn new key bindings.  However, Devil\ndoes not have a hard separation between insert mode and command mode\nlike God mode has.  Instead, Devil waits for an activation key (=,= by\ndefault) and as soon as it is activated, it intercepts and translates\nkeys, runs the corresponding command, and then gets out of the way.\nSo Devil tries to retain the non-modal editing experience of vanilla\nEmacs.\n\nNow it is worth mentioning that some of this non-modal editing\nexperience can be reproduced in god-mode too using its\n=god-execute-with-current-bindings= function.  Here is an example:\n\n#+begin_src elisp\n  (global-set-key (kbd \",\") #'god-execute-with-current-bindings)\n#+end_src\n\nWith this configuration, God mode translates =, x f= to =C-x C-f=.\nSimilarly =, g x= invokes =M-x= and =, G s= invokes =C-M-x=.  This\nprovides a non-modal editing experience in God mode too.  However,\nthis experience does not extend seamlessly to minibuffers.  Devil does\nextend its Devil key translation to minibuffers.\n\nFurther note that in God mode the =ctrl= modifier has sticky\nbehaviour, i.e., the modifier remains active automatically for the\nentire key sequence.  Therefore in the above example, we type =,= only\nonce while typing =, x f= to invoke =C-x C-f=.  However, this sticky\nbehaviour implies that we need some way to disambiguate between key\nsequences like =C-x C-f= (=find-file=) and =C-x f=\n(=set-fill-column=).  God mode solves this by introducing =SPC= to\ndeactivate the modifier, e.g., =, x f= translates to =C-x C-f= but =,\nx SPC f= translates to =C-x f=.  Devil does not treat the modifier key\nas sticky which leads to simpler key sequences at the cost of a little\nadditional typing, i.e., =, x , f= translates to =C-x C-f= and =, x f=\ntranslates to =C-x f=.\n\nTo summarize, there are primarily four things that Devil does\ndifferently:\n\n- Provide a non-modal editing experience from the outset.\n- Seamlessly extend the same editing experience to minibuffer,\n  incremental search, etc.\n- Translate key sequences using string replacements.  This allows for\n  arbitrary and sophisticated key translations for the adventurous.\n- Choose non-sticky behaviour for the modifier keys.\n\nThese differences could make Devil easier to use than God mode for\nsome people but clumsy for other people.  It depends on one's tastes\nand preferences.\n\n* Frequently Asked Questions\n:PROPERTIES:\n:CUSTOM_ID: frequently-asked-questions\n:END:\n01. Why was the comma (=,=) chosen as the default Devil key?  Isn't\n    the semicolon (=;=) a better choice since it belongs to the home\n    row?\n\n    Opinions vary.  As the author and maintainer of this minor mode, I\n    made a choice to use the comma as the default Devil key.\n    Although, the semicolon belongs to the home row on most keyboards\n    and the comma does not, I find the vertical movement to reach the\n    comma key with the long finger more convenient than the horizontal\n    movement necessary to reach the semicolon with the little finger.\n\n    As a touch typist, my fingers rest on the eight home row keys when\n    idle.  The horizontal movement necessary to type the semicolon\n    leads to a significant angular movement of the wrist.  Curling my\n    long finger to reach the comma key helps me avoid this wrist\n    strain.  If you do not like this default, it is quite easy to\n    customise the Devil key to be the semicolon or any other key of\n    your choice.  See the section [[*Custom Devil Key]] and the subsequent\n    sections to learn how to do this.\n\n02. I am happy with typing =, ,= every time, I need to type a comma.\n    Can I free up =, SPC= to invoke =set-mark-command=?\n\n    Yes, this can be done by removing the special key =, SPC= from\n    =devil-special-keys=.  See the section [[*Reclaim , SPC to Set Mark]]\n    to find out how to do this.\n\n03. Can I make the Devil key sticky, i.e., can I type =, x f= instead\n    of =, x , f= to invoke =C-x C-f=?\n\n    Devil does not support sticky keys.  Say, Devil were to translate\n    =, x f= to =C-x C-f=, how would we invoke =C-x f= then?  We need\n    some way to disambiguate between =C-x C-f= and =C-x f=.  Different\n    tools take different approaches to disambiguate the two key\n    sequences.  God-mode translates =x f= to =C-x C-f= and =x SPC f=\n    to =C-x f=, i.e., God-mode treats the =C-= modifier as sticky by\n    default but when we want to make it non-sticky, we need to type\n    =SPC= in god-mode.  This makes some key sequences like =C-x C-f=\n    shorter to type but some other key sequences like =C-x f= longer\n    to type.\n\n    Devil treats the Devil key as non-sticky, so that there is no need\n    for additional peculiar rules to switch between sticky and\n    non-sticky behaviour to disambiguate key sequences like =C-x C-f=\n    and =C-x f=.  With Devil =, x , f= translates to =C-x C-f= and\n    similarly =, x f= translates to =C-x f=.  The translation rules\n    are simpler at the cost of a little additional typing in some\n    cases.  In most such cases, Devil requires typing an additional\n    comma that one could have avoided if the comma were sticky.\n    However, in other cases, Devil eliminates the need to type an\n    extra key to make the modifier key non-sticky.\n\n04. Are there some things that are easier to do with Devil than\n    god-mode?\n\n    Devil is not necessarily easier than god-mode.  It is different.\n    Preferences vary, so some may find Devil easier to use while some\n    others may find god-mode easier to use.  See the section\n    [[*Comparison with God Mode]] for more details on the differences\n    between the two modes.\n\n05. I've called the function =global-devil-mode= in my config file,\n    but when Emacs opens Devil does not seem to active.  Why isn't it\n    working?\n\n    The most likely issue is that the first buffer shown is the\n    default Emacs startup screen, which is created without running any\n    hooks which would give Devil the opportunity to be applied.  See\n    the section [[*Mode as a buffer default]] for further details and\n    possible solutions.\n\n* Conclusion\n:PROPERTIES:\n:CUSTOM_ID: conclusion\n:END:\nDevil is a minor mode to translate key sequences.  Devil utilises this\ntranslation capability to provide a modifier-free editing experience\nand it does so without resorting to modal-editing.  Devil retains the\nnon-modal editing of vanilla Emacs.  This mode was written as a quirky\nexperiment to make it easier to use Emacs without modifier keys.\nHowever, the resulting mode turned out to be quite convenient to use,\nin general.  You might find Devil comfortable.  Or you might find\nDevil to be a terrible idea.  It is also possible that you might find\nDevil useful but intrusive.  In such cases, there are plenty of\ncustomisable options that you can modify to configure Devil according\nto your preferences.  If you need any help or if you find any issues,\nplease create an issue at [[https://github.com/susam/devil/issues]].\n"
  },
  {
    "path": "Makefile",
    "content": "checks: tests test-sentence-ends\n\ntest-sentence-ends:\n\tgrep -n '[^0-9][.?=)] [A-Z]' *.org *.el; [ $$? = 1 ]\n\ntests:\n\temacs --batch -l devil.el -l devil-tests.el -f ert-run-tests-batch-and-exit\n"
  },
  {
    "path": "README.org",
    "content": "* Devil Mode\n\n[[https://melpa.org/#/devil][file:https://melpa.org/packages/devil-badge.svg]]\n[[https://stable.melpa.org/#/devil][file:https://stable.melpa.org/packages/devil-badge.svg]]\n[[https://elpa.nongnu.org/nongnu/devil.html][file:https://elpa.nongnu.org/nongnu/devil.svg]]\n[[https://mastodon.social/@susam][file:https://img.shields.io/badge/mastodon-%40susam-%2355f.svg]]\n\nDevil mode trades your comma key in exchange for a modifier-free\nediting experience in Emacs.  Yes, the comma key!  The key you would\nnormally wield for punctuation in nearly every corner of text.  Yes,\nthis is twisted!  It would not be called the Devil otherwise, would\nit?  If it were any more rational, we might call it something divine,\nlike, uh, the God mode?  But alas, there is nothing divine to be found\nhere.  Welcome, instead, to the realm of the Devil!  You will be\ngranted the occasional use of the comma key for punctuation, but only\nif you can charm the Devil.  But beware, for in this sinister domain,\nyou must relinquish your comma key and embrace an editing experience\nthat whispers wicked secrets into your fingertips!\n\n** Introduction\n:PROPERTIES:\n:CUSTOM_ID: introduction\n:END:\n\nDevil is available in MELPA as well as NonGNU ELPA.  If you are using\nEmacs 28.1 or a more recent version of Emacs, you can get the latest\nstable version of Devil by typing =M-x package-install RET devil RET=.\nOtherwise, you need to add MELPA or NonGNU ELPA to your list of\npackage archives and then install MELPA.  More details on the\ninstallation procedure is provided in the [[https://susam.github.io/devil/][manual]].\n\nBy default, Devil mode rebinds the comma key to activate Devil.  Once\nactivated, Devil reads a so-called Devil key sequence from you.  As\nyou type your Devil key sequence, Devil translates the key sequence to\na regular Emacs key sequence.  If any command is bound to the\ntranslated Emacs key sequence, Devil runs that command and then\ndeactivates itself.\n\nBy default, each comma in the Devil key sequence is translated to\n\"C-\".  For example, if you type \", x , f\", Devil translates it to \"C-x\nC-f\".  Similarly \", m\" is translated to \"M-\".  If you type \", m x\",\nDevil translates it to \"M-x\".  Further \", m m\" is translated to\n\"C-M-\".  If you type \", m m f\" Devil translates it to \"C-M-f\".  There\nare several other translations available in the default translation\nrules that let you enjoy working with Emacs while avoiding modifier\nkeys.  Further, the Devil activation key, translation rules, etc. are\ncustomisable.  Thus if you do not like the default choices made in\nthis package, you can customise it easily to suit your preferences.\n\nRead the [[https://susam.github.io/devil/][manual]] to learn how to install, use, and customise Devil.\n\n** Channels\n:PROPERTIES:\n:CUSTOM_ID: channels\n:END:\n\nThe author of this project hangs out at the following places online:\n\n- Website: [[https://susam.net][susam.net]]\n- Mastodon: [[https://mastodon.social/@susam][@susam@mastodon.social]]\n- GitHub: [[https://github.com/susam][@susam]]\n\nYou are welcome to subscribe to, follow, or join one or more of the\nabove channels to receive updates from the author or ask questions\nabout this project.\n\n** Support\n:PROPERTIES:\n:CUSTOM_ID: support\n:END:\n\nTo report bugs, suggest improvements, or ask questions, [[https://github.com/susam/devil/issues][create issues]].\n\n** Contributing\n:PROPERTIES:\n:CUSTOM_ID: contributing\n:END:\n\nSee [[https://github.com/susam/devil/blob/main/CONTRIBUTING.org][CONTRIBUTING.org]] for contribution guidelines.\n\n** Thanks\n:PROPERTIES:\n:CUSTOM_ID: thanks\n:END:\n\nThanks to:\n\n- [[https://github.com/riscy][Chris Rayner]] for initial code review.\n- [[https://github.com/phikal][Philip Kaludercic]] for initial code review and patches.\n- [[https://github.com/morganwillcock][Morgan Willcock]] for initial feedback and code reviews.\n\n** Reactions\n:PROPERTIES:\n:CUSTOM_ID: reactions\n:END:\n\nSome amusing reactions to this project collected from various corners\nof the world wide web:\n\n#+begin_quote\nEvery bit of this horrifies me, and I can't believe you've done it.\nOutstanding.  Well done!  -- [[https://news.ycombinator.com/item?id=35953341][@kstrauser]]\n#+end_quote\n\n#+begin_quote\nThis is insane.  I am going to try it immediately.  -- [[https://news.ycombinator.com/item?id=35855621][@jrockway]]\n#+end_quote\n\n#+begin_quote\nWill defiantly check this out.  -- [[https://old.reddit.com/r/emacs/comments/13aj99j/devil_mode_a_twisted_key_sequence_translator_for/jj94y35/][@strings]]\n#+end_quote\n\n#+begin_quote\nDefiantly!  -- [[https://old.reddit.com/r/emacs/comments/13aj99j/devil_mode_a_twisted_key_sequence_translator_for/jj98owf/][@oantolin]]\n#+end_quote\n\n#+begin_quote\n😈  -- [[https://old.reddit.com/r/emacs/comments/13aj99j/devil_mode_a_twisted_key_sequence_translator_for/jj72ive/][@SequentialDesign]]\n#+end_quote\n\n** More\n:PROPERTIES:\n:CUSTOM_ID: more\n:END:\n\nSee [[https://github.com/susam/emacs4cl][Emacs4CL]], a DIY quick-starter kit to set up Emacs for Common Lisp\nprogramming.\n\nSee [[https://github.com/susam/emfy][Emfy]], a DIY quick-starter kit to set up Emacs for general purpose\nediting and programming.\n"
  },
  {
    "path": "devil-tests.el",
    "content": ";;; devil-tests.el --- Tests for devil  -*- lexical-binding: t; -*-\n\n;;; Commentary:\n\n;; Unit tests for the internal devil logic.  Run these with M-x ert\n;; RET devil- RET.\n\n;;; Code:\n\n(require 'ert)\n(require 'devil)\n\n\f\n;;; Customization ====================================================\n\n(ert-deftest devil-log ()\n  \"Test if `devil--log' works as expected.\"\n  ;; When logging is disabled, message is not called.\n  (unwind-protect\n      (let ((devil-logging nil)\n            (count 0))\n        (advice-add 'message :override\n                    (lambda (&rest args) (setq count (1+ count)))\n                    '((name . message-override)))\n        (devil--log \"foo\")\n        (should (= count 0)))\n    (advice-remove 'message 'message-override))\n  ;; When logging is enabled, message is called.\n  (unwind-protect\n      (let ((devil-logging t)\n            (count 0))\n        (advice-add 'message :override\n                    (lambda (&rest args) (setq count (1+ count)))\n                    '((name . message-override)))\n        (devil--log \"foo\")\n        (should (= count 1)))\n    (advice-remove 'message 'message-override))\n  ;; When logging is disabled, logging arguments are not evaluated.\n  (unwind-protect\n      (let ((devil-logging nil)\n            (count 0))\n        (advice-add 'message :override (lambda (&rest args))\n                    '((name . message-override)))\n        (devil--log (progn (setq count (1+ count)) \"foo\")\n                    (progn (setq count (1+ count)))\n                    (progn (setq count (1+ count))))\n        (should (= count 0)))\n    (advice-remove 'message 'message-override))\n  ;; When logging is enabled, logging arguments are evaluated.\n  (unwind-protect\n      (let ((devil-logging t)\n            (count 0))\n        (advice-add 'message :override (lambda (&rest args))\n                    '((name . message-override)))\n        (devil--log (progn (setq count (1+ count)) \"foo\")\n                    (progn (setq count (1+ count)))\n                    (progn (setq count (1+ count))))\n        (should (= count 3)))\n    (advice-remove 'message 'message-override)))\n\n\f\n;;; Command Lookup ===================================================\n\n(ert-deftest devil--incomplete-key-p ()\n  \"Test if `devil--invalid-key-p' works as expected.\"\n  (should (devil--incomplete-key-p \"C-\"))\n  (should (devil--incomplete-key-p \"C-x C-\"))\n  (should (devil--incomplete-key-p \"C-M-S-\"))\n  (should (not (devil--incomplete-key-p \"\")))\n  (should (not (devil--incomplete-key-p \"C-x-C-f\")))\n  (should (not (devil--incomplete-key-p \"C-x CC-f\")))\n  (should (not (devil--incomplete-key-p \"C-x C-f\")))\n  (should (not (devil--incomplete-key-p \"C-M-x\"))))\n\n\f\n;;; Key Translation ==================================================\n\n(ert-deftest devil--translate ()\n  \"Test if `devil-translate' works as expected.\"\n  ;; Trivial translations.\n  (should (string= (devil--translate (vconcat \"a\")) \"a\"))\n  (should (string= (devil--translate (vconcat \"A\")) \"A\"))\n  ;; Translations involving the C- modifier.\n  (should (string= (devil--translate (vconcat \",\")) \"C-\"))\n  (should (string= (devil--translate (vconcat \",x\")) \"C-x\"))\n  (should (string= (devil--translate (vconcat \",x,\")) \"C-x C-\"))\n  (should (string= (devil--translate (vconcat \",x,f\")) \"C-x C-f\"))\n  ;; Escape hatch to type commas.\n  (should (string= (devil--translate (vconcat \",,\")) \",\"))\n  (should (string= (devil--translate (vconcat \",,,,\")) \", ,\"))\n  ;; Translations involving M- modifier.\n  (should (string= (devil--translate (vconcat \",mx\")) \"M-x\"))\n  (should (string= (devil--translate (vconcat \",mmx\")) \"C-M-x\"))\n  (should (string= (devil--translate (vconcat \",m,x\")) \"M-, x\"))\n  (should (string= (devil--translate (vconcat \",mmm\")) \"C-M-m\"))\n  ;; Translations involing C- and uppercase letter.\n  (should (string= (devil--translate (vconcat \",a\")) \"C-a\"))\n  (should (string= (devil--translate (vconcat \",A\")) \"C-S-a\"))\n  (should (string= (devil--translate (vconcat \",mA\")) \"M-A\"))\n  (should (string= (devil--translate (vconcat \",mmA\")) \"C-M-S-a\"))\n  (should (string= (devil--translate (vconcat \",A,mmA,a\")) \"C-S-a C-M-S-a C-a\"))\n  (should (string= (devil--translate (vconcat \",A,mA,mA,a\")) \"C-S-a M-A M-A C-a\"))\n  ;; Translations involving C- and RET.\n  (should (string= (devil--translate (vconcat \",\\r\")) \"C-RET\"))\n  (should (string= (devil--translate (vconcat \",m\\r\")) \"M-RET\"))\n  (should (string= (devil--translate (vconcat \",mm\\r\")) \"C-M-RET\"))\n  (should (string= (devil--translate (vconcat \",\\r,R,m\\r\")) \"C-RET C-S-r M-RET\"))\n  ;; Translations provided in the manual as examples.\n  (should (string= (devil--translate (vconcat \",s\")) \"C-s\"))\n  (should (string= (devil--translate (vconcat \",mx\")) \"M-x\"))\n  (should (string= (devil--translate (vconcat \",mms\")) \"C-M-s\"))\n  (should (string= (devil--translate (vconcat \",m,\")) \"M-,\"))\n  (should (string= (devil--translate (vconcat \",mzm\")) \"M-m\"))\n  (should (string= (devil--translate (vconcat \",[x\")) \"C-[ x\"))\n  (should (string= (devil--translate (vconcat \",c,,\")) \"C-c ,\"))\n  (should (string= (devil--translate (vconcat \",z \")) \"C-SPC\"))\n  (should (string= (devil--translate (vconcat \",zz\")) \"C-z\"))\n  (should (string= (devil--translate (vconcat \",z,\")) \"C-,\")))\n\n(ert-deftest devil--terminal-key ()\n  \"Test if `devil--terminal-key' works as expected.\"\n  (let ((local-function-key-map (make-sparse-keymap)))\n    ;; Define bindings for fallback.\n    (define-key local-function-key-map (kbd \"<tab>\") (kbd \"TAB\"))\n    (define-key local-function-key-map (kbd \"M-<return>\") (kbd \"M-RET\"))\n    ;; Test translation\n    (should (string= (devil--terminal-key \"\") \"\"))\n    (should (string= (devil--terminal-key \"a\") \"a\"))\n    (should (string= (devil--terminal-key \"<return>\") \"<return>\"))\n    (should (string= (devil--terminal-key \"C-<tab>\") \"C-<tab>\"))\n    (should (string= (devil--terminal-key \"C-<return>\") \"C-<return>\"))\n    (should (string= (devil--terminal-key \"<tab>\") \"TAB\"))\n    (should (string= (devil--terminal-key \"M-<return>\") \"M-RET\"))\n    (should (string= (devil--terminal-key \"C-<tab> M-<return>\") \"C-<tab> M-RET\"))))\n\n(ert-deftest devil--shifted-key ()\n  \"Test if `devil--shifted-key' works as expected.\"\n  (should (string= (devil--shifted-key \"A\") \"S-a\"))\n  (should (string= (devil--shifted-key \"C-A\") \"C-S-a\"))\n  (should (string= (devil--shifted-key \"C-M-A\") \"C-M-S-a\"))\n  (should (string= (devil--shifted-key \"A \") \"S-a \"))\n  (should (string= (devil--shifted-key \"C-A \") \"C-S-a \"))\n  (should (string= (devil--shifted-key \"C-M-A \") \"C-M-S-a \")))\n\n(ert-deftest devil--invalid-key-p ()\n  \"Test if `devil--invalid-key-p' works as expected.\"\n  (should (devil--invalid-key-p \"\"))\n  (should (devil--invalid-key-p \"C-x-C-f\"))\n  (should (devil--invalid-key-p \"C-x CC-f\"))\n  (should (not (devil--invalid-key-p \"C-x C-f\")))\n  (should (not (devil--invalid-key-p \"C-M-x\"))))\n\n\f\n;;; Utility Functions ================================================\n\n(ert-deftest devil-format ()\n  \"Test if `devil-format' works as expected.\"\n  (let ((devil-key \",\"))\n    (should (string= (devil-format \"%k\") \",\"))\n    (should (string= (devil-format \"Devil: %k\") \"Devil: ,\"))\n    (should (string= (devil-format \"%k %%\") \", %\"))\n    (should (string= (devil-format \"%r => %t\" (kbd \",\")) \", => C-\"))\n    (should (string= (devil-format \"%r => %t\" (kbd \", x\")) \", x => C-x\")))\n  (let ((devil-key (kbd \"<left>\")))\n    (should (string= (devil-format \"%k\") \"<left>\"))\n    (should (string= (devil-format \"Devil: %k\") \"Devil: <left>\"))\n    (should (string= (devil-format \"%k %%\") \"<left> %\"))\n    (should (string= (devil-format \"%r => %t\" (kbd \"<left> x\"))\n                     \"<left> x => C-x\"))\n    (should (string= (devil-format \"%r => %t\" (kbd \"<left> x <left>\"))\n                     \"<left> x <left> => C-x C-\"))))\n\n(ert-deftest devil-string-replace ()\n  \"Test if `devil-string-replace' works as expected.\"\n  (should (string= (devil-string-replace \"\" \"\" \"\") \"\"))\n  (should (string= (devil-string-replace \"\" \"foo\" \"\") \"\"))\n  (should (string= (devil-string-replace \"foo\" \"foo\" \"foo\") \"foo\"))\n  (should (string= (devil-string-replace \"foo\" \"bar\" \"\") \"\"))\n  (should (string= (devil-string-replace \"foo\" \"bar\" \"foo\") \"bar\"))\n  (should (string= (devil-string-replace \"foo\" \"bar\" \"Foo\") \"Foo\"))\n  (should (string= (devil-string-replace \"foo\" \"bar\" \"FOO\") \"FOO\"))\n  (should (string= (devil-string-replace \"f..\" \"bar\" \"foo f..\") \"foo bar\"))\n  (should (string= (devil-string-replace \"f..\" \"<\\\\&>\" \"foo f..\") \"foo <\\\\&>\")))\n\n(ert-deftest devil-regexp-replace ()\n  \"Test if `devil-string-replace' works as expected.\"\n  (should (string= (devil-regexp-replace \"\" \"\" \"\") \"\"))\n  (should (string= (devil-regexp-replace \"\" \"foo\" \"\") \"\"))\n  (should (string= (devil-regexp-replace \"foo\" \"foo\" \"foo\") \"foo\"))\n  (should (string= (devil-regexp-replace \"foo\" \"bar\" \"\") \"\"))\n  (should (string= (devil-regexp-replace \"foo\" \"bar\" \"foo\") \"bar\"))\n  (should (string= (devil-regexp-replace \"foo\" \"bar\" \"Foo\") \"Foo\"))\n  (should (string= (devil-regexp-replace \"foo\" \"bar\" \"FOO\") \"FOO\"))\n  (should (string= (devil-regexp-replace \"f..\" \"bar\" \"foo f..\") \"bar bar\"))\n  (should (string= (devil-regexp-replace \"f..\" \"<\\\\&>\" \"foo f..\") \"<foo> <f..>\")))\n\n(provide 'devil-tests)\n;;; devil-tests.el ends here\n"
  },
  {
    "path": "devil.el",
    "content": ";;; devil.el --- Minor mode for translating key sequences  -*- lexical-binding: t; -*-\n\n;; Copyright (c) 2022-2023 Susam Pal\n\n;; Author: Susam Pal <susam@susam.net>\n;; Maintainer: Susam Pal <susam@susam.net>\n;; Version: 0.7.0-beta3\n;; Package-Requires: ((emacs \"24.4\"))\n;; Keywords: convenience, abbrev\n;; URL: https://github.com/susam/devil\n\n;; This file is not part of GNU Emacs.\n\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;; DEALINGS IN THE SOFTWARE.\n\n;;; Commentary:\n\n;; Devil intercepts your devil key (comma by default) to let you type\n;; key sequences without using modifier keys.  Devil is highly\n;; configurable and it can be configured to perform other key sequence\n;; translations.\n\n;;; Code:\n\n\f\n;;; Customization ====================================================\n\n(defgroup devil '()\n  \"Minor mode for translating key sequences.\"\n  :prefix \"devil-\"\n  :group 'editing)\n\n(defconst devil-version \"0.6.0\"\n  \"Devil version string.\")\n\n(defvar devil-mode-map (make-sparse-keymap)\n  \"Keymap for Devil mode.\n\nBy default, only `devil-key' is added to this keymap so that\nDevil can be activated using it.  To support multiple activation\nkeys, this keymap may be modified to add multiple keys to\nactivate Devil.\")\n\n(defcustom devil-logging nil\n  \"Non-nil iff Devil should print log messages.\"\n  :type 'boolean)\n\n(defun devil-toggle-logging ()\n  \"Toggle the value of `devil-logging'.\"\n  (interactive)\n  (setq devil-logging (not devil-logging))\n  (message \"Devil: Logging %s\" (if devil-logging \"enabled\" \"disabled\")))\n\n(defmacro devil--log (format-string &rest args)\n  \"Write log message with the given FORMAT-STRING and ARGS.\"\n  `(when devil-logging\n     (message (concat \"Devil: \" ,format-string) ,@args)))\n\n(defun devil--custom-devil-key (symbol value)\n  \"Set Devil key variable SYMBOL to the key sequence in given VALUE.\n\nAfter setting SYMBOL to VALUE, clear all key bindings in\n`devil-mode-map' and add a new key binding such that the key\nsequence given in VALUE activates Devil.\"\n  (set-default symbol value)\n  (setcdr devil-mode-map nil)\n  (define-key devil-mode-map value #'devil)\n  (devil--log \"Keymap updated to %s\" devil-mode-map))\n\n(defun devil-set-key (key)\n  \"Set `devil-key' to the given KEY and update `devil-mode-map'.\n\nKEY is a string or vector that represents a sequence of\nkeystrokes, e.g., `\\\",\\\"', `(kbd \\\"<left>\\\")', etc.  This\nfunction clears existing key bindings in `devil-mode-map' and\nsets a single key binding in this keymap so that Devil can be\nactivated using the given KEY.\"\n  (devil--custom-devil-key 'devil-key key))\n\n(defcustom devil-key \",\"\n  \"The key sequence that begins Devil input.\n\nDo not set this variable directly.  Either use the\n`devil-set-key' function to set this variable or customize this\nvariable using Emacs customization features/functions.  Doing so\nensures that the `devil-mode-map' is updated correctly to use the\nupdated value of this variable.\"\n  :type 'key-sequence\n  :set #'devil--custom-devil-key)\n\n(defun devil-key-executor (key)\n  \"Create a command to call `devil-execute-key' with KEY when invoked.\n\nKEY is a string in the format returned by commands such as `C-h\nk' (`describe-key').  Format control sequences supported by\n`devil-format' may be used in KEY.\n\nThis is a convenience function that returns an interactive lambda\nthat may be used as a binding value for a special key defined in\n`devil-special-keys'.  When the lambda returned by this function\nis later invoked, it disables Devil keys temporarily and executes\nthe bindings bound to KEY.  This allows key sequences involving\nthe configured `devil-key' to be executed to produce their\noriginal behaviour thus avoiding invoking Devil recursively.\"\n  (lambda ()\n    (interactive)\n    (devil-execute-key key)))\n\n(defcustom devil-special-keys\n  (list (cons \"%k %k\" (devil-key-executor \"%k\"))\n        (cons \"%k SPC\" (devil-key-executor \"%k SPC\"))\n        (cons \"%k RET\" (devil-key-executor \"%k RET\"))\n        (cons \"%k <return>\" (devil-key-executor \"%k <return>\"))\n        (cons \"%k h %k k\" #'devil-describe-key)\n        (cons \"%k h %k l\" #'devil-toggle-logging))\n  \"Special Devil keys that are executed as soon as they are typed.\n\nThe value of this variable is an alist where each key represents\na Devil key sequence.  If `key-description' of Devil key sequence\nmatches any key in this alist, the function or lambda in the\ncorresponding value is invoked.  Format control sequences\nsupported by `devil-format' may be used in the keys.\"\n  :type '(alist :key-type string :value-type function))\n\n(defcustom devil-translations\n  (list (cons \"%k m m\" \"C-M-\")\n        (cons \"%k m %k\" \"M-,\")\n        (cons \"%k m z\" \"M-\")\n        (cons \"%k m\" \"M-\")\n        (cons \"%k %k\" \"%k\")\n        (cons \"%k z\" \"C-\")\n        (cons \"%k\"  \"C-\"))\n  \"Translation rules to convert Devil input to Emacs key sequence.\n\nThe value of this variable is an alist where each item represents\na translation rule that is applied on the `key-description' of\nthe Devil key sequence read from the user in order to obtain the\nEmacs key sequence to be executed.  The translation rules are\napplied in the sequence they occur in the alist.  For each rule,\nif the key occurs anywhere in the Devil key sequence, it is\nreplaced with the corresponding value in the translation rule.\nHowever, if a replacement leads to an invalid key sequence, then\nthat replacement is skipped.  Format control sequences supported\nby `devil-format' may be used in the keys and values.\"\n  :type '(alist :key-type string :value-type string))\n\n(defcustom devil-repeatable-keys\n  '((\"%k /\")\n    (\"%k d\")\n    (\"%k k\")\n    (\"%k m ^\")\n    (\"%k m e\")\n    (\"%k m b\" \"%k m f\" \"%k m a\" \"% k m e\")\n    (\"%k m @\" \"%k m h\")\n    (\"%k m y\")\n    (\"%k p\" \"%k n\" \"%k b\" \"%k f\" \"%k a\" \"%k e\")\n    (\"%k s\")\n    (\"%k x [\" \"%k x ]\")\n    (\"%k x ^\" \"%k x {\" \"%k x }\")\n    (\"%k x o\")\n    (\"%k x u\"))\n  \"Devil mode repeatable key sequences arranged in groups.\n\nThe value of this variable is a list of lists.  Each item (each\ninner list) of the top-level list represents a group of\nrepeatable key sequences.  Each item of each group is a\nrepeatable key sequence.  A repeatable key sequence may be\nrepeated merely by typing the last character in the key sequence.\nAfter a repeatable key sequence has been typed, typing the last\ncharacter of any repeatable key sequence that belongs to the same\ngroup executes that key sequence.\n\nNote that only Devil key sequences that get translated to a\nregular Emacs key sequence and result in the execution of an\nEmacs command can be repeatable.  The special keys defined in\n`devil-special-keys' are never repeatable.\n\nFormat control sequences supported by `devil-format' may be used\nin the items.  Only key sequences that translate to a complete\nEmacs key sequence according to `devil-translations' and execute\nan Emacs command are made repeatable.\"\n  :type '(repeat (repeat string)))\n\n(defcustom devil-all-keys-repeatable nil\n  \"All successfully translated key sequences become repeatable if non-nil.\n\nWhen this variable is set to non-nil all key sequences that\ntranslate to a complete and defined Emacs key sequence become a\nrepeatable key sequence, i.e., every such key sequence can be\nrepeated merely by typing the last character in the key\nsequence.\"\n  :type 'boolean)\n\n(defcustom devil-lighter \" Devil\"\n  \"String displayed on the mode line when Devil mode is enabled.\"\n  :type 'string)\n\n(defcustom devil-prompt \"Devil: %t\"\n  \"A format control string that determines the `devil' prompt.\n\nFormat control sequences supported by `devil-format' may be used\nin the format control string.\"\n  :type 'string)\n\n(defcustom devil-describe-prompt \"Describe Devil key: %t\"\n  \"A format control string that determines the `devil-describe-key' prompt.\n\nFormat control sequences supported by `devil-format' may be used\nin the format control string.\"\n  :type 'string)\n\n(defcustom devil-global-sets-buffer-default nil\n  \"Non-nil iff `global-devil-mode' modifies new buffer defaults.\n\nWhen non-nil and `global-devil-mode' is enabled, `devil-mode'\nwill be enabled in all new buffers without relying on the\nstandard global minor-mode hooks.\n\nWhile this solves issues with `devil-mode' not being active in\nbuffers which have not called the hooks where a minor-mode could\nbe applied, the decision to bypass these hooks is likely to have\nbeen intentional.  It is not recommended to enable this option\nunless you are absolutely sure of the consequences.\n\nTo work around the most common issue, where `global-devil-mode'\nis enabled during start-up but `devil-mode' is not enabled in the\ndefault Emacs startup screen, a safer solution is to advise the\nfunction which creates the startup screen to enable the mode\nlocally.\"\n  :type 'boolean)\n\n\f\n;;; Minor Mode Definition ============================================\n\n;;;###autoload\n(define-minor-mode devil-mode\n  \"Local minor mode to support Devil key sequences.\"\n  :lighter devil-lighter\n  (devil--log \"Mode is %s in %s\" devil-mode (buffer-name)))\n\n;;;###autoload\n(define-globalized-minor-mode\n  global-devil-mode devil-mode devil--on\n  (if global-devil-mode (devil--add-extra-keys) (devil--remove-extra-keys))\n  (when devil-global-sets-buffer-default\n    (setq-default devil-mode global-devil-mode)))\n\n(defun devil--on ()\n  \"Turn Devil mode on.\"\n  (devil-mode 1))\n\n\f\n;;; Bonus Key Bindings ===============================================\n\n(defvar devil--saved-keys nil\n  \"Original key bindings saved by Devil.\")\n\n(defun devil--add-extra-keys ()\n  \"Add key bindings to keymaps for Isearch and universal argument.\"\n  (devil--log \"Adding extra key bindings\")\n  (setq devil--saved-keys (devil--original-keys-to-be-saved))\n  (define-key isearch-mode-map devil-key #'devil)\n  (define-key universal-argument-map (kbd \"u\") #'universal-argument-more))\n\n(defun devil--remove-extra-keys ()\n  \"Remove Devil key bindings from Isearch and universal argument.\"\n  (devil--log \"Removing extra key bindings\")\n  (define-key isearch-mode-map (kbd \",\")\n    (devil--aget 'isearch-comma devil--saved-keys))\n  (define-key universal-argument-map (kbd \"u\")\n    (devil--aget 'universal-u devil--saved-keys)))\n\n(defun devil--original-keys-to-be-saved ()\n  \"Return an alist of keys that will be modified by Devil.\"\n  (list (cons 'isearch-comma (lookup-key isearch-mode-map devil-key))\n        (cons 'universal-u (lookup-key universal-argument-map (kbd \"u\")))))\n\n\f\n;;; Activation Commands ==============================================\n\n(defun devil ()\n  \"Read and execute a Devil key sequence.\"\n  (interactive)\n  (devil--log \"Activated with %s\" (key-description (this-command-keys)))\n  (let* ((result (devil--read-key devil-prompt (this-command-keys)))\n         (key (devil--aget 'key result))\n         (translated-key (devil--aget 'translated-key result))\n         (binding (devil--aget 'binding result)))\n    (devil--log \"Read key: %s => %s => %s => %s\"\n                key (key-description key) translated-key binding)\n    (if (not binding)\n        (message \"Devil: %s is undefined\" translated-key)\n      (devil--execute-command key binding)\n      (when translated-key\n        (devil--set-repeatable-keys (key-description key))))))\n\n(defun devil-describe-key ()\n  \"Describe a Devil key sequence.\"\n  (interactive)\n  (devil--log \"Activated with %s\" (key-description (this-command-keys)))\n  (let* ((result (devil--read-key devil-describe-prompt (vector)))\n         (key (devil--aget 'key result))\n         (translated-key (devil--aget 'translated-key result))\n         (binding (devil--aget 'binding result)))\n    (devil--log \"Read key: %s => %s => %s => %s\"\n                key (key-description key) translated-key binding)\n    (if translated-key\n        (describe-key (list (cons (kbd translated-key) key)))\n      ;; Create a transient keymap to describe special key sequence.\n      (let* ((virtual-keymap (make-sparse-keymap))\n             (exit-function (set-transient-map virtual-keymap)))\n        (define-key virtual-keymap key binding)\n        (describe-key key)\n        (funcall exit-function)))))\n\n\f\n;;; Command Lookup ===================================================\n\n(defconst devil--fallbacks (list #'devil--terminal-key)\n  \"A list of functions that further translate a translated key.\")\n\n(defun devil--read-key (prompt key)\n  \"Read Devil key sequence.\n\nKey events are read until it is determined to be a valid special\nkey sequence, a valid complete key sequence after translation to\nEmacs key sequence, or an undefined key sequence after\ntranslation to Emacs key sequence.\n\nPROMPT is a format control string that defines the prompt to be\ndisplayed while reading the key sequence.  Format control\nsequences supported by `devil-format' may be used in PROMPT.\n\nKEY is a vector that represents the key sequence read so far.\nThis function reads a new key from the user, appends it to KEY,\nand then checks if the result is a valid key sequence or an\nundefined key sequence.  If the result is a valid key sequence\nfor a special key command or an Emacs command, then the command\nis executed.  Otherwise, this function calls itself recursively\nto read yet another key from the user.\"\n  (setq key (vconcat key (vector (read-event (devil-format prompt key)))))\n  (or (devil--find-special-command key)\n      (devil--find-regular-command key)\n      (devil--read-key prompt key)))\n\n(defun devil--find-special-command (key)\n  \"Find special command defined for KEY.\n\nIf the `key-description' of the given key sequence vector KEY is\nfound to be a special key in `devil-special-keys', the\ncorresponding special command is executed, and a non-nil result\nis returned.  Otherwise nil is returned.\"\n  (catch 'break\n    (dolist (entry devil-special-keys)\n      (when (string= (key-description key) (devil-format (car entry)))\n        (devil--log \"Found special command: %s => %s\"\n                    (key-description key) (cdr entry))\n        (throw 'break (devil--binding-result key nil (cdr entry)))))))\n\n(defun devil--find-regular-command (key)\n  \"Translate KEY and find command bound to it.\n\nAfter translating the given key sequence vector KEY to an Emacs\nkey sequence, if the resulting key sequence turns out to be an\nincomplete key, then nil is returned.  If it turns out to be a\ncomplete key sequence, a non-nil result is returned.\"\n  (devil--find-command key (devil--translate key) devil--fallbacks))\n\n(defun devil--find-command (key translated-key fallbacks)\n  \"Find command bound to TRANSLATED-KEY translated from KEY.\n\nFALLBACKS is a list of functions.  When FALLBACKS is non-nil and\nno binding is found for the given TRANSLATED-KEY, the given\nTRANSLATED-KEY is translated further by invoking the `car' of\nthis list.  Then this function is called recursively with the\n`cdr' of this list.\"\n  (let* ((parsed-key (ignore-errors (kbd translated-key)))\n         (binding (when parsed-key (key-binding parsed-key))))\n    (cond ((devil--incomplete-key-p translated-key)\n           (devil--log \"Ignoring incomplete key: %s\" translated-key)\n           nil)\n          ((keymapp binding)\n           (devil--log \"Ignoring prefix key: %s\" translated-key)\n           nil)\n          ((commandp binding)\n           (devil--log \"Found command: %s => %s\" translated-key binding)\n           (devil--binding-result key translated-key binding))\n          (t\n           (devil--log \"Undefined key: %s => %s\" translated-key binding)\n           (let ((fallback-key (when fallbacks (funcall (car fallbacks)\n                                                        translated-key))))\n             (if (and fallback-key (not (string= translated-key fallback-key)))\n                 (devil--find-command key fallback-key (cdr fallbacks))\n               (devil--binding-result key translated-key nil)))))))\n\n(defun devil--incomplete-key-p (translated-key)\n  \"Return t iff TRANSLATED-KEY is an incomplete Emacs key sequence.\"\n  (string-match \"[ACHMSs]-$\" translated-key))\n\n(defun devil--binding-result (key translated-key binding)\n  \"Create alist for the given KEY, TRANSLATED-KEY, and BINDING.\"\n  (list (cons 'key key)\n        (cons 'translated-key translated-key)\n        (cons 'binding binding)))\n\n\f\n;;; Key Translation ==================================================\n\n(defun devil--translate (key)\n  \"Translate a given Devil key sequence vector to Emacs key sequence.\n\nKEY is a key sequence vector that represents a Devil key\nsequence.  The returned value is an Emacs key sequence string in\nthe format returned by commands such as `C-h k' (`describe-key').\"\n  (setq key (key-description key))\n  (let ((result \"\")\n        (index 0))\n    ;; Scan Devil key from left to right.\n    (while (< index (length key))\n      (catch 'break\n        ;; Try each translation at the current scan position.\n        (dolist (entry devil-translations key)\n          (let* ((from-key (devil-format (car entry)))\n                 (to-key (devil-format (cdr entry)))\n                 (in-key (substring key index))\n                 (try-key))\n            (when (string-prefix-p from-key in-key)\n              ;; Apply matching translation at the current scan position.\n              (setq try-key (devil--clean-key (concat result to-key)))\n              (unless (devil--invalid-key-p try-key)\n                ;; Translation succeeded.  Do not apply any more\n                ;; translation at the current scan position.  Instead\n                ;; move ahead to the next scan position.\n                (setq result try-key)\n                (setq index (+ index (length from-key)))\n                (throw 'break t)))))\n        ;; If no translation succeeded, increment scan position and\n        ;; try applying translations at the new scan position.\n        (let ((char (substring key index (1+ index))))\n          (setq result (devil--clean-key (concat result char))))\n        (setq index (1+ index))))\n    (devil--normalize-ctrl-uppercase-chord result)))\n\n(defun devil--terminal-key (translated-key)\n  \"Translate TRANSLATED-KEY to an Emacs key sequence for terminal Emacs.\n\nThe argument TRANSLATED-KEY is a string that represents an Emacs\nkey sequence returned by `devil--translate'.  Each keystroke in\nthe key sequence is looked up in `local-function-key-map'.  If a\nmatch is found, it is replaced with its corresponding binding.\"\n  (unless (devil--incomplete-key-p translated-key)\n    (let ((result \"\"))\n      (dolist (chunk (split-string translated-key \" \" t))\n        (let* ((separator (if (string= result \"\") \"\" \" \"))\n               (binding (lookup-key local-function-key-map (kbd chunk))))\n          (when (and binding (not (keymapp binding)))\n            (setq chunk (key-description binding)))\n          (setq result (concat result separator chunk))))\n      result)))\n\n(defun devil--clean-key (translated-key)\n  \"Clean up TRANSLATED-KEY to properly formatted Emacs key sequence.\"\n  (devil-regexp-replace \"\\\\([ACHMSs]\\\\)- \" \"\\\\1-\" translated-key))\n\n(defun devil--normalize-ctrl-uppercase-chord (translated-key)\n  \"Normalize chords containing ctrl and uppercase letter in TRANSLATED-KEY.\"\n  (devil-regexp-replace \"C-\\\\(?:[ACHMs]-\\\\)*[A-Z]\\\\(?: \\\\|$\\\\)\"\n                        'devil--shifted-key translated-key))\n\n(defun devil--shifted-key (translated-key)\n  \"Replace the last character in TRANSLATED-KEY with its shifted form.\"\n  (let* ((hyphen-index (if (string-suffix-p \" \" translated-key) -2 -1))\n         (prefix (substring translated-key 0 hyphen-index))\n         (suffix (substring translated-key hyphen-index)))\n    (concat prefix \"S-\" (downcase suffix))))\n\n(defun devil--invalid-key-p (translated-key)\n  \"Return t iff TRANSLATED-KEY is an invalid Emacs key sequence.\"\n  (catch 'break\n    (dolist (chunk (split-string translated-key \" \"))\n      (when (or (string= chunk \"\")\n                (not (string-match-p \"^\\\\(?:[ACHMSs]-\\\\)*\\\\([^-]*\\\\|<.*>\\\\)$\" chunk))\n                (string-match-p \"\\\\([ACHMSs]-\\\\)[^ ]*\\\\1\" chunk))\n        (throw 'break t)))))\n\n\f\n;;; Command Execution ================================================\n\n(defun devil-execute-key (key)\n  \"Suppress Devil keys and execute the given KEY.\n\nKEY is a string in the format returned by commands such as `C-h\nk' (`describe-key').  Format control sequences supported by\n`devil-format' may be used in KEY.\"\n  (let ((keymap (cdr devil-mode-map))\n        (key (devil-format key)))\n    (setcdr devil-mode-map nil)\n    (devil--remove-extra-keys)\n    (devil--log \"Disabling keymaps\")\n    (unwind-protect\n        (devil--find-bindings-and-execute key)\n      (devil--log \"Enabling keymaps\")\n      (setcdr devil-mode-map keymap)\n      (devil--add-extra-keys))))\n\n(defun devil--find-bindings-and-execute (key)\n  \"Find bindings bound to the given KEY and execute them.\"\n  (let ((accumulator \"\"))\n    (dolist (chunk (split-string key \" \" t))\n      (let ((separator (if (string= accumulator \"\") \"\" \" \")))\n        (setq accumulator (concat accumulator separator chunk)))\n      (let* ((result (devil--find-command key accumulator devil--fallbacks))\n             (binding (devil--aget 'binding result)))\n        (cond ((not binding)\n               (message \"Devil: %s is undefined\" accumulator)\n               (setq accumulator \"\"))\n              (t\n               (devil--execute-command (kbd chunk) binding)\n               (setq accumulator \"\")))))))\n\n(defun devil--execute-command (key binding)\n  \"Execute the given BINDING bound to the given KEY.\"\n  (let ((described-key (key-description key)))\n    (devil--update-command-loop-info key binding)\n    (devil--log-command-loop-info)\n    (devil--log \"Executing command: %s => %s\" described-key binding)\n    (call-interactively binding)))\n\n(defun devil--update-command-loop-info (key binding)\n  \"Update variables that maintain command loop information.\n\nThe given KEY and BINDING is used to update variables that\nmaintain command loop information.  This allows the commands that\ndepend on them behave as if they were being invoked directly with\nthe original Emacs key sequence.\"\n  ;;\n  ;; Set `last-command-event' so that `digit-argument' can determine\n  ;; the correct digit for key sequences like , 5 (C-5).  See M-x\n  ;; find-function RET digit-argument RET for details.\n  (setq last-command-event (aref key (- (length key) 1)))\n  ;;\n  ;; Set `this-command' to make several commands like , z SPC , z SPC\n  ;; (C-SPC C-SPC) and , p (C-p) work correctly.  Emacs copies\n  ;; `this-command' to `last-command'.  Both variables are used by\n  ;; `set-mark-command' to decide whether to activate/deactivate the\n  ;; current mark.  The first variable is used by vertical motion\n  ;; commands to keep the cursor at the `temporary-goal-column'.  There\n  ;; may be other commands too that depend on this variable.\n  (setq this-command binding)\n  ;;\n  ;; Set `real-this-command' to make , x z (C-x z) work correctly.\n  ;; Emacs copies it to `last-repeatable-command' which is then used\n  ;; by repeat.  See the following for more details:\n  ;;\n  ;;   - M-x find-function RET repeat RET\n  ;;   - C-h v last-repeatable-command RET\n  ;;   - grep kset_last_repeatable_command src/keyboard.c\n  (setq real-this-command binding))\n\n(defun devil--log-command-loop-info ()\n  \"Log command loop information for debugging purpose.\"\n  (devil--log \"Found current-prefix-arg: %s; \\\nthis-command: %s; last-command: %s; last-repeatable-command: %s; \\\nlast-command-event: %s; char-before: %s\"\n              current-prefix-arg\n              this-command\n              last-command\n              last-repeatable-command\n              last-command-event\n              (char-before)))\n\n(defun devil--set-repeatable-keys (described-key)\n  \"Set transient map for repeatable keys in the same group as DESCRIBED-KEY.\"\n  (let ((group (or (devil--find-repeatable-group described-key)\n                   (when devil-all-keys-repeatable (list described-key)))))\n    (when group\n      (devil--log \"Setting repeatable keys for %s: %S\" described-key group)\n      (devil--set-transient-map group))))\n\n(defun devil--set-transient-map (repeatable-keys-group)\n  \"Set transient map for the keys in REPEATABLE-KEYS-GROUP.\"\n  (let ((map (make-sparse-keymap)))\n    (dolist (repeatable-key repeatable-keys-group)\n      (let* ((key (vconcat (kbd (devil-format repeatable-key))))\n             (translated-key (devil--translate key))\n             (transient-key (vector (aref key (1- (length key)))))\n             (result (devil--find-command key translated-key devil--fallbacks))\n             (binding (devil--aget 'binding result)))\n        (when binding\n          (devil--log \"Setting transient repeatable key: %s => %s\"\n                      (key-description transient-key) binding)\n          (define-key map transient-key binding))))\n    (set-transient-map map t)))\n\n(defun devil--find-repeatable-group (described-key)\n  \"Find the repeatable keys group that DESCRIBED-KEY belongs to.\"\n  (catch 'break\n    (dolist (repeatable-group devil-repeatable-keys)\n      (dolist (repeatable-key repeatable-group)\n        (when (string= described-key (devil-format repeatable-key))\n          (throw 'break repeatable-group))))))\n\n\f\n;;; Utility Functions ================================================\n\n(defun devil-format (format-string &optional key)\n  \"Format a Devil FORMAT-STRING.\n\nKEY must be a key sequence vector.  The following format control\nsequences are supported in FORMAT-STRING:\n\n%k - Devil key.\n%r - Devil key sequence read by Devil so far.\n%t - Emacs key sequence translated from the Devil key sequence.\n%% - The percent sign.\"\n  (format-spec format-string (list (cons ?k (key-description devil-key))\n                                   (cons ?r (key-description key))\n                                   (cons ?t (devil--translate key))\n                                   (cons ?% \"%\"))))\n\n(defun devil-string-replace (from-string to-string in-string)\n  \"Replace FROM-STRING with TO-STRING in IN-STRING.\"\n  (let ((case-fold-search nil))\n    (replace-regexp-in-string (regexp-quote from-string)\n                              to-string in-string t t)))\n\n(defun devil-regexp-replace (regexp replacement in-string)\n  \"Replace REGEXP with REPLACEMENT in IN-STRING.\"\n  (let ((case-fold-search nil))\n    (replace-regexp-in-string regexp replacement in-string t)))\n\n(defun devil--aget (key alist)\n  \"Find KEY in ALIST and return corresponding value.\"\n  (cdr (assoc key alist)))\n\n(provide 'devil)\n;;; devil.el ends here\n"
  }
]